diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000..2ec3c7610 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,12 @@ +[profile.default] +test-threads = 1 +default-filter = 'not test(skip_local) and not test(skip_ci)' + +[profile.ci] +failure-output = "final" +test-threads = 1 +fail-fast = false +default-filter = 'not test(skip_ci)' + +[profile.ci.junit] +path = "junit.xml" diff --git a/.evergreen/MSRV-Cargo.lock b/.evergreen/MSRV-Cargo.lock deleted file mode 100644 index 46481024d..000000000 --- a/.evergreen/MSRV-Cargo.lock +++ /dev/null @@ -1,2505 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "0.7.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" -dependencies = [ - "memchr", -] - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "async-channel" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-executor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "once_cell", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" -dependencies = [ - "async-channel", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "num_cpus", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" -dependencies = [ - "concurrent-queue", - "futures-lite", - "libc", - "log", - "once_cell", - "parking", - "polling", - "slab", - "socket2", - "waker-fn", - "winapi", -] - -[[package]] -name = "async-lock" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" -dependencies = [ - "async-io", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "libc", - "once_cell", - "signal-hook", - "winapi", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-attributes", - "async-channel", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-std-resolver" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2f8a4a203be3325981310ab243a28e6e4ea55b6519bffce05d41ab60e09ad8" -dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "socket2", - "trust-dns-resolver", -] - -[[package]] -name = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-task" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" - -[[package]] -name = "async-trait" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async_once" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" - -[[package]] -name = "atomic-waker" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" -dependencies = [ - "async-channel", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "once_cell", -] - -[[package]] -name = "bson" -version = "2.3.0" -source = "git+https://github.com/mongodb/bson-rust?branch=main#0612667e1344f9aabc3592a2cee02a96ac1b76bc" -dependencies = [ - "ahash", - "base64", - "chrono", - "hex", - "indexmap", - "lazy_static", - "rand", - "serde", - "serde_bytes", - "serde_json", - "serde_with", - "time", - "uuid 0.8.2", - "uuid 1.1.2", -] - -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - -[[package]] -name = "bytes" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b3de4a0c5e67e16066a0715723abd91edc2f9001d09c46e1dca929351e130e" - -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "winapi", -] - -[[package]] -name = "concurrent-queue" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" -dependencies = [ - "cache-padded", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "ctor" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "ctrlc" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" -dependencies = [ - "nix", - "winapi", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "either" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum-as-inner" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "event-listener" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "flate2" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "function_name" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef632c665dc6e2b99ffa4d913f7160bd902c4d3e4cb732d81dc3d221f848512" -dependencies = [ - "function_name-proc-macro", -] - -[[package]] -name = "function_name-proc-macro" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569d2238870f92cff64fc810013b61edaf446ebcfba36b649b96bc5b4078328a" -dependencies = [ - "proc-macro-crate", - "quote", - "syn", -] - -[[package]] -name = "futures" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-executor" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" - -[[package]] -name = "futures-lite" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gloo-timers" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654" -dependencies = [ - "winapi", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" -dependencies = [ - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "ipconfig" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" -dependencies = [ - "socket2", - "widestring", - "winapi", - "winreg 0.7.0", -] - -[[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" - -[[package]] -name = "itoa" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lambda_runtime" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a81840726d481d20b99a9ce87430f644e9590cb77715e1e66c5f4432c9b586" -dependencies = [ - "async-stream", - "bytes", - "futures", - "http", - "hyper", - "lambda_runtime_api_client", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tower", - "tracing", -] - -[[package]] -name = "lambda_runtime_api_client" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54698c666ffe503cb51fa66e4567e53e806128a10359de7095999d925a771ed" -dependencies = [ - "http", - "hyper", - "tokio", - "tower-service", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", - "value-bag", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "md-5" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" -dependencies = [ - "digest", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "mongocrypt" -version = "0.1.2" -source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main" -dependencies = [ - "bson", - "mongocrypt-sys", - "serde", -] - -[[package]] -name = "mongocrypt-sys" -version = "0.1.2+1.8.0-alpha1" -source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#eee5a9817cdfb92204f6167ade5064f540e8b9e9" - -[[package]] -name = "mongodb" -version = "2.4.0" -dependencies = [ - "approx", - "async-std", - "async-std-resolver", - "async-trait", - "async_once", - "base64", - "bitflags", - "bson", - "chrono", - "ctrlc", - "derivative", - "derive_more", - "flate2", - "function_name", - "futures", - "futures-core", - "futures-executor", - "futures-util", - "hex", - "hmac", - "home", - "lambda_runtime", - "lazy_static", - "md-5", - "mongocrypt", - "num_cpus", - "openssl", - "openssl-probe", - "pbkdf2", - "percent-encoding", - "pretty_assertions", - "rand", - "rayon", - "regex", - "reqwest", - "rustc_version_runtime", - "rustls", - "rustls-pemfile", - "semver 1.0.12", - "serde", - "serde_bytes", - "serde_json", - "serde_with", - "sha-1", - "sha2", - "snap", - "socket2", - "stringprep", - "strsim", - "take_mut", - "thiserror", - "time", - "tokio", - "tokio-openssl", - "tokio-rustls", - "tokio-util", - "trust-dns-proto", - "trust-dns-resolver", - "typed-builder", - "uuid 1.1.2", - "webpki-roots", - "zstd", -] - -[[package]] -name = "nix" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" - -[[package]] -name = "openssl" -version = "0.10.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - -[[package]] -name = "parking" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pin-project" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" - -[[package]] -name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" -dependencies = [ - "cfg-if", - "libc", - "log", - "wepoll-ffi", - "winapi", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "pretty_assertions" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" -dependencies = [ - "ctor", - "diff", - "output_vt100", - "yansi", -] - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcc2916cde080c1876ff40292a396541241fe0072ef928cd76582e9ea5d60d2" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quote" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rayon" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "reqwest" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-rustls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", - "winreg 0.10.1", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.12", -] - -[[package]] -name = "rustc_version_runtime" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f" -dependencies = [ - "rustc_version 0.2.3", - "semver 0.9.0", -] - -[[package]] -name = "rustls" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" -dependencies = [ - "base64", -] - -[[package]] -name = "ryu" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "serde" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.140" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "signal-hook" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" - -[[package]] -name = "snap" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" - -[[package]] -name = "socket2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "stringprep" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - -[[package]] -name = "thiserror" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" -dependencies = [ - "itoa", - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tokio" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "once_cell", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "winapi", -] - -[[package]] -name = "tokio-macros" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-openssl" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" -dependencies = [ - "futures-util", - "openssl", - "openssl-sys", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-stream" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" -dependencies = [ - "once_cell", -] - -[[package]] -name = "trust-dns-proto" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "lazy_static", - "log", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "parking_lot", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "trust-dns-proto", -] - -[[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "typed-builder" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-bidi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" - -[[package]] -name = "unicode-ident" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" - -[[package]] -name = "unicode-normalization" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "uuid" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "value-bag" -version = "1.0.0-alpha.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" -dependencies = [ - "ctor", - "version_check", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" - -[[package]] -name = "web-sys" -version = "0.3.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "webpki-roots" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" -dependencies = [ - "webpki", -] - -[[package]] -name = "wepoll-ffi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" -dependencies = [ - "cc", -] - -[[package]] -name = "widestring" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi", -] - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.1+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" -dependencies = [ - "cc", - "libc", -] diff --git a/.evergreen/aws-ecs-test/src/main.rs b/.evergreen/aws-ecs-test/src/main.rs index 27c9bf7bd..3bcea7c89 100644 --- a/.evergreen/aws-ecs-test/src/main.rs +++ b/.evergreen/aws-ecs-test/src/main.rs @@ -1,14 +1,17 @@ -use mongodb::{bson::Document, Client}; +use mongodb::{ + bson::{doc, Document}, + Client, +}; #[tokio::main] async fn main() { let uri = std::env::var("MONGODB_URI").expect("no URI given!"); let client = Client::with_uri_str(&uri).await.unwrap(); - + client .database("aws") .collection::("somecoll") - .find_one(None, None) + .find_one(doc! {}) .await .unwrap(); } diff --git a/.evergreen/aws-lambda-test/.gitignore b/.evergreen/aws-lambda-test/.gitignore new file mode 100644 index 000000000..2f6cd7518 --- /dev/null +++ b/.evergreen/aws-lambda-test/.gitignore @@ -0,0 +1,224 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,osx,linux,windows,pycharm,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,osx,linux,windows,pycharm,visualstudiocode + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/rust,osx,linux,windows,pycharm,visualstudiocode + +.aws-sam \ No newline at end of file diff --git a/.evergreen/aws-lambda-test/README.md b/.evergreen/aws-lambda-test/README.md new file mode 100644 index 000000000..46b2eaa99 --- /dev/null +++ b/.evergreen/aws-lambda-test/README.md @@ -0,0 +1,128 @@ +# aws-lambda-test + +This project contains source code and supporting files for a serverless application that you can deploy with the SAM CLI. It includes the following files and folders: + +- `rust_app/Cargo.toml` - Project configuration file. +- `rust_app/src/main.rs` - Code for the application's Lambda function. +- `template.yaml` - A template that defines the application's AWS resources. + +The application uses several AWS resources, including Lambda functions and an API Gateway API. These resources are defined in the `template.yaml` file in this project. You can update the template to add AWS resources through the same deployment process that updates your application code. + +If you prefer to use an integrated development environment (IDE) to build and test your application, you can use the AWS Toolkit. +The AWS Toolkit is an open source plug-in for popular IDEs that uses the SAM CLI to build and deploy serverless applications on AWS. The AWS Toolkit also adds a simplified step-through debugging experience for Lambda function code. See the following links to get started. + +* [CLion](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [GoLand](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [IntelliJ](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [WebStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [Rider](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PhpStorm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [PyCharm](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [RubyMine](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [DataGrip](https://docs.aws.amazon.com/toolkit-for-jetbrains/latest/userguide/welcome.html) +* [VS Code](https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/welcome.html) +* [Visual Studio](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/welcome.html) + + +## Requirements +* This template was tested with Rust v1.66.0 and above. + +## Deploy the sample application + +To deploy the application, you need the folllowing tools: + +* SAM CLI - [Install the SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) +* Docker - [Install Docker community edition](https://hub.docker.com/search/?type=edition&offering=community) +* [Rust](https://www.rust-lang.org/) version 1.82.0 or newer +* [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda) for cross-compilation + +To build and deploy your application for the first time, run the following in your shell: + +```bash +sam build +sam deploy +``` + +The first command will build the source of your application. The second command will package and deploy your application to AWS with the default `samconfig.toml` in the project. Alternatively, you can run `sam deploy --guided` to deploy with a series of prompts: + +* **Stack Name**: The name of the stack to deploy to CloudFormation. This should be unique to your account and region, and a good starting point would be something matching your project name. +* **AWS Region**: The AWS region you want to deploy your app to. +* **Confirm changes before deploy**: If set to yes, any change sets will be shown to you before execution for manual review. If set to no, the AWS SAM CLI will automatically deploy application changes. +* **Allow SAM CLI IAM role creation**: Many AWS SAM templates, including this example, create AWS IAM roles required for the AWS Lambda function(s) included to access AWS services. By default, these are scoped down to minimum required permissions. To deploy an AWS CloudFormation stack which creates or modifies IAM roles, the `CAPABILITY_IAM` value for `capabilities` must be provided. If permission isn't provided through this prompt, to deploy this example you must explicitly pass `--capabilities CAPABILITY_IAM` to the `sam deploy` command. +* **Save arguments to `samconfig.toml`**: If set to yes, your choices will be saved to a configuration file inside the project, so that in the future you can just re-run `sam deploy` without parameters to deploy changes to your application. + +You can find your API Gateway Endpoint URL in the output values displayed after deployment. + +## Use the SAM CLI to build and test locally + +Build your application with the `sam build` command. + +```bash +aws-lambda-test$ sam build +``` + +The SAM CLI builds the Rust app based on `rust_app/Cargo.toml`, creates a deployment package, and saves it in the `.aws-sam/build` folder. + +Test a single function by invoking it directly with a test event. An event is a JSON document that represents the input that the function receives from the event source. Test events are included in the `events` folder in this project. + +Run functions locally and invoke them with the `sam local invoke` command. + +```bash +aws-lambda-test$ sam local invoke HelloWorldFunction --event events/event.json +``` + +The SAM CLI can also emulate your application's API. Use the `sam local start-api` to run the API locally on port 3000. + +```bash +aws-lambda-test$ sam local start-api +aws-lambda-test$ curl http://localhost:3000/ +``` + +The SAM CLI reads the application template to determine the API's routes and the functions that they invoke. The `Events` property on each function's definition includes the route and method for each path. + +```yaml + Events: + HelloWorld: + Type: Api + Properties: + Path: /hello + Method: get +``` + +## Add a resource to your application +The application template uses AWS Serverless Application Model (AWS SAM) to define application resources. AWS SAM is an extension of AWS CloudFormation with a simpler syntax for configuring common serverless application resources such as functions, triggers, and APIs. For resources not included in [the SAM specification](https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md), you can use standard [AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html) resource types. + +## Fetch, tail, and filter Lambda function logs + +To simplify troubleshooting, SAM CLI has a command called `sam logs`. `sam logs` lets you fetch logs generated by your deployed Lambda function from the command line. In addition to printing the logs on the terminal, this command has several nifty features to help you quickly find the bug. + +`NOTE`: This command works for all AWS Lambda functions; not just the ones you deploy using SAM. + +```bash +aws-lambda-test$ sam logs -n HelloWorldFunction --stack-name aws-lambda-test --tail +``` + +You can find more information and examples about filtering Lambda function logs in the [SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-logging.html). + +## Tests + +Tests are defined alongside your lambda function code in the `rust_app/src` folder. + +```bash +cargo test +``` + + +## Cleanup + +To delete the sample application that you created, use the AWS CLI. Assuming you used your project name for the stack name, you can run the following: + +```bash +sam delete +``` + +## Resources + +See the [AWS SAM developer guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) for an introduction to SAM specification, the SAM CLI, and serverless application concepts. + +Next, you can use AWS Serverless Application Repository to deploy ready-to-use apps that go beyond hello world samples and learn how authors developed their applications: [AWS Serverless Application Repository main page](https://aws.amazon.com/serverless/serverlessrepo/). diff --git a/.evergreen/aws-lambda-test/events/event.json b/.evergreen/aws-lambda-test/events/event.json new file mode 100644 index 000000000..0956a139e --- /dev/null +++ b/.evergreen/aws-lambda-test/events/event.json @@ -0,0 +1,62 @@ +{ + "body": "hello world", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "/path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "accessKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" + } +} \ No newline at end of file diff --git a/.evergreen/aws-lambda-test/mongodb/Cargo.toml b/.evergreen/aws-lambda-test/mongodb/Cargo.toml new file mode 100644 index 000000000..a5fc0fb87 --- /dev/null +++ b/.evergreen/aws-lambda-test/mongodb/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "aws-lambda-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +lambda_runtime = "0.6.0" +mongodb = { path = "../../.." } +serde = "1.0.136" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/.evergreen/aws-lambda-test/mongodb/src/main.rs b/.evergreen/aws-lambda-test/mongodb/src/main.rs new file mode 100644 index 000000000..5d939f47b --- /dev/null +++ b/.evergreen/aws-lambda-test/mongodb/src/main.rs @@ -0,0 +1,144 @@ +use std::sync::Arc; + +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use mongodb::{ + bson::doc, + event::{cmap::CmapEvent, command::CommandEvent, sdam::SdamEvent, EventHandler}, + options::ClientOptions, + Client, +}; +use serde::{Deserialize, Serialize}; +use tokio::sync::OnceCell; + +struct State { + client: Client, + stats: Arc>, +} + +#[derive(Clone, Serialize)] +struct Stats { + heartbeats_started: u32, + failed_heartbeat_durations_millis: Vec, + command_succeeded_durations_millis: Vec, + command_failed_durations_millis: Vec, + connections_open: u32, + max_connections_open: u32, +} + +impl Stats { + fn new() -> Self { + Self { + heartbeats_started: 0, + failed_heartbeat_durations_millis: vec![], + command_succeeded_durations_millis: vec![], + command_failed_durations_millis: vec![], + connections_open: 0, + max_connections_open: 0, + } + } + + fn handle_sdam(&mut self, event: &SdamEvent) { + match event { + SdamEvent::ServerHeartbeatStarted(ev) => { + assert!(!ev.awaited); + self.heartbeats_started += 1; + } + SdamEvent::ServerHeartbeatFailed(ev) => { + assert!(!ev.awaited); + self.failed_heartbeat_durations_millis + .push(ev.duration.as_millis()); + } + _ => (), + } + } + + fn handle_command(&mut self, event: &CommandEvent) { + match event { + CommandEvent::Succeeded(ev) => { + self.command_succeeded_durations_millis + .push(ev.duration.as_millis()); + } + CommandEvent::Failed(ev) => { + self.command_failed_durations_millis + .push(ev.duration.as_millis()); + } + _ => (), + } + } + + fn handle_cmap(&mut self, event: &CmapEvent) { + match event { + CmapEvent::ConnectionCreated(_) => { + self.connections_open += 1; + self.max_connections_open = + std::cmp::max(self.connections_open, self.max_connections_open); + } + CmapEvent::ConnectionClosed(_) => { + self.connections_open -= 1; + } + _ => (), + } + } +} + +impl State { + async fn new() -> Self { + let uri = std::env::var("MONGODB_URI") + .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); + let mut options = ClientOptions::parse(uri) + .await + .expect("Failed to parse URI"); + let stats = Arc::new(std::sync::Mutex::new(Stats::new())); + { + let stats = Arc::clone(&stats); + options.sdam_event_handler = Some(EventHandler::callback(move |ev| { + stats.lock().unwrap().handle_sdam(&ev) + })); + } + { + let stats = Arc::clone(&stats); + options.command_event_handler = Some(EventHandler::callback(move |ev| { + stats.lock().unwrap().handle_command(&ev) + })); + } + { + let stats = Arc::clone(&stats); + options.cmap_event_handler = Some(EventHandler::callback(move |ev| { + stats.lock().unwrap().handle_cmap(&ev) + })); + } + + let client = Client::with_options(options).expect("Failed to create MongoDB Client"); + + Self { client, stats } + } +} + +static STATE: OnceCell = OnceCell::const_new(); + +async fn get_state() -> &'static State { + STATE.get_or_init(State::new).await +} + +#[derive(Deserialize)] +struct Request {} + +async fn function_handler(_event: LambdaEvent) -> Result { + let state = get_state().await; + let coll = state.client.database("faas_test").collection("faas_test"); + let id = coll.insert_one(doc! {}).await?.inserted_id; + coll.delete_one(doc! { "id": id }).await?; + + let stats = { + let mut guard = state.stats.lock().unwrap(); + let value = guard.clone(); + *guard = Stats::new(); + value + }; + Ok(stats) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(function_handler)).await +} diff --git a/.evergreen/aws-lambda-test/samconfig.toml b/.evergreen/aws-lambda-test/samconfig.toml new file mode 100644 index 000000000..5eaf3d359 --- /dev/null +++ b/.evergreen/aws-lambda-test/samconfig.toml @@ -0,0 +1,31 @@ +# More information about the configuration file can be found here: +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html +version = 0.1 + +[default] +[default.global.parameters] +stack_name = "aws-lambda-test" + +[default.build.parameters] +cached = true +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +resolve_s3 = true + +[default.package.parameters] +resolve_s3 = true + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" diff --git a/.evergreen/aws-lambda-test/template.yaml b/.evergreen/aws-lambda-test/template.yaml new file mode 100644 index 000000000..ecf35bcff --- /dev/null +++ b/.evergreen/aws-lambda-test/template.yaml @@ -0,0 +1,53 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: > + aws-lambda-test + + Sample SAM Template for aws-lambda-test + +# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +Globals: + Function: + Timeout: 30 + MemorySize: 128 + +Parameters: + MongoDbUri: + Type: String + Description: The MongoDB connection string. + +Resources: + MongoDBFunction: + Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction + Metadata: + BuildMethod: rust-cargolambda # More info about Cargo Lambda: https://github.com/cargo-lambda/cargo-lambda + Properties: + CodeUri: ./mongodb # Points to dir of Cargo.toml + Handler: bootstrap # Do not change, as this is the default executable name produced by Cargo Lambda + Runtime: provided.al2 + Architectures: + #- arm64 # local testing on M1 + - x86_64 # deployment on evergreen + Environment: + Variables: + MONGODB_URI: !Ref MongoDbUri + Events: + MongoDB: + Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api + Properties: + Path: /mongodb + Method: get + +Outputs: + # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function + # Find out more about other implicit resources you can reference within SAM + # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api + MongoDBApi: + Description: "API Gateway endpoint URL for Prod stage for Hello World function" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/" + MongoDBFunction: + Description: "Hello World Lambda Function ARN" + Value: !GetAtt MongoDBFunction.Arn + MongoDBFunctionIamRole: + Description: "Implicit IAM Role created for Hello World function" + Value: !GetAtt MongoDBFunctionRole.Arn diff --git a/.evergreen/azure-kms-test/Cargo.toml b/.evergreen/azure-kms-test/Cargo.toml index a659f4b86..93ffb787d 100644 --- a/.evergreen/azure-kms-test/Cargo.toml +++ b/.evergreen/azure-kms-test/Cargo.toml @@ -10,4 +10,4 @@ tokio = "1.28.1" [dependencies.mongodb] path = "../.." -features = ["in-use-encryption-unstable", "azure-kms"] +features = ["in-use-encryption", "azure-kms"] diff --git a/.evergreen/azure-kms-test/src/main.rs b/.evergreen/azure-kms-test/src/main.rs index c0c25f3b0..4b5facf74 100644 --- a/.evergreen/azure-kms-test/src/main.rs +++ b/.evergreen/azure-kms-test/src/main.rs @@ -1,23 +1,32 @@ use mongodb::{ bson::doc, - Client, client_encryption::{ClientEncryption, MasterKey}, mongocrypt::ctx::KmsProvider, Namespace, + client_encryption::{AzureMasterKey, ClientEncryption, MasterKey}, error::Result, + mongocrypt::ctx::KmsProvider, + Client, + Namespace, }; +use std::env; + #[tokio::main] async fn main() -> Result<()> { let c = ClientEncryption::new( Client::with_uri_str("mongodb://localhost:27017").await?, Namespace::new("keyvault", "datakeys"), - [(KmsProvider::Azure, doc! { }, None)], + [(KmsProvider::azure(), doc! {}, None)], )?; - c.create_data_key(MasterKey::Azure { - key_vault_endpoint: "https://keyvault-drivers-2411.vault.azure.net/keys/".to_string(), - key_name: "KEY-NAME".to_string(), - key_version: None, - }) - .run() + let key_name = env::var("KEY_NAME").expect("KEY_NAME environment variable should be set"); + let key_vault_endpoint = env::var("KEY_VAULT_ENDPOINT") + .expect("KEY_VAULT_ENDPOINT environment variable should be set"); + + c.create_data_key(MasterKey::Azure( + AzureMasterKey::builder() + .key_vault_endpoint(key_vault_endpoint) + .key_name(key_name) + .build(), + )) .await?; println!("Azure KMS integration test passed!"); diff --git a/.evergreen/benchmarks.yml b/.evergreen/benchmarks.yml index fb63a7791..b401b464a 100644 --- a/.evergreen/benchmarks.yml +++ b/.evergreen/benchmarks.yml @@ -132,15 +132,13 @@ functions: AUTH=${AUTH} \ SSL=${SSL} \ REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \ + SKIP_LEGACY_SHELL=1 \ sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh # run-orchestration generates expansion file with the MONGODB_URI for the cluster - command: expansions.update params: file: mo-expansion.yml - - "run driver benchmarks": - command: shell.exec - type: test params: shell: bash working_dir: "src" @@ -149,9 +147,21 @@ functions: export MONGODB_URI="${MONGODB_URI}" export SSL="${SSL}" . .evergreen/generate-uri.sh + - command: expansions.update + params: + file: src/uri-expansions.yml - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - .evergreen/run-driver-benchmarks.sh + "run driver benchmarks": + - command: shell.exec + type: test + params: + shell: bash + working_dir: "src" + include_expansions_in_env: + - MONGODB_URI + script: | + ${PREPARE_SHELL} + .evergreen/run-driver-benchmarks.sh "run bson benchmarks": - command: shell.exec @@ -172,7 +182,7 @@ functions: working_dir: "src" script: | ${PREPARE_SHELL} - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-compile-benchmarks.sh + .evergreen/run-compile-benchmarks.sh "upload-mo-artifacts": - command: shell.exec @@ -192,9 +202,39 @@ functions: display_name: "mongodb-logs.tar.gz" "upload benchmark results": - - command: perf.send + - command: shell.exec params: - file: "src/benchmark-results.json" + script: | + # We use the requester expansion to determine whether the data is from a mainline evergreen run or not + if [ "${requester}" == "commit" ]; then + is_mainline=true + else + is_mainline=false + fi + + parsed_order_id=$(echo "${revision_order_id}" | awk -F'_' '{print $NF}') + + ENCODED_URL=$(echo "https://performance-monitoring-api.corp.mongodb.com/raw_perf_results/cedar_report?project=${project_id}&version=${version_id}&variant=${build_variant}&order=$parsed_order_id&task_name=${task_name}&task_id=${task_id}&execution=${execution}&mainline=$is_mainline" | sed -e 's/ /%20/g') + + # Submit the performance data to the SPS endpoint + response=$(curl -s -w "\nHTTP_STATUS:%{http_code}" -X 'POST' \ + "$ENCODED_URL" \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + -d @src/benchmark-results.json) + + http_status=$(echo "$response" | grep "HTTP_STATUS" | awk -F':' '{print $2}') + response_body=$(echo "$response" | sed '/HTTP_STATUS/d') + + # We want to throw an error if the data was not successfully submitted + if [ "$http_status" -ne 200 ]; then + echo "Error: Received HTTP status $http_status" + echo "Response Body: $response_body" + exit 1 + fi + + echo "Response Body: $response_body" + echo "HTTP Status: $http_status" "stop mongo orchestration": - command: shell.exec @@ -275,33 +315,18 @@ post: - func: "cleanup" tasks: - - name: "benchmark-rapid-standalone" - tags: ["rapid", "standalone"] + - name: "benchmark-driver" + tags: ["driver"] commands: - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "server" - - func: "run driver benchmarks" - - func: "upload benchmark results" - - - name: "benchmark-rapid-replica_set" - tags: ["rapid", "replica_set"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "replica_set" - - func: "run driver benchmarks" - - func: "upload benchmark results" - - - name: "benchmark-rapid-sharded_cluster" - tags: ["rapid", "sharded_cluster"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "sharded_cluster" + MONGODB_VERSION: "v8.0-perf" + # Note that drivers-evergreen-tools expects `SSL` as the environmental + # variable, not `TLS`, so we have to use that for the actual value used in the + # script; we use `TLS` for the metadata that isn't used by the actual shell + # scripts. + AUTH: "auth" + SSL: "ssl" - func: "run driver benchmarks" - func: "upload benchmark results" @@ -317,14 +342,6 @@ tasks: - func: "upload benchmark results" axes: - - id: "mongodb-version" - display_name: MongoDB Version - values: - - id: "rapid" - display_name: "rapid" - variables: - MONGODB_VERSION: "rapid" - - id: "topology" display_name: Topology values: @@ -341,71 +358,31 @@ axes: variables: TOPOLOGY: "sharded_cluster" - - id: "async-runtime" - display_name: Async Runtime - values: - - id: "tokio" - display_name: "tokio" - variables: - ASYNC_RUNTIME: "tokio" - - id: "async-std" - display_name: "async-std" - variables: - ASYNC_RUNTIME: "async-std" - - # Note that drivers-evergreen-tools expects `SSL` as the environmental - # variable, not `TLS`, so we have to use that for the actual value used in the - # script; we use `TLS` for the metadata that isn't used by the actual shell - # scripts. - - id: "auth-and-tls" - display_name: Authentication and TLS - values: - - id: "auth-and-tls" - display_name: Auth TLS - variables: - AUTH: "auth" - SSL: "ssl" - - id: "os" display_name: OS values: - - id: ubuntu-18.04 - display_name: "Ubuntu 18.04" - run_on: ubuntu1804-test + - id: rhel90-dbx-perf-large + display_name: "RHEL 90 (perf)" + run_on: rhel90-dbx-perf-large variables: PYTHON: "/opt/mongodbtoolchain/v3/bin/python" VENV_BIN_DIR: "bin" - # TODO: RUST-990 re-enable macOS benchmarks - # - id: macos-10.14 - # display_name: "MacOS 10.14" - # run_on: macos-1014 - # variables: - # PYTHON: "/opt/mongodbtoolchain/v3/bin/python" - # VENV_BIN_DIR: "bin" - - id: windows-64-vs2017 - display_name: "Windows (VS 2017)" - run_on: windows-64-vs2017-test - variables: - PYTHON: "/cygdrive/c/python/Python36/python" - VENV_BIN_DIR: "Scripts" buildvariants: - matrix_name: "driver benchmarks" matrix_spec: os: - - ubuntu-18.04 - - windows-64-vs2017 - auth-and-tls: "*" - async-runtime: "*" - display_name: "${os} ${auth-and-tls} with ${async-runtime}" + - rhel90-dbx-perf-large + topology: "*" + display_name: "Benchmark ${topology}" tasks: - - ".rapid" + - ".driver" - matrix_name: "bson benchmarks" matrix_spec: os: - - ubuntu-18.04 + - rhel90-dbx-perf-large display_name: "BSON Benchmarks" tasks: - ".bson" @@ -413,9 +390,7 @@ buildvariants: - matrix_name: "compile-only" matrix_spec: os: - - ubuntu-18.04 - async-runtime: "*" - display_name: "Compile on ${os} with ${async-runtime}" + - rhel90-dbx-perf-large + display_name: "Compile" tasks: - "benchmark-compile" - diff --git a/.evergreen/build-static-test-tarball.sh b/.evergreen/build-static-test-tarball.sh new file mode 100644 index 000000000..8c923e062 --- /dev/null +++ b/.evergreen/build-static-test-tarball.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +source ./.evergreen/env.sh + +rm -rf test_files && mkdir test_files +cp ${TEST_FILES}/* test_files + +export RUSTFLAGS="-C target-feature=+crt-static" +cargo test ${BUILD_FEATURES} --target x86_64-unknown-linux-gnu get_exe_name +TEST_BINARY=$(cat exe_name.txt) +TEST_TARBALL="/tmp/mongo-rust-driver.tar.gz" +tar czvf ${TEST_TARBALL} ${TEST_BINARY} ./.evergreen test_files + +cat < static-test-tarball-expansion.yml +STATIC_TEST_BINARY: ${TEST_BINARY} +STATIC_TEST_TARBALL: ${TEST_TARBALL} +EOT diff --git a/.evergreen/cargo-test.sh b/.evergreen/cargo-test.sh new file mode 100644 index 000000000..d9544d19b --- /dev/null +++ b/.evergreen/cargo-test.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +CARGO_OPTIONS=() +TEST_OPTIONS=() +FEATURE_FLAGS=() +CARGO_RESULT=0 + +join_by() { + local IFS="$1" + shift + echo "$*" +} + +cargo_test_options() { + local FILTERED=() + for FEAT in "${FEATURE_FLAGS[@]}"; do + [[ "${FEAT}" != "" ]] && FILTERED+=("${FEAT}") + done + local FEATURE_OPTION="" + if ((${#FILTERED[@]} != 0)); then + FEATURE_OPTION="--features $(join_by , "${FILTERED[@]}")" + fi + echo $1 ${CARGO_OPTIONS[@]} ${FEATURE_OPTION} -- ${TEST_OPTIONS[@]} +} + +cargo_test() { + LOG_PATH=$(mktemp) + tail -f ${LOG_PATH} & + TAIL_PID=$! + LOG_UNCAPTURED=${LOG_PATH} RUST_BACKTRACE=1 cargo nextest run --profile ci $(cargo_test_options $1) + ((CARGO_RESULT = ${CARGO_RESULT} || $?)) + if [[ -f "results.xml" ]]; then + mv results.xml previous.xml + merge-junit -o results.xml previous.xml target/nextest/ci/junit.xml + else + mv target/nextest/ci/junit.xml results.xml + fi + kill ${TAIL_PID} + rm ${LOG_PATH} +} diff --git a/.evergreen/check-cargo-deny.sh b/.evergreen/check-cargo-deny.sh index 4d4899bad..d9345b936 100755 --- a/.evergreen/check-cargo-deny.sh +++ b/.evergreen/check-cargo-deny.sh @@ -1,8 +1,9 @@ #!/bin/bash set -o errexit +set -o xtrace -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh cargo install --locked cargo-deny -cargo deny --all-features check \ No newline at end of file +cargo deny --all-features check diff --git a/.evergreen/check-clippy.sh b/.evergreen/check-clippy.sh index 0a6a83fce..c3c0761c0 100755 --- a/.evergreen/check-clippy.sh +++ b/.evergreen/check-clippy.sh @@ -2,14 +2,15 @@ set -o errexit -source ./.evergreen/configure-rust.sh -source ./.evergreen/feature-combinations.sh +source ./.evergreen/env.sh # Pin clippy to the latest version. This should be updated when new versions of Rust are released. -CLIPPY_VERSION=1.67.0 +CLIPPY_VERSION=1.85.0 rustup install $CLIPPY_VERSION -for ((i = 0; i < ${#FEATURE_COMBINATIONS[@]}; i++)); do - cargo +$CLIPPY_VERSION clippy --all-targets ${FEATURE_COMBINATIONS[$i]} -p mongodb -- -D warnings -done +# Check with default features. +cargo +$CLIPPY_VERSION clippy --all-targets -p mongodb -- -D warnings + +# Check with all features. +cargo +$CLIPPY_VERSION clippy --all-targets --all-features -p mongodb -- -D warnings diff --git a/.evergreen/check-rustdoc.sh b/.evergreen/check-rustdoc.sh index 7329f4006..cebd17dbc 100755 --- a/.evergreen/check-rustdoc.sh +++ b/.evergreen/check-rustdoc.sh @@ -2,23 +2,21 @@ set -o errexit -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh # docs.rs builds the driver on a read-only file system. to create a more realistic environment, we first # build the driver to ensure we have all the deps already in src, and then limit the permissions on that directory # and rebuild the docs. -# this is to help us avoid introducing problems like those described here +# this is to help us avoid introducing problems like those described here # https://docs.rs/about/builds#read-only-directories where we or a dependency modify source code during the # build process. -source ./.evergreen/feature-combinations.sh - # build with all available features to ensure all optional dependencies are brought in too. -cargo +nightly build $ADDITIONAL_FEATURES +cargo +nightly build --all-features cargo clean chmod -R 555 ${CARGO_HOME}/registry/src # this invocation mirrors the way docs.rs builds our documentation (see the [package.metadata.docs.rs] section # in Cargo.toml). -cargo +nightly rustdoc $ADDITIONAL_FEATURES -- -D warnings --cfg docsrs +cargo +nightly rustdoc --all-features -- -D warnings --cfg docsrs diff --git a/.evergreen/check-rustfmt.sh b/.evergreen/check-rustfmt.sh index 18f2503de..c42ee9efb 100755 --- a/.evergreen/check-rustfmt.sh +++ b/.evergreen/check-rustfmt.sh @@ -2,6 +2,6 @@ set -o errexit -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh rustfmt +nightly --unstable-features --check src/**/*.rs rustfmt +nightly --unstable-features --check src/*.rs diff --git a/.evergreen/check-semgrep.sh b/.evergreen/check-semgrep.sh new file mode 100755 index 000000000..78fcc949e --- /dev/null +++ b/.evergreen/check-semgrep.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -o errexit + +if [ -t 0 ] ; then + # Interactive shell + PYTHON3=${PYTHON3:-"python3"} +else + # Evergreen run (probably) + source ./.evergreen/env.sh + source ${DRIVERS_TOOLS}/.evergreen/find-python3.sh + PYTHON3=$(find_python3) +fi + +if [[ -f "semgrep/bin/activate" ]]; then + echo 'Using existing virtualenv...' + . semgrep/bin/activate +else + echo 'Creating new virtualenv...' + ${PYTHON3} -m venv semgrep + echo 'Activating new virtualenv...' + . semgrep/bin/activate + echo 'Installing semgrep...' + python3 -m pip install semgrep +fi + +# Show human-readable output +semgrep --config p/rust --error +# Generate a SARIF report +semgrep --config p/rust --quiet --sarif -o sarif.json \ No newline at end of file diff --git a/.evergreen/compile-only.sh b/.evergreen/compile-only.sh index 485967b16..3742566da 100755 --- a/.evergreen/compile-only.sh +++ b/.evergreen/compile-only.sh @@ -3,17 +3,28 @@ set -o errexit set -o xtrace -source ./.evergreen/configure-rust.sh -rustup update $RUST_VERSION +source ./.evergreen/env.sh -# pin dependencies who have bumped their MSRVs to > ours in recent releases. -if [ "$MSRV" = "true" ]; then - cp .evergreen/MSRV-Cargo.lock Cargo.lock +# Install the MSRV and generate a new lockfile with MSRV-compatible dependencies. +if [ "$RUST_VERSION" != "" ]; then + rustup toolchain install $RUST_VERSION + TOOLCHAIN="+${RUST_VERSION}" + CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback cargo +nightly -Zmsrv-policy generate-lockfile fi -source ./.evergreen/feature-combinations.sh +# Test with default features. +cargo $TOOLCHAIN build -# build with all available features to ensure all optional dependencies are brought in too. -for ((i = 0; i < ${#FEATURE_COMBINATIONS[@]}; i++)); do - rustup run $RUST_VERSION cargo build ${FEATURE_COMBINATIONS[$i]} -done +# Test with all features. +if [ "$RUST_VERSION" != "" ]; then + cargo $TOOLCHAIN build --features openssl-tls,sync,aws-auth,gssapi-auth,zlib-compression,zstd-compression,snappy-compression,in-use-encryption,tracing-unstable +else + cargo $TOOLCHAIN build --all-features +fi + +# Test with no default features. +if [ "$RUST_VERSION" != "" ]; then + cargo $TOOLCHAIN build --no-default-features --features compat-3-0-0,rustls-tls +else + cargo $TOOLCHAIN build --no-default-features --features compat-3-3-0,bson-3,rustls-tls +fi diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 0f83b08bf..e40e29c64 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -1,2034 +1,1940 @@ -######################################## -# Evergreen Template for MongoDB Drivers -######################################## +############################################################ +# Evergreen configuration file for the MongoDB Rust Driver # +############################################################ -# When a task that used to pass starts to fail -# Go through all versions that may have been skipped to detect -# when the task started failing +# If a task fails, go through all versions that may have been skipped to detect when it started +# failing. stepback: true -# Mark a failure as a system/bootstrap failure (purple box) rather then a task -# failure by default. -# Actual testing tasks are marked with `type: test` +# Mark a failure as a system failure (purple) rather than a test failure (red) by default. To +# override this for tests, mark the function as type: test. command_type: system -# Protect ourself against rogue test case, or curl gone wild, that runs forever -# 12 minutes is the longest we'll ever run -exec_timeout_secs: 3600 # 12 minutes is the longest we'll ever run +# If any of the pre tasks fails, the task will be marked as a failure. +pre_error_fails_task: true + +include: + - filename: .evergreen/suite-tasks.yml + +# Functions to run before all tasks (except those in task groups). +pre: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + - func: "install rust" + - func: "install junit dependencies" + +# Functions to run after all tasks (except those in task groups). +post: + - func: "stop csfle servers" + - func: "stop load balancer" + - func: "stop mongo orchestration" + - func: "tear down aws" + - func: "upload test results" + - func: "upload-mo-artifacts" + - func: "cleanup" -# What to do when evergreen hits the timeout (`post:` tasks are run automatically) +# Cause a timeout if a task does not complete within 150 minutes. TODO RUST-1721: reduce this. +exec_timeout_secs: 9000 +# What to do if the timeout is hit. Post-task functions will still run. timeout: - command: shell.exec params: script: | ls -la -functions: - "fetch source": - # Executes git clone and applies the submitted patch, if any - - command: git.get_project - params: - directory: "src" - # Applies the subitted patch, if any - # Deprecated. Should be removed. But still needed for certain agents (ZAP) - - command: git.apply_patch - # Make an evergreen exapanstion file with dynamic values - - command: shell.exec - params: - working_dir: "src" - script: | - # Get the current unique version of this checkout - if [ "${is_patch}" = "true" ]; then - CURRENT_VERSION=$(git describe)-patch-${version_id} - else - CURRENT_VERSION=latest - fi - - export DRIVERS_TOOLS="$(pwd)/../drivers-tools" - - # Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory - if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin - export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) - fi - - export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration" - export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin" - export UPLOAD_BUCKET="${project}" - export PROJECT_DIRECTORY="$(pwd)" - LIBMONGOCRYPT_SUFFIX_DIR="lib" - # The RHEL path is 'lib64', not 'lib' - if [ "${LIBMONGOCRYPT_OS}" = "rhel-80-64-bit" ]; then - LIBMONGOCRYPT_SUFFIX_DIR="lib64" - fi - export MONGOCRYPT_LIB_DIR="$PROJECT_DIRECTORY/libmongocrypt/${LIBMONGOCRYPT_OS}/$LIBMONGOCRYPT_SUFFIX_DIR" - export LD_LIBRARY_PATH="$MONGOCRYPT_LIB_DIR:$LD_LIBRARY_PATH" - - cat < expansion.yml - CURRENT_VERSION: "$CURRENT_VERSION" - DRIVERS_TOOLS: "$DRIVERS_TOOLS" - MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME" - MONGODB_BINARIES: "$MONGODB_BINARIES" - UPLOAD_BUCKET: "$UPLOAD_BUCKET" - PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" - PREPARE_SHELL: | - set -o errexit - set -o xtrace - export DRIVERS_TOOLS="$DRIVERS_TOOLS" - export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME" - export MONGODB_BINARIES="$MONGODB_BINARIES" - export UPLOAD_BUCKET="$UPLOAD_BUCKET" - export PROJECT_DIRECTORY="$PROJECT_DIRECTORY" - export DRIVERS_TOOLS_X509="$DRIVERS_TOOLS/.evergreen/x509gen" - export MONGOCRYPT_LIB_DIR="$MONGOCRYPT_LIB_DIR" - export LD_LIBRARY_PATH="$LD_LIBRARY_PATH" - - export TMPDIR="$MONGO_ORCHESTRATION_HOME/db" - export PATH="$MONGODB_BINARIES:$PATH" - export PROJECT="${project}" - - export AUTH=${AUTH} - export SSL=${SSL} - export TOPOLOGY=${TOPOLOGY} - export MONGODB_VERSION=${MONGODB_VERSION} - - export AZURE_IMDS_MOCK_PORT=44175 - - export SESSION_TEST_REQUIRE_MONGOCRYPTD=1 - - if [ "Windows_NT" != "$OS" ]; then - ulimit -n 64000 - fi - EOT - # See what we've done - cat expansion.yml - - # Load the expansion file to make an evergreen variable with the current unique version - - command: expansions.update - params: - file: src/expansion.yml - - "add aws auth variables to file": - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - cat < ${DRIVERS_TOOLS}/.evergreen/auth_aws/aws_e2e_setup.json - { - "iam_auth_ecs_account" : "${iam_auth_ecs_account}", - "iam_auth_ecs_secret_access_key" : "${iam_auth_ecs_secret_access_key}", - "iam_auth_ecs_account_arn": "arn:aws:iam::557821124784:user/authtest_fargate_user", - "iam_auth_ecs_cluster": "${iam_auth_ecs_cluster}", - "iam_auth_ecs_task_definition": "${iam_auth_ecs_task_definition}", - "iam_auth_ecs_subnet_a": "${iam_auth_ecs_subnet_a}", - "iam_auth_ecs_subnet_b": "${iam_auth_ecs_subnet_b}", - "iam_auth_ecs_security_group": "${iam_auth_ecs_security_group}", - "iam_auth_assume_aws_account" : "${iam_auth_assume_aws_account}", - "iam_auth_assume_aws_secret_access_key" : "${iam_auth_assume_aws_secret_access_key}", - "iam_auth_assume_role_name" : "${iam_auth_assume_role_name}", - "iam_auth_ec2_instance_account" : "${iam_auth_ec2_instance_account}", - "iam_auth_ec2_instance_secret_access_key" : "${iam_auth_ec2_instance_secret_access_key}", - "iam_auth_ec2_instance_profile" : "${iam_auth_ec2_instance_profile}", - "iam_auth_assume_web_role_name": "${iam_auth_assume_web_role_name}", - "iam_web_identity_issuer": "${iam_web_identity_issuer}", - "iam_web_identity_jwks_uri": "${iam_web_identity_jwks_uri}", - "iam_web_identity_token_file": "${iam_web_identity_token_file}", - "iam_web_identity_rsa_key": "${iam_web_identity_rsa_key}" - } - EOF - - "run aws auth test with regular aws credentials": - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - # The aws_e2e_assume_role script requires python3 with boto3. - pip install boto3 - cd ${DRIVERS_TOOLS}/.evergreen/auth_aws - mongo aws_e2e_regular_aws.js - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; sys.stdout.write(ul.quote_plus(sys.argv[1]))"' - USER=$(urlencode ${iam_auth_ecs_account}) - PASS=$(urlencode ${iam_auth_ecs_secret_access_key}) - MONGODB_URI="mongodb://$USER:$PASS@localhost" - EOF - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-aws-tests.sh +################## +# Build Variants # +################## +buildvariants: + - name: compile + display_name: "Compile Only" + run_on: + - rhel87-small + tasks: + - name: compile-on-latest + - name: compile-on-msrv - "run aws auth test with assume role credentials": - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - cd ${DRIVERS_TOOLS}/.evergreen/auth_aws - # The aws_e2e_assume_role script requires python3 with boto3. - pip install boto3 - cd ${DRIVERS_TOOLS}/.evergreen/auth_aws - mongo aws_e2e_assume_role.js - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias urlencode='python -c "import sys, urllib as ul; sys.stdout.write(ul.quote_plus(sys.argv[1]))"' - alias jsonkey='python -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" < ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json' - USER=$(jsonkey AccessKeyId) - USER=$(urlencode $USER) - PASS=$(jsonkey SecretAccessKey) - PASS=$(urlencode $PASS) - SESSION_TOKEN=$(jsonkey SessionToken) - SESSION_TOKEN=$(urlencode $SESSION_TOKEN) - MONGODB_URI="mongodb://$USER:$PASS@localhost" - EOF - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-aws-tests.sh + - name: lint + display_name: "Lint" + run_on: + - rhel87-small + tasks: + - name: .lint - "run aws auth test with aws EC2 credentials": - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - cd ${DRIVERS_TOOLS}/.evergreen/auth_aws - mongo aws_e2e_ec2.js - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - # Write an empty prepare_mongodb_aws so no auth environment variables - # are set. - echo "" > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-aws-tests.sh + - name: cargo-deny + display_name: "Cargo Deny" + run_on: + - rhel87-small + tasks: + - name: check-cargo-deny + + - name: rhel-8 + display_name: "RHEL 8" + run_on: + - rhel87-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - name: .standalone + - name: .replicaset + - name: .sharded + + - name: ubuntu-22.04-arm64 + display_name: "Ubuntu 22.04 (ARM64)" + run_on: + - ubuntu2204-arm64-small + expansions: + AUTH: auth + SSL: ssl + tasks: + # Ubuntu 22.04 does not support MongoDB versions below 6.0. + - name: .standalone !.4.0 !.4.2 !.4.4 !.5.0 + - name: .replicaset !.4.0 !.4.2 !.4.4 !.5.0 + - name: .sharded !.4.0 !.4.2 !.4.4 !.5.0 + + - name: macos-14.00 + display_name: "MacOS 14.00" + run_on: + - macos-14 + expansions: + AUTH: auth + SSL: ssl + tasks: + - name: .standalone + - name: .replicaset + - name: .sharded + + - name: windows-64-vs2017 + display_name: "Windows (VS 2017)" + run_on: + - windows-64-vs2017-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - name: .standalone + - name: .replicaset + - name: .sharded + + - name: openssl-rhel + display_name: "OpenSSL (RHEL)" + run_on: + - rhel87-small + expansions: + OPENSSL: true + AUTH: auth + SSL: ssl + tasks: + - name: .rapid .replicaset + + - name: openssl-ubuntu + display_name: "OpenSSL (Ubuntu)" + patchable: false + run_on: + - ubuntu2204-arm64-small + expansions: + OPENSSL: true + AUTH: auth + SSL: ssl + tasks: + - name: .rapid .replicaset + + - name: openssl-macos + display_name: "OpenSSL (MacOS)" + patchable: false + run_on: + - macos-14 + expansions: + OPENSSL: true + AUTH: auth + SSL: ssl + tasks: + - name: .rapid .replicaset + + - name: openssl-windows + display_name: "OpenSSL (Windows)" + patchable: false + run_on: + - windows-64-vs2017-small + expansions: + OPENSSL: true + AUTH: auth + SSL: ssl + tasks: + - name: .rapid .replicaset - "run aws auth test with aws credentials as environment variables": - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - export AWS_ACCESS_KEY_ID=${iam_auth_ecs_account} - export AWS_SECRET_ACCESS_KEY=${iam_auth_ecs_secret_access_key} - EOF - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - ASYNC_RUNTIME=${ASYNC_RUNTIME} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} .evergreen/run-aws-tests.sh + - name: no-auth-tls + display_name: "No Auth/TLS" + run_on: + - rhel87-small + tasks: + - .rapid .replicaset + + - name: compression + display_name: "Compression" + patchable: false + run_on: + - rhel87-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - .compression + + - name: stable-api + display_name: "Stable API V1" + patchable: false + run_on: + - rhel87-small + expansions: + REQUIRE_API_VERSION: true + MONGODB_API_VERSION: 1 + AUTH: auth + # Configuring SSL: ssl causes errors in bootstrap mongo-orchestration. + tasks: + # The Stable API was introduced in MongoDB version 5.0. Drivers Evergreen Tools only supports + # setting REQUIRE_API_VERSION on standalones and sharded clusters. + - .standalone !.4.0 !.4.2 !.4.4 + - .sharded !.4.0 !.4.2 !.4.4 + + - name: sync-api + display_name: "Sync API" + run_on: + - rhel87-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - .sync - "run aws auth test with aws credentials and session token as environment variables": - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - alias jsonkey='python -c "import json,sys;sys.stdout.write(json.load(sys.stdin)[sys.argv[1]])" < ${DRIVERS_TOOLS}/.evergreen/auth_aws/creds.json' - export AWS_ACCESS_KEY_ID=$(jsonkey AccessKeyId) - export AWS_SECRET_ACCESS_KEY=$(jsonkey SecretAccessKey) - export AWS_SESSION_TOKEN=$(jsonkey SessionToken) - EOF - - command: shell.exec - type: test - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-aws-tests.sh + - name: atlas-connectivity + display_name: "Atlas Connectivity" + patchable: false + run_on: + - rhel87-small + tasks: + - test-atlas-connectivity + + - name: aws-auth + display_name: "AWS Authentication" + patchable: false + run_on: + - ubuntu2004-small + expansions: + ORCHESTRATION_FILE: auth-aws.json + tasks: + - .aws-auth + + - name: azure-kms + display_name: "Azure KMS" + patchable: false + run_on: + # The Azure CLI is not available on RHEL/Ubuntu machines. + - debian11-small + expansions: + LIBMONGOCRYPT_OS: "debian11" + tasks: + - name: azure-kms-task-group + # Limit the test to only schedule every 14 days to reduce external resource usage. + batchtime: 20160 + + - name: gcp-kms + display_name: "GCP KMS" + patchable: false + run_on: + # The GCP CLI is not available on RHEL/Ubuntu machines. + - debian11-small + expansions: + LIBMONGOCRYPT_OS: "debian11" + tasks: + - name: gcp-kms-task-group + # Limit the test to only schedule every 14 days to reduce external resource usage. + batchtime: 20160 + + - name: gssapi-auth + display_name: "GSSAPI Authentication" + patchable: true + run_on: + - ubuntu2004-small + tasks: + - test-gssapi-auth + + - name: x509-auth + display_name: "x509 Authentication" + patchable: false + run_on: + - rhel87-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - name: test-x509-auth + + - name: oidc-linux + display_name: "OIDC Linux" + patchable: true + run_on: + - ubuntu2204-large + expansions: + AUTH: auth + SSL: ssl + tasks: + - test-oidc-task-group + - test-azure-oidc-task-group + - test-gcp-oidc-task-group + - test-k8s-oidc-task-group + + - name: oidc-macos + display_name: "OIDC MacOS" + patchable: true + run_on: + - macos-14 + expansions: + AUTH: auth + SSL: ssl + tasks: + - test-oidc-task-group + + - name: oidc-windows + disable: true + display_name: "OIDC Windows" + patchable: true + run_on: + - windows-64-vsMulti-small + expansions: + AUTH: auth + SSL: ssl + tasks: + - test-oidc-task-group + - test-azure-oidc-task-group + - test-gcp-oidc-task-group + + - name: in-use-encryption + display_name: "In-Use Encryption" + run_on: + - rhel80-small + expansions: + LIBMONGOCRYPT_OS: rhel-80-64-bit + AUTH: auth + SSL: ssl + tasks: + - .in-use-encryption + + - name: in-use-encryption-disable-crypt-shared + display_name: "In-Use Encryption (disable crypt_shared)" + patchable: false + run_on: + - rhel80-small + expansions: + LIBMONGOCRYPT_OS: rhel-80-64-bit + AUTH: auth + SSL: ssl + DISABLE_CRYPT_SHARED: true + tasks: + - .in-use-encryption + + - name: in-use-encryption-openssl + display_name: "In-Use Encryption (OpenSSL)" + run_on: + - rhel80-small + expansions: + LIBMONGOCRYPT_OS: rhel-80-64-bit + AUTH: auth + SSL: ssl + OPENSSL: true + tasks: + - test-in-use-encryption-openssl - "run aws ECS auth test": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - AUTH_AWS_DIR=${DRIVERS_TOOLS}/.evergreen/auth_aws - ECS_SRC_DIR=$AUTH_AWS_DIR/src + - name: load-balancer + display_name: "Load Balancer" + run_on: + - rhel87-small + tasks: + - .load-balancer - mkdir -p $ECS_SRC_DIR/.evergreen + - name: search-index + display_name: "Search Index Helpers" + patchable: false + run_on: + - rhel87-small + tasks: + - name: search-index-task-group - # fix issue with `TestData` in SERVER-46340 - sed -i '1s+^+TestData = {};\n+' $AUTH_AWS_DIR/lib/ecs_hosted_test.js + - name: aws-lambda + display_name: "AWS Lambda" + patchable: false + run_on: + - ubuntu2204-small + tasks: + - name: test-aws-lambda-task-group - # compile mini test project - cd $PROJECT_DIRECTORY/.evergreen/aws-ecs-test - . ${PROJECT_DIRECTORY}/.evergreen/configure-rust.sh - cargo build - cd - + - name: happy-eyeballs-macos + display_name: "Happy Eyeballs (MacOS)" + run_on: + - macos-14 + tasks: + - happy-eyeballs-task-group + + #- name: graviton-legacy + # display_name: "Graviton (legacy versions)" + # run_on: + # - amazon2-arm64-latest-large-m8g + # tasks: + # - name: .6.0 + # - name: .5.0 + # - name: .4.4 + # - name: .4.4 + # - name: .4.2 + + - name: graviton + display_name: "Graviton" + run_on: + - amazon2023-arm64-latest-large-m8g + tasks: + - name: .latest + #- name: .8.0 + #- name: .7.0 - # copy mini test binary - cp $PROJECT_DIRECTORY/.evergreen/run-mongodb-aws-ecs-test.sh $ECS_SRC_DIR/.evergreen - cp $PROJECT_DIRECTORY/.evergreen/aws-ecs-test/target/debug/aws-ecs-test $ECS_SRC_DIR +############### +# Task Groups # +############### +task_groups: + - name: azure-kms-task-group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + - func: "install rust" + - func: "install libmongocrypt" + - command: shell.exec + params: + shell: bash + script: |- + ${PREPARE_SHELL} + set +o xtrace + echo '${testazurekms_publickey}' > /tmp/testazurekms_publickey + echo '${testazurekms_privatekey}' > /tmp/testazurekms_privatekey + # Set 600 permissions on private key file. Otherwise ssh / scp may error with permissions "are too open". + chmod 600 /tmp/testazurekms_privatekey + export AZUREKMS_CLIENTID=${testazurekms_clientid} + export AZUREKMS_TENANTID=${testazurekms_tenantid} + export AZUREKMS_SECRET=${testazurekms_secret} + export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_PUBLICKEYPATH=/tmp/testazurekms_publickey + export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey + export AZUREKMS_SCOPE=${testazurekms_scope} + export AZUREKMS_VMNAME_PREFIX=rustdriver + set -o xtrace + $DRIVERS_TOOLS/.evergreen/csfle/azurekms/create-and-setup-vm.sh + - command: expansions.update + params: + file: testazurekms-expansions.yml + - command: shell.exec + params: + shell: bash + script: |- + cat < azure_shell.yml + AZURE_SHELL: | + export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} + export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} + export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey + export AZUREKMS_KEY_NAME='${testazurekms_keyname}' + export AZUREKMS_KEY_VAULT_ENDPOINT='${testazurekms_keyvaultendpoint}' + EOT + - command: expansions.update + params: + file: azure_shell.yml + teardown_group: + - command: expansions.update + params: + file: testazurekms-expansions.yml + - command: shell.exec + params: + shell: bash + script: |- + ${PREPARE_SHELL} + ${AZURE_SHELL} + set -o errexit + $DRIVERS_TOOLS/.evergreen/csfle/azurekms/delete-vm.sh + tasks: + - test-azure-kms - cd $AUTH_AWS_DIR - cat < setup.js - const mongo_binaries = "$MONGODB_BINARIES"; - const project_dir = "$ECS_SRC_DIR"; - EOF + - name: gcp-kms-task-group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + - func: "install rust" + - func: "install libmongocrypt" + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + set +o xtrace + echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json + export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json + export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS + export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" + set -o xtrace + $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/create-and-setup-instance.sh + - command: expansions.update + params: + file: testgcpkms-expansions.yml + teardown_group: + - command: shell.exec + params: + shell: "bash" + script: | + ${PREPARE_SHELL} + set +o xtrace + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + set -o xtrace + $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/delete-instance.sh + tasks: + - test-gcp-kms - cat setup.js - mongo --nodb setup.js aws_e2e_ecs.js - - "run aws assume role with web identity test": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - cd ${DRIVERS_TOOLS}/.evergreen/auth_aws - . ./activate-authawsvenv.sh - mongo aws_e2e_web_identity.js - - command: shell.exec - type: test - params: - working_dir: "src" - silent: true - script: | - # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - cat <<'EOF' > "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" - export AWS_ROLE_ARN="${iam_auth_assume_web_role_name}" - export AWS_WEB_IDENTITY_TOKEN_FILE="${iam_web_identity_token_file}" - EOF - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - # the test should be run with and without a session name set - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \ - ASSERT_NO_URI_CREDS=true \ - AWS_ROLE_SESSION_NAME="test" \ - .evergreen/run-aws-tests.sh - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \ - ASSERT_NO_URI_CREDS=true \ - .evergreen/run-aws-tests.sh + - name: search-index-task-group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 # 30 minutes + setup_group: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + - func: "install rust" + - func: "install junit dependencies" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: subprocess.exec + params: + working_dir: src + binary: bash + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - DRIVERS_TOOLS + - LAMBDA_STACK_NAME + - MONGODB_VERSION + env: + MONGODB_VERSION: "7.0" + args: + - .evergreen/with-secrets.sh + - drivers/atlas-dev + - ${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: subprocess.exec + params: + working_dir: src + binary: bash + include_expansions_in_env: + - CLUSTER_NAME + args: + - .evergreen/with-secrets.sh + - drivers/atlas-dev + - ${DRIVERS_TOOLS}/.evergreen/atlas/teardown-atlas-cluster.sh + - func: "upload test results" + tasks: + - test-search-index + - name: test-aws-lambda-task-group + setup_group: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "make files executable" + - func: "install rust" + - func: "install cargo-lambda" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: subprocess.exec + params: + working_dir: src + binary: bash + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - DRIVERS_TOOLS + - LAMBDA_STACK_NAME + - MONGODB_VERSION + args: + - .evergreen/with-secrets.sh + - drivers/atlas-dev + - ${DRIVERS_TOOLS}/.evergreen/atlas/setup-atlas-cluster.sh + - command: expansions.update + params: + file: src/atlas-expansion.yml + teardown_group: + - command: subprocess.exec + params: + working_dir: src + binary: bash + include_expansions_in_env: + - CLUSTER_NAME + args: + - .evergreen/with-secrets.sh + - drivers/atlas-dev + - ${DRIVERS_TOOLS}/.evergreen/atlas/teardown-atlas-cluster.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - test-aws-lambda-deployed - "run x509 tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} + - name: test-oidc-task-group + setup_group: + - func: fetch source + - func: create expansions + - func: prepare resources + - func: fix absolute paths + - func: init test-results + - func: make files executable + - func: assume ec2 role + - func: install rust + - func: install junit dependencies + - command: shell.exec + params: + shell: bash + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + script: | + ${PREPARE_SHELL} + ${DRIVERS_TOOLS}/.evergreen/auth_oidc/setup.sh + teardown_task: + - func: "upload test results" + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-latest - export MONGODB_URI="${MONGODB_URI}" - export SSL="${SSL}" - . .evergreen/generate-uri.sh + - name: test-azure-oidc-task-group + setup_group: + - func: fetch source + - func: create expansions + - func: prepare resources + - func: fix absolute paths + - func: init test-results + - func: make files executable + - func: install rust + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + duration_seconds: 3600 + - command: subprocess.exec + params: + binary: bash + env: + AZUREOIDC_VMNAME_PREFIX: "RUST_DRIVER" + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/create-and-setup-vm.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/delete-vm.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-azure-latest - export CERT_PATH=$DRIVERS_TOOLS/.evergreen/x509gen/client.pem + - name: test-gcp-oidc-task-group + setup_group: + - func: fetch source + - func: create expansions + - func: prepare resources + - func: fix absolute paths + - func: init test-results + - func: make files executable + - func: install rust + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + duration_seconds: 3600 + - command: subprocess.exec + params: + binary: bash + env: + GCPOIDC_VMNAME_PREFIX: "RUST_DRIVER" + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/setup.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-gcp-latest - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - TLS_FEATURE=${TLS_FEATURE} \ - .evergreen/run-x509-tests.sh + - name: test-k8s-oidc-task-group + setup_group: + - func: fetch source + - func: create expansions + - func: prepare resources + - func: fix absolute paths + - func: init test-results + - func: make files executable + - func: install rust + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + duration_seconds: 3600 + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + teardown_task: + - command: subprocess.exec + params: + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown.sh + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + tasks: + - oidc-auth-test-k8s-latest - "run plain tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} + - name: happy-eyeballs-task-group + setup_group_can_fail_task: true + setup_group_timeout_secs: 1800 + setup_group: + - func: "fetch source" + - func: "create expansions" + - func: "prepare resources" + - func: "windows fix" + - func: "fix absolute paths" + - func: "init test-results" + - func: "make files executable" + - func: "install rust" + - func: "install junit dependencies" + - func: "start happy eyeballs server" + tasks: + - test-happy-eyeballs + teardown_task: + - func: "stop happy eyeballs server" + - func: "upload test results" - .evergreen/run-plain-tests.sh +######### +# Tasks # +######### +tasks: + - name: compile-on-latest + commands: + - func: "compile only" - "run connection string tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} + - name: compile-on-msrv + commands: + - func: "compile only" + vars: + # Our minimum supported Rust version. This should be updated whenever the MSRV is bumped. + RUST_VERSION: 1.82.0 - .evergreen/run-connection-string-tests.sh + - name: check-cargo-deny + commands: + - func: "check cargo deny" - "prepare resources": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - rm -rf $DRIVERS_TOOLS - git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS - echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config + - name: check-rustfmt + tags: [lint] + commands: + - func: "check rustfmt" - "install rust": - command: shell.exec - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/install-dependencies.sh rust - - "install libmongocrypt": - command: shell.exec - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/install-dependencies.sh libmongocrypt - - "install junit dependencies": - command: shell.exec - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/install-dependencies.sh junit-dependencies + - name: check-clippy + tags: [lint] + commands: + - func: "check clippy" - "bootstrap mongo-orchestration": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - ORCHESTRATION_FILE=${ORCHESTRATION_FILE} \ - MONGODB_VERSION=${MONGODB_VERSION} \ - TOPOLOGY=${TOPOLOGY} \ - AUTH=${AUTH} \ - SSL=${SSL} \ - REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \ - LOAD_BALANCER=${LOAD_BALANCER} \ - sh ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh - # run-orchestration generates expansion file with the MONGODB_URI for the cluster - - command: expansions.update - params: - file: mo-expansion.yml + - name: check-semgrep + tags: [lint] + commands: + - func: "check semgrep" - "run tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - export MONGODB_URI="${MONGODB_URI}" - export SSL="${SSL}" - export SINGLE_MONGOS_LB_URI="${SINGLE_MONGOS_LB_URI}" - export MULTI_MONGOS_LB_URI="${MULTI_MONGOS_LB_URI}" - . .evergreen/generate-uri.sh - - SNAPPY_COMPRESSION_ENABLED=${SNAPPY_COMPRESSION_ENABLED} \ - ZLIB_COMPRESSION_ENABLED=${ZLIB_COMPRESSION_ENABLED} \ - ZSTD_COMPRESSION_ENABLED=${ZSTD_COMPRESSION_ENABLED} \ - SINGLE_THREAD=${SINGLE_THREAD} \ - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - MONGODB_API_VERSION=${MONGODB_API_VERSION} \ - TLS_FEATURE=${TLS_FEATURE} \ - .evergreen/run-tests.sh - - "run serverless tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - set +o xtrace - # Exported without xtrace to avoid leaking credentials - export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" - export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - - export SSL="${SSL}" - export SINGLE_MONGOS_LB_URI="${SERVERLESS_URI}" - . .evergreen/generate-uri.sh - - SNAPPY_COMPRESSION_ENABLED="true" \ - ZLIB_COMPRESSION_ENABLED="true" \ - ZSTD_COMPRESSION_ENABLED="true" \ - SINGLE_THREAD=${SINGLE_THREAD} \ - ASYNC_RUNTIME=${ASYNC_RUNTIME} \ - MONGODB_API_VERSION=${MONGODB_API_VERSION} \ - TLS_FEATURE=${TLS_FEATURE} \ - .evergreen/run-serverless-tests.sh + - name: check-rustdoc + tags: [lint] + commands: + - func: "check rustdoc" - "run atlas tests": - - command: shell.exec - type: test - params: - silent: true - shell: bash - working_dir: "src" - script: | - # DO NOT ECHO WITH XTRACE (which PREPARE_SHELL does) - export MONGO_ATLAS_TESTS=1 - export MONGO_ATLAS_FREE_TIER_REPL_URI='${MONGO_ATLAS_FREE_TIER_REPL_URI}' - export MONGO_ATLAS_FREE_TIER_REPL_URI_SRV='${MONGO_ATLAS_FREE_TIER_REPL_URI_SRV}' - export MONGO_ATLAS_SERVERLESS_URI='${MONGO_ATLAS_SERVERLESS_URI}' - export MONGO_ATLAS_SERVERLESS_URI_SRV='${MONGO_ATLAS_SERVERLESS_URI_SRV}' - export MONGO_ATLAS_REPL_URI='${MONGO_ATLAS_REPL_URI}' - export MONGO_ATLAS_REPL_URI_SRV='${MONGO_ATLAS_REPL_URI_SRV}' - export MONGO_ATLAS_SHARDED_URI='${MONGO_ATLAS_SHARDED_URI}' - export MONGO_ATLAS_SHARDED_URI_SRV='${MONGO_ATLAS_SHARDED_URI_SRV}' - export MONGO_ATLAS_TLS11_URI='${MONGO_ATLAS_TLS11_URI}' - export MONGO_ATLAS_TLS11_URI_SRV='${MONGO_ATLAS_TLS11_URI_SRV}' - export MONGO_ATLAS_TLS12_URI='${MONGO_ATLAS_TLS12_URI}' - export MONGO_ATLAS_TLS12_URI_SRV='${MONGO_ATLAS_TLS12_URI_SRV}' - export PROJECT_DIRECTORY='${PROJECT_DIRECTORY}' - ASYNC_RUNTIME=${ASYNC_RUNTIME} .evergreen/run-atlas-tests.sh - - "run kmip server": - - command: shell.exec - params: - shell: bash - working_dir: "src" - background: true - script: | - ${PREPARE_SHELL} - export TLS_FEATURE=${TLS_FEATURE} - .evergreen/run-csfle-kmip-servers.sh + # Driver test suite runs for the full set of versions and topologies are in + # suite-tasks.yml, generated by .evergreen/generate_tasks. - "run mock azure imds server": - - command: shell.exec - params: - shell: bash - working_dir: "src" - background: true - script: | - ${PREPARE_SHELL} - .evergreen/run-csfle-mock-azure-imds.sh + - name: test-zstd-compression + tags: [compression] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "run driver test suite" + vars: + ZSTD: true - "build csfle expansions": - - command: shell.exec - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - . ${DRIVERS_TOOLS}/.evergreen/find-python3.sh - PYTHON=$(find_python3) - set +o xtrace - cat < csfle-expansions.yml - PREPARE_CSFLE: | - export ASYNC_RUNTIME=${ASYNC_RUNTIME} - export CSFLE_TLS_CERT_DIR=$DRIVERS_TOOLS_X509 - export DISABLE_CRYPT_SHARED=${DISABLE_CRYPT_SHARED} - export TLS_FEATURE=${TLS_FEATURE} - export CRYPT_SHARED_LIB_PATH=${CRYPT_SHARED_LIB_PATH} - export PYTHON=$PYTHON - $PYTHON -m pip install boto3 - # Exported without xtrace to avoid leaking credentials - set +o xtrace - export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - export AZURE_TENANT_ID=${AZURE_TENANT_ID} - export AZURE_CLIENT_ID=${AZURE_CLIENT_ID} - export AZURE_CLIENT_SECRET=${AZURE_CLIENT_SECRET} - export GCP_EMAIL=${GCP_EMAIL} - export GCP_PRIVATE_KEY=${GCP_PRIVATE_KEY} - export CSFLE_LOCAL_KEY=${CSFLE_LOCAL_KEY} - set -o xtrace - EOT - - command: expansions.update - params: - file: src/csfle-expansions.yml + - name: test-zlib-compression + tags: [compression] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "run driver test suite" + vars: + ZLIB: true - "run csfle tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ${PREPARE_CSFLE} - - .evergreen/run-csfle-tests.sh + - name: test-snappy-compression + tags: [compression] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "run driver test suite" + vars: + SNAPPY: true - "run csfle serverless tests": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - ${PREPARE_CSFLE} + - name: test-aws-auth-regular-credentials + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: "auth-aws.json" + MONGODB_VERSION: "latest" + TOPOLOGY: "server" + - func: "add aws auth variables to file" + - func: "run aws auth test with regular aws credentials" - # Exported without xtrace to avoid leaking credentials - set +o xtrace - export SERVERLESS_ATLAS_USER="${SERVERLESS_ATLAS_USER}" - export SERVERLESS_ATLAS_PASSWORD="${SERVERLESS_ATLAS_PASSWORD}" - set -o xtrace + - name: test-aws-auth-assume-role-credentials + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws auth test with assume role credentials" - export SINGLE_MONGOS_LB_URI="${SERVERLESS_URI}" - . .evergreen/generate-uri.sh - export SERVERLESS=serverless + - name: test-aws-auth-environment-variables + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: "auth-aws.json" + MONGODB_VERSION: "latest" + TOPOLOGY: "server" + - func: "add aws auth variables to file" + - func: "run aws auth test with aws credentials as environment variables" - .evergreen/run-csfle-tests.sh + - name: test-aws-auth-environment-variables-session-token + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws auth test with aws credentials and session token as environment variables" - "build and upload gcp kms test": - - command: shell.exec - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} + - name: test-aws-auth-ec2 + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws auth test with aws EC2 credentials" - set +o xtrace - export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} - export GCPKMS_PROJECT=${GCPKMS_PROJECT} - export GCPKMS_ZONE=${GCPKMS_ZONE} - export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} - set -o xtrace + - name: test-aws-auth-ecs + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws ecs auth test" - mkdir test-contents - cp -r $MONGOCRYPT_LIB_DIR test-contents + - name: test-aws-auth-assume-role-with-web-identity + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws assume role with web identity test" - echo "Building test ... begin" - . ${PROJECT_DIRECTORY}/.evergreen/configure-rust.sh - cargo test get_exe_name --features in-use-encryption-unstable,gcp-kms -- --ignored - cp $(cat exe_name.txt) test-contents/test-exe - echo "Building test ... end" - - echo "Copying test contents ... begin" - tar czf test-contents.tgz test-contents - GCPKMS_SRC=test-contents.tgz GCPKMS_DST=$GCPKMS_INSTANCENAME: $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/copy-file.sh - echo "Copying test contents ... end" - - echo "Untarring test contents ... begin" - GCPKMS_CMD="tar xf test-contents.tgz" $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/run-command.sh - echo "Untarring test contents ... end" - - "run gcp kms test": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - - set +o xtrace - export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} - export GCPKMS_PROJECT=${GCPKMS_PROJECT} - export GCPKMS_ZONE=${GCPKMS_ZONE} - export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} - set -o xtrace - - export GCPKMS_CMD="ON_DEMAND_GCP_CREDS_SHOULD_SUCCEED=1 \ - RUST_BACKTRACE=1 LD_LIBRARY_PATH=./test-contents/lib \ - ./test-contents/test-exe on_demand_gcp_credentials --nocapture" - $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/run-command.sh - - - "compile only": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - RUST_VERSION=${RUST_VERSION} MSRV=${MSRV} .evergreen/compile-only.sh - - "check cargo deny": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/check-cargo-deny.sh - - "check rustfmt": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/check-rustfmt.sh - - "check clippy": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/check-clippy.sh - - "check rustdoc": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/check-rustdoc.sh - - "check manual": - - command: shell.exec - type: test - params: - shell: bash - working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/install-dependencies.sh mdbook - manual/test.sh - - "upload-mo-artifacts": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz - - command: s3.put - params: - aws_key: ${aws_key} - aws_secret: ${aws_secret} - local_file: mongodb-logs.tar.gz - remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz - bucket: mciuploads - permissions: public-read - content_type: ${content_type|application/x-gzip} - display_name: "mongodb-logs.tar.gz" - - "stop mongo orchestration": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - - cd "$MONGO_ORCHESTRATION_HOME" - # source the mongo-orchestration virtualenv if it exists - if [ -f venv/bin/activate ]; then - . venv/bin/activate - elif [ -f venv/Scripts/activate ]; then - . venv/Scripts/activate - fi - mongo-orchestration stop - - - "cleanup": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - rm -rf $DRIVERS_TOOLS || true - - "fix absolute paths": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - set +o xtrace - for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do - perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename - done - - "windows fix": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - set +o xtrace - for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do - cat $i | tr -d '\r' > $i.new - mv $i.new $i - done - - # Copy client certificate because symlinks do not work on Windows. - if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin - cp ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem - fi - - "make files executable": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - set +o xtrace - for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do - chmod +x $i - done - - "init test-results": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - echo '{"results": [{ "status": "FAIL", "test_file": "Build", "log_raw": "No test-results.json found was created" } ]}' > ${PROJECT_DIRECTORY}/test-results.json - - "start load balancer": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - export MONGODB_URI="${MONGODB_URI}" - ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start - - command: expansions.update - params: - file: lb-expansion.yml + - name: test-aws-auth-assume-role-with-web-identity-session-name + tags: [aws-auth] + commands: + - func: "bootstrap mongo-orchestration" + vars: + ORCHESTRATION_FILE: auth-aws.json + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "add aws auth variables to file" + - func: "run aws assume role with web identity test" + vars: + AWS_ROLE_SESSION_NAME: test - "stop load balancer": - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop + - name: test-gssapi-auth + commands: + - func: "run gssapi auth test" - "upload test results": - - command: attach.xunit_results - params: - file: src/results.xml + - name: test-atlas-connectivity + commands: + - func: "run atlas tests" -pre_error_fails_task: true -pre: - - func: "fetch source" - - func: "prepare resources" - - func: "windows fix" - - func: "fix absolute paths" - - func: "init test-results" - - func: "make files executable" - - func: "install rust" - - func: "install libmongocrypt" + - name: test-gcp-kms + commands: + - func: "build and upload gcp kms test" + - func: "run gcp kms test" -post: - - func: "stop load balancer" - - func: "stop mongo orchestration" - - func: "upload test results" - - func: "upload-mo-artifacts" - - func: "cleanup" + - name: test-azure-kms + commands: + - command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + ${AZURE_SHELL} + .evergreen/run-azure-kms-test.sh -tasks: - - name: "test-3.6-standalone" - tags: ["3.6", "standalone"] + - name: test-search-index + commands: + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + include_expansions_in_env: + - PROJECT_DIRECTORY + - MONGODB_URI + - PATH + args: + - .evergreen/run-search-index-test.sh + + - name: test-x509-auth commands: - - func: "install junit dependencies" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "3.6" - TOPOLOGY: "server" - - func: "run tests" + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "run x509 tests" - - name: "test-3.6-replica_set" - tags: ["3.6", "replica_set"] + - name: test-tokio-sync + tags: [sync] commands: - - func: "install junit dependencies" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "3.6" - TOPOLOGY: "replica_set" - - func: "run tests" + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "run sync tests" - - name: "test-3.6-sharded_cluster" - tags: ["3.6", "sharded_cluster"] + - name: test-in-use-encryption-4.2 + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "3.6" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + MONGODB_VERSION: 4.2 + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.0-standalone" - tags: ["4.0", "standalone"] + - name: test-in-use-encryption-4.4 + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.0" - TOPOLOGY: "server" - - func: "run tests" + MONGODB_VERSION: 4.4 + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.0-replica_set" - tags: ["4.0", "replica_set"] + - name: test-in-use-encryption-5.0 + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.0" - TOPOLOGY: "replica_set" - - func: "run tests" + MONGODB_VERSION: 5.0 + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.0-sharded_cluster" - tags: ["4.0", "sharded_cluster"] + - name: test-in-use-encryption-6.0 + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.0" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + MONGODB_VERSION: 6.0 + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.2-standalone" - tags: ["4.2", "standalone"] + - name: test-in-use-encryption-7.0 + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.2" - TOPOLOGY: "server" - - func: "run tests" + MONGODB_VERSION: 7.0 + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.2-replica_set" - tags: ["4.2", "replica_set"] + - name: test-in-use-encryption-rapid + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.2" - TOPOLOGY: "replica_set" - - func: "run tests" + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.2-sharded_cluster" - tags: ["4.2", "sharded_cluster"] + - name: test-in-use-encryption-latest + tags: [in-use-encryption] commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.2" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + MONGODB_VERSION: latest + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.4-standalone" - tags: ["4.4", "standalone"] + - name: test-in-use-encryption-openssl commands: - - func: "install junit dependencies" + - func: "install libmongocrypt" - func: "bootstrap mongo-orchestration" vars: - MONGODB_VERSION: "4.4" - TOPOLOGY: "server" - - func: "run tests" + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "start csfle servers" + - func: "run csfle tests" - - name: "test-4.4-replica_set" - tags: ["4.4", "replica_set"] + - name: test-load-balancer-5.0 + tags: [load-balancer] commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" + - func: bootstrap mongo-orchestration vars: - MONGODB_VERSION: "4.4" - TOPOLOGY: "replica_set" - - func: "run tests" - - - name: "test-4.4-sharded_cluster" - tags: ["4.4", "sharded_cluster"] + MONGODB_VERSION: 5.0 + TOPOLOGY: sharded_cluster + LOAD_BALANCER: true + - func: start load balancer + - func: run driver test suite + + - name: test-load-balancer-6.0 + tags: [load-balancer] commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" + - func: bootstrap mongo-orchestration vars: - MONGODB_VERSION: "4.4" - TOPOLOGY: "sharded_cluster" - - func: "run tests" - - - name: "test-4.4-aws-auth" - # "4.4" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] + MONGODB_VERSION: 6.0 + TOPOLOGY: sharded_cluster + LOAD_BALANCER: true + - func: start load balancer + - func: run driver test suite + + - name: test-load-balancer-7.0 + tags: [load-balancer] commands: - - func: "bootstrap mongo-orchestration" + - func: bootstrap mongo-orchestration vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "4.4" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" - - - name: "test-5.0-standalone" - tags: ["5.0", "standalone"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "5.0" - TOPOLOGY: "server" - - func: "run tests" - - - name: "test-5.0-replica_set" - tags: ["5.0", "replica_set"] + MONGODB_VERSION: 7.0 + TOPOLOGY: sharded_cluster + LOAD_BALANCER: true + - func: start load balancer + - func: run driver test suite + + - name: test-load-balancer-rapid + tags: [load-balancer] commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "5.0" - TOPOLOGY: "replica_set" - - func: "run tests" - - - name: "test-5.0-sharded_cluster" - tags: ["5.0", "sharded_cluster"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "5.0" - TOPOLOGY: "sharded_cluster" - - func: "run tests" - - - name: "test-5.0-load_balancer" - tags: ["5.0", "load_balancer"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" + - func: bootstrap mongo-orchestration vars: - MONGODB_VERSION: "5.0" - TOPOLOGY: "sharded_cluster" - LOAD_BALANCER: "true" - - func: "start load balancer" - - func: "run tests" - - - name: "test-5.0-aws-auth" - # "5.0" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "5.0" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" - - - name: "test-6.0-standalone" - tags: ["6.0", "standalone"] + MONGODB_VERSION: rapid + TOPOLOGY: sharded_cluster + LOAD_BALANCER: true + - func: start load balancer + - func: run driver test suite + + - name: test-load-balancer-latest + tags: [load-balancer] commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" + - func: bootstrap mongo-orchestration vars: - MONGODB_VERSION: "6.0" - TOPOLOGY: "server" - - func: "run tests" + MONGODB_VERSION: latest + TOPOLOGY: sharded_cluster + LOAD_BALANCER: true + - func: start load balancer + - func: run driver test suite - - name: "test-6.0-replica_set" - tags: ["6.0", "replica_set"] + - name: test-aws-lambda-deployed commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "6.0" - TOPOLOGY: "replica_set" - - func: "run tests" - - - name: "test-6.0-sharded_cluster" - tags: ["6.0", "sharded_cluster"] + - command: ec2.assume_role + params: + role_arn: ${LAMBDA_AWS_ROLE_ARN} + duration_seconds: 3600 + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - .evergreen/run-aws-lambda-test.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - DRIVERS_ATLAS_PUBLIC_API_KEY + - DRIVERS_ATLAS_PRIVATE_API_KEY + - DRIVERS_ATLAS_LAMBDA_USER + - DRIVERS_ATLAS_LAMBDA_PASSWORD + - DRIVERS_ATLAS_GROUP_ID + - LAMBDA_STACK_NAME + - PROJECT_DIRECTORY + - DRIVERS_TOOLS + - CLUSTER_NAME + - MONGODB_URI + env: + TEST_LAMBDA_DIRECTORY: ${PROJECT_DIRECTORY}/.evergreen/aws-lambda-test + AWS_REGION: us-east-1 + SAM_BUILD_ARGS: --beta-features --debug + + - name: "oidc-auth-test-latest" commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "6.0" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + - func: "run oidc auth test with test credentials" - - name: "test-6.0-load_balancer" - tags: ["6.0", "load_balancer"] + - name: "oidc-auth-test-azure-latest" commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "6.0" - TOPOLOGY: "sharded_cluster" - LOAD_BALANCER: "true" - - func: "start load balancer" - - func: "run tests" - - - name: "test-6.0-aws-auth" - # "6.0" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] + - func: "build static test tarball" + vars: + BUILD_FEATURES: "--features azure-oidc" + TEST_FILES: "${PROJECT_DIRECTORY}/src/test/spec/json/auth/unified" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/azure/run-driver-test.sh + env: + AZUREOIDC_DRIVERS_TAR_FILE: ${STATIC_TEST_TARBALL} + AZUREOIDC_TEST_CMD: "source ./env.sh && RUST_BACKTRACE=1 ./${STATIC_TEST_BINARY} test::spec::oidc_skip_ci::azure -- --no-capture" + + - name: "oidc-auth-test-gcp-latest" commands: - - func: "bootstrap mongo-orchestration" - vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "6.0" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" - - - name: "test-7.0-standalone" - tags: ["7.0", "standalone"] + - func: "build static test tarball" + vars: + BUILD_FEATURES: "--features gcp-oidc" + TEST_FILES: "${PROJECT_DIRECTORY}/src/test/spec/json/auth/unified" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/gcp/run-driver-test.sh + env: + GCPOIDC_DRIVERS_TAR_FILE: ${STATIC_TEST_TARBALL} + GCPOIDC_TEST_CMD: "source ./secrets-export.sh && RUST_BACKTRACE=1 ./${STATIC_TEST_BINARY} test::spec::oidc_skip_ci::gcp -- --no-capture" + + - name: "oidc-auth-test-k8s-latest" commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "7.0" - TOPOLOGY: "server" - - func: "run tests" - - - name: "test-7.0-replica_set" - tags: ["7.0", "replica_set"] + - func: "build static test tarball" + vars: + TEST_FILES: "${PROJECT_DIRECTORY}/src/test/spec/json/auth/unified" + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + duration_seconds: 1800 + - func: "run oidc k8s test" + vars: + VARIANT: eks + - func: "run oidc k8s test" + vars: + VARIANT: gke + - func: "run oidc k8s test" + vars: + VARIANT: aks + + - name: "test-happy-eyeballs" commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "7.0" - TOPOLOGY: "replica_set" - - func: "run tests" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - .evergreen/run-happy-eyeballs-tests.sh + include_expansions_in_env: + - PROJECT_DIRECTORY + +############# +# Functions # +############# +functions: + "fetch source": + # Executes git clone and applies the submitted patch, if any. + - command: git.get_project + params: + directory: "src" - - name: "test-7.0-sharded_cluster" - tags: ["7.0", "sharded_cluster"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "7.0" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + "create expansions": + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - .evergreen/create-expansions.sh + include_expansions_in_env: + - is_patch + - version_id + - project + - LIBMONGOCRYPT_OS + - command: expansions.update + params: + file: src/expansion.yml - - name: "test-7.0-load_balancer" - tags: ["7.0", "load_balancer"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "7.0" - TOPOLOGY: "sharded_cluster" - LOAD_BALANCER: "true" - - func: "start load balancer" - - func: "run tests" - - - name: "test-7.0-aws-auth" - # "7.0" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "7.0" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" + "add aws auth variables to file": + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: subprocess.exec + params: + binary: bash + working_dir: ${DRIVERS_TOOLS}/.evergreen/auth_aws + args: + - ./setup_secrets.sh + - drivers/aws_auth + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN - - name: "test-rapid-standalone" - tags: ["rapid", "standalone"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "server" - - func: "run tests" + "run aws auth test with regular aws credentials": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: regular + SKIP_CREDENTIAL_CACHING_TESTS: "true" - - name: "test-rapid-replica_set" - tags: ["rapid", "replica_set"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "replica_set" - - func: "run tests" + "run aws auth test with assume role credentials": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: assume-role + SKIP_CREDENTIAL_CACHING_TESTS: "true" - - name: "test-rapid-sharded_cluster" - tags: ["rapid", "sharded_cluster"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + "run aws auth test with aws EC2 credentials": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: ec2 - - name: "test-rapid-load_balancer" - tags: ["rapid", "load_balancer"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "rapid" - TOPOLOGY: "sharded_cluster" - LOAD_BALANCER: "true" - - func: "start load balancer" - - func: "run tests" - - - name: "test-rapid-aws-auth" - # "rapid" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "rapid" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" + "run aws auth test with aws credentials as environment variables": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: env-creds + SKIP_CREDENTIAL_CACHING_TESTS: "true" - - name: "test-latest-standalone" - tags: ["latest", "standalone"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "latest" - TOPOLOGY: "server" - - func: "run tests" + "run aws auth test with aws credentials and session token as environment variables": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: session-creds + SKIP_CREDENTIAL_CACHING_TESTS: "true" + + "run aws ecs auth test": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} - - name: "test-latest-replica_set" - tags: ["latest", "replica_set"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "latest" - TOPOLOGY: "replica_set" - - func: "run tests" + # The "upload test results" post-task function expects this file to be present. + touch results.xml - - name: "test-latest-sharded_cluster" - tags: ["latest", "sharded_cluster"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "latest" - TOPOLOGY: "sharded_cluster" - - func: "run tests" + AUTH_AWS_DIR=${DRIVERS_TOOLS}/.evergreen/auth_aws + ECS_SRC_DIR=$AUTH_AWS_DIR/src - - name: "test-latest-load_balancer" - tags: ["latest", "load_balancer"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "latest" - TOPOLOGY: "sharded_cluster" - LOAD_BALANCER: "true" - - func: "start load balancer" - - func: "run tests" + mkdir -p $ECS_SRC_DIR/.evergreen + # compile mini test project + cd $PROJECT_DIRECTORY/.evergreen/aws-ecs-test + . ${PROJECT_DIRECTORY}/.evergreen/env.sh + cargo build + cd - - - name: "test-latest-aws-auth" - # "latest" explicitly left off to keep this out of the generic matrix - tags: ["aws-auth"] - commands: - - func: "bootstrap mongo-orchestration" - vars: - ORCHESTRATION_FILE: "auth-aws.json" - MONGODB_VERSION: "latest" - AUTH: "auth" - TOPOLOGY: "server" - - func: "add aws auth variables to file" - - func: "run aws auth test with regular aws credentials" - - func: "run aws auth test with assume role credentials" - - func: "run aws auth test with aws credentials as environment variables" - - func: "run aws auth test with aws credentials and session token as environment variables" - - func: "run aws auth test with aws EC2 credentials" - - func: "run aws ECS auth test" - - func: "run aws assume role with web identity test" + # copy mini test binary + cp $PROJECT_DIRECTORY/.evergreen/run-mongodb-aws-ecs-test.sh $ECS_SRC_DIR/.evergreen + cp $PROJECT_DIRECTORY/.evergreen/aws-ecs-test/target/debug/aws-ecs-test $ECS_SRC_DIR - - name: "test-connection-string" - commands: - - func: "run connection string tests" + export PROJECT_DIRECTORY="$ECS_SRC_DIR" + $AUTH_AWS_DIR/aws_setup.sh ecs - - name: "test-serverless" - tags: ["serverless"] - commands: - - func: "install junit dependencies" - - func: "run serverless tests" + "run aws assume role with web identity test": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-aws-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + env: + AWS_AUTH_TYPE: web-identity + + "run gssapi auth test": + - command: subprocess.exec + type: test + params: + binary: bash + working_dir: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-gssapi-tests.sh + include_expansions_in_env: + - PROJECT_DIRECTORY - - name: "test-csfle" - tags: ["csfle"] - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - - func: "run kmip server" - - func: "run mock azure imds server" - - func: "build csfle expansions" - - func: "run csfle tests" + "run x509 tests": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} - - name: "test-csfle-serverless" - tags: ["csfle", "serverless"] - commands: - - command: expansions.update - params: - file: src/expansion.yml - - command: expansions.update - params: - file: serverless-expansion.yml - - func: "install junit dependencies" - - func: "install libmongocrypt" - - func: "run kmip server" - - func: "run mock azure imds server" - - func: "build csfle expansions" - - func: "run csfle serverless tests" + export MONGODB_URI="${MONGODB_URI}" + export CERT_PATH=$DRIVERS_TOOLS/.evergreen/x509gen/client.pem - - name: "test-azure-kms" - commands: - - command: shell.exec - params: - working_dir: "src" - script: | - ${PREPARE_SHELL} - ${AZURE_SHELL} - .evergreen/run-azure-kms-test.sh + .evergreen/run-x509-tests.sh - - name: "test-atlas-connectivity" - tags: ["atlas-connect"] - commands: - - func: "run atlas tests" + "prepare resources": + - command: subprocess.exec + params: + working_dir: src + include_expansions_in_env: + - DRIVERS_TOOLS + binary: bash + args: + - .evergreen/fetch-drivers-tools.sh + - command: subprocess.exec + params: + working_dir: src + include_expansions_in_env: + - DRIVERS_TOOLS + binary: bash + args: + - .evergreen/find-python3.sh + - command: expansions.update + params: + file: src/python3.yml + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config - - name: "test-x509-auth" - commands: - - func: "install junit dependencies" - - func: "bootstrap mongo-orchestration" - vars: - MONGODB_VERSION: "4.4" - TOPOLOGY: "server" - - func: "run x509 tests" + "install rust": + command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + .evergreen/install-dependencies.sh rust - - name: "test-plain-auth" - commands: - - func: "run plain tests" + "install libmongocrypt": + command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + .evergreen/install-dependencies.sh libmongocrypt - - name: "test-gcp-kms" - commands: - - func: "build and upload gcp kms test" - - func: "run gcp kms test" + "install junit dependencies": + command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + .evergreen/install-dependencies.sh junit-dependencies - - name: "compile-only" - commands: - - func: "compile only" + "install cargo-lambda": + command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + .evergreen/install-dependencies.sh cargo-lambda - - name: "check-cargo-deny" - commands: - - func: "check cargo deny" + "bootstrap mongo-orchestration": + - command: subprocess.exec + params: + binary: sh + args: + - ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh + include_expansions_in_env: + - AUTH + - SSL + - TOPOLOGY + - LOAD_BALANCER + - REQUIRE_API_VERSION + - MONGODB_VERSION + - ORCHESTRATION_FILE + - MONGODB_BINARIES + - command: expansions.update + params: + file: mo-expansion.yml + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - .evergreen/generate-uri.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - MONGODB_URI + - SINGLE_MONGOS_LB_URI + - MULTI_MONGOS_LB_URI + - SSL + - command: expansions.update + params: + file: src/uri-expansions.yml - - name: "check-rustfmt" - commands: - - func: "check rustfmt" + "run driver test suite": + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - .evergreen/run-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + - OPENSSL + - SINGLE_MONGOS_LB_URI + - MULTI_MONGOS_LB_URI + - MONGODB_URI + - MONGODB_API_VERSION + - PATH + - ZSTD + - ZLIB + - SNAPPY + + "run sync tests": + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - .evergreen/run-sync-tests.sh + include_expansions_in_env: + - PROJECT_DIRECTORY + - MONGODB_URI - - name: "check-clippy" - commands: - - func: "check clippy" + "run atlas tests": + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} - - name: "check-rustdoc" - commands: - - func: "check rustdoc" - - - name: "check-manual" - commands: - - func: "check manual" - - - -axes: - - id: "extra-rust-versions" - values: - - id: "min" - display_name: "1.57 (minimum supported version)" - variables: - RUST_VERSION: "1.57.0" - MSRV: true - - id: "nightly" - display_name: "nightly" - variables: - RUST_VERSION: "nightly" - - id: "mongodb-version" - display_name: MongoDB Version - values: - - id: "latest" - display_name: "latest" - variables: - MONGODB_VERSION: "latest" - - id: "rapid" - display_name: "rapid" - variables: - MONGODB_VERSION: "rapid" - - id: "7.0" - display_name: "7.0" - variables: - MONGODB_VERSION: "7.0" - - id: "6.0" - display_name: "6.0" - variables: - MONGODB_VERSION: "6.0" - - id: "5.0" - display_name: "5.0" - variables: - MONGODB_VERSION: "5.0" - - id: "4.4" - display_name: "4.4" - variables: - MONGODB_VERSION: "4.4" - - id: "4.2" - display_name: "4.2" - variables: - MONGODB_VERSION: "4.2" - - id: "4.0" - display_name: "4.0" - variables: - MONGODB_VERSION: "4.0" - - id: "3.6" - display_name: "3.6" - variables: - MONGODB_VERSION: "3.6" - - - id: "topology" - display_name: Topology - values: - - id: "standalone" - display_name: Standalone - variables: - TOPOLOGY: "server" - - id: "replica-set" - display_name: Replica Set - variables: - TOPOLOGY: "replica_set" - - id: "sharded-cluster" - display_name: Sharded - variables: - TOPOLOGY: "sharded_cluster" - - - id: "async-runtime" - display_name: Async Runtime - values: - - id: "tokio" - display_name: "tokio" - variables: - ASYNC_RUNTIME: "tokio" - - id: "async-std" - display_name: "async-std" - variables: - ASYNC_RUNTIME: "async-std" - - # Note that drivers-evergreen-tools expects `SSL` as the environmental - # variable, not `TLS`, so we have to use that for the actual value used in the - # script; we use `TLS` for the metadata that isn't used by the actual shell - # scripts. - - id: "auth-and-tls" - display_name: Authentication and TLS - values: - - id: "auth-and-tls" - display_name: Auth TLS (Default) - variables: - AUTH: "auth" - SSL: "ssl" - TLS_FEATURE: "" - - id: "openssl-auth-and-tls" - display_name: Auth TLS (OpenSSL) - variables: - AUTH: "auth" - SSL: "ssl" - TLS_FEATURE: "openssl-tls" - - id: "noauth-and-notls" - display_name: NoAuth NoTLS - variables: - AUTH: "noauth" - SSL: "nossl" - TLS_FEATURE: "" - - - id: "tls-feature" - display_name: "TLS Feature" - values: - - id: "default" - display_name: "rustls TLS" - variables: - TLS_FEATURE: "" - - id: "openssl" - display_name: "OpenSSL TLS" - variables: - TLS_FEATURE: "openssl-tls" - - - id: "compressor" - display_name: "Compressor" - values: - - id: "no-compression" - display_name: No Compressor - - id: "zstd-compression" - display_name: Zstd - variables: - ZSTD_COMPRESSION_ENABLED: "true" - - id: "zlib-compression" - display_name: Zlib - variables: - ZLIB_COMPRESSION_ENABLED: "true" - - id: "snappy-compression" - display_name: Snappy - variables: - SNAPPY_COMPRESSION_ENABLED: "true" - - - id: "os" - display_name: OS - values: - - id: ubuntu-18.04 - display_name: "Ubuntu 18.04" - run_on: ubuntu1804-test - variables: - VENV_BIN_DIR: "bin" - LIBMONGOCRYPT_OS: "ubuntu1804-64" - - id: rhel-8.0 - display_name: "RHEL 8.0" - run_on: rhel80-test - variables: - VENV_BIN_DIR: "bin" - LIBMONGOCRYPT_OS: "rhel-80-64-bit" - - id: ubuntu-20.04 - display_name: "Ubuntu 20.04" - run_on: ubuntu2004-test - variables: - VENV_BIN_DIR: "bin" - LIBMONGOCRYPT_OS: "ubuntu2004-64" - - id: ubuntu-20.04-arm64 - display_name: "ARM64 Ubuntu 20.04" - run_on: ubuntu2004-arm64-test - variables: - VENV_BIN_DIR: "bin" - LIBMONGOCRYPT_OS: "ubuntu2004-arm64" - - id: macos-11.00 - display_name: "MacOS 11.00" - run_on: macos-1100 - variables: - SINGLE_THREAD: true - VENV_BIN_DIR: "bin" - LIBMONGOCRYPT_OS: "macos" - - id: windows-64-vs2017 - display_name: "Windows (VS 2017)" - run_on: windows-64-vs2017-test - variables: - VENV_BIN_DIR: "Scripts" - LIBMONGOCRYPT_OS: "windows-test" - - id: debian-11 - display_name: "Debian 11" - run_on: debian11-small - variables: - LIBMONGOCRYPT_OS: "debian11" - - - id: "versioned-api" - display_name: "Versioned API" - values: - - id: require-api-v1 - display_name: "Require API V1" - variables: - REQUIRE_API_VERSION: "1" - MONGODB_API_VERSION: "1" - - - id: "crypt-shared" - display_name: "crypt_shared" - values: - - id: enabled - display_name: "crypt_shared enabled" - variables: - DISABLE_CRYPT_SHARED: "" - - id: disabled - display_name: "crypt_shared disabled" - variables: - DISABLE_CRYPT_SHARED: "true" + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - .evergreen/run-atlas-tests.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + + "start csfle servers": + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + - command: subprocess.exec + params: + working_dir: src + binary: bash + include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"] + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/setup-secrets.sh + - command: subprocess.exec + params: + working_dir: src + binary: bash + background: true + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/start-servers.sh + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/await-servers.sh -task_groups: - - name: serverless_task_group - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 # 30 minutes - setup_group: - - func: "fetch source" - - func: "prepare resources" - - func: "windows fix" - - func: "fix absolute paths" - - func: "init test-results" - - func: "make files executable" - - func: "install rust" - - command: shell.exec - params: - shell: "bash" - script: | - ${PREPARE_SHELL} - set +o xtrace - SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ - SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ - SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ - LOADBALANCED=ON \ - bash ${DRIVERS_TOOLS}/.evergreen/serverless/create-instance.sh - - command: expansions.update - params: - file: serverless-expansion.yml - teardown_group: - - command: shell.exec - params: - script: | - ${PREPARE_SHELL} - set +o xtrace - SERVERLESS_DRIVERS_GROUP=${SERVERLESS_DRIVERS_GROUP} \ - SERVERLESS_API_PUBLIC_KEY=${SERVERLESS_API_PUBLIC_KEY} \ - SERVERLESS_API_PRIVATE_KEY=${SERVERLESS_API_PRIVATE_KEY} \ - SERVERLESS_INSTANCE_NAME=${SERVERLESS_INSTANCE_NAME} \ - bash ${DRIVERS_TOOLS}/.evergreen/serverless/delete-instance.sh - - func: "upload test results" - - func: "upload-mo-artifacts" - - func: "cleanup" + "stop csfle servers": + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/csfle/stop-servers.sh - tasks: - - ".serverless" - - - name: azurekms_task_group - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 # 30 minutes - setup_group: - - func: "fetch source" - - func: "prepare resources" - - func: "windows fix" - - func: "fix absolute paths" - - func: "init test-results" - - func: "make files executable" - - func: "install rust" - - func: "install libmongocrypt" - - command: shell.exec - params: - shell: bash - script: |- - ${PREPARE_SHELL} - set +o xtrace - echo '${testazurekms_publickey}' > /tmp/testazurekms_publickey - echo '${testazurekms_privatekey}' > /tmp/testazurekms_privatekey - # Set 600 permissions on private key file. Otherwise ssh / scp may error with permissions "are too open". - chmod 600 /tmp/testazurekms_privatekey - export AZUREKMS_CLIENTID=${testazurekms_clientid} - export AZUREKMS_TENANTID=${testazurekms_tenantid} - export AZUREKMS_SECRET=${testazurekms_secret} - export AZUREKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS - export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} - export AZUREKMS_PUBLICKEYPATH=/tmp/testazurekms_publickey - export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey - export AZUREKMS_SCOPE=${testazurekms_scope} - export AZUREKMS_VMNAME_PREFIX=rustdriver - set -o xtrace - $DRIVERS_TOOLS/.evergreen/csfle/azurekms/create-and-setup-vm.sh - - command: expansions.update - params: - file: testazurekms-expansions.yml - - command: shell.exec - params: - shell: bash - script: |- - cat < azure_shell.yml - AZURE_SHELL: | - export AZUREKMS_VMNAME=${AZUREKMS_VMNAME} - export AZUREKMS_RESOURCEGROUP=${testazurekms_resourcegroup} - export AZUREKMS_PRIVATEKEYPATH=/tmp/testazurekms_privatekey - EOT - - command: expansions.update - params: - file: azure_shell.yml - teardown_group: - - command: expansions.update - params: - file: testazurekms-expansions.yml - - command: shell.exec - params: - shell: bash - script: |- - ${PREPARE_SHELL} - ${AZURE_SHELL} - set -o errexit - $DRIVERS_TOOLS/.evergreen/csfle/azurekms/delete-vm.sh - tasks: - - test-azure-kms + "run csfle tests": + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - .evergreen/run-csfle-tests.sh + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + - MONGODB_URI + - MONGOCRYPT_LIB_DIR + - OPENSSL + - OS + - LD_LIBRARY_PATH + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - CSFLE_LOCAL_KEY + - CRYPT_SHARED_LIB_PATH + - DISABLE_CRYPT_SHARED + - AZURE_IMDS_MOCK_PORT - - name: testgcpkms_task_group - setup_group_can_fail_task: true - setup_group_timeout_secs: 1800 # 30 minutes - setup_group: - - func: "fetch source" - - func: "prepare resources" - - func: "windows fix" - - func: "fix absolute paths" - - func: "init test-results" - - func: "make files executable" - - func: "install rust" - - func: "install libmongocrypt" - - command: shell.exec - params: - shell: "bash" - script: | - ${PREPARE_SHELL} - set +o xtrace - echo '${testgcpkms_key_file}' > /tmp/testgcpkms_key_file.json - export GCPKMS_KEYFILE=/tmp/testgcpkms_key_file.json - export GCPKMS_DRIVERS_TOOLS=$DRIVERS_TOOLS - export GCPKMS_SERVICEACCOUNT="${testgcpkms_service_account}" - set -o xtrace - $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/create-and-setup-instance.sh - - command: expansions.update - params: - file: testgcpkms-expansions.yml - teardown_group: - - command: shell.exec - params: - shell: "bash" - script: | - ${PREPARE_SHELL} - set +o xtrace - export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} - export GCPKMS_PROJECT=${GCPKMS_PROJECT} - export GCPKMS_ZONE=${GCPKMS_ZONE} - export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} - set -o xtrace - $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/delete-instance.sh - tasks: - - test-gcp-kms + "build and upload gcp kms test": + - command: shell.exec + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} -buildvariants: -- - matrix_name: "tests" - matrix_spec: - os: - - rhel-8.0 - - ubuntu-20.04 - - ubuntu-20.04-arm64 - - macos-11.00 - - windows-64-vs2017 - auth-and-tls: "*" - async-runtime: "*" - compressor: "*" - exclude_spec: - - compressor: ["zlib-compression", "zstd-compression", "snappy-compression"] - auth-and-tls: "noauth-and-notls" - os: "*" - async-runtime: "*" - - compressor: ["zlib-compression", "zstd-compression", "snappy-compression"] - async-runtime: "async-std" - os: "*" - auth-and-tls: "*" - - display_name: "! ${os} ${auth-and-tls} with ${async-runtime} and ${compressor}" - tasks: - - ".latest" - - ".rapid" - - ".7.0" - - ".6.0" - - ".5.0" - - ".4.4" - - ".4.2" - - ".4.0" - - ".3.6" - rules: - # Some older versions of MongoDB do not have specific Linux builds, so we just use - # generic Linux builds on them. These do not link to OpenSSL, so we have to skip - # the SSL tests on them. - - if: - os: ["ubuntu-20.04", "ubuntu-20.04-arm64"] - auth-and-tls: "*" - async-runtime: "*" - compressor: "*" - then: - remove_tasks: [".3.6", ".4.0", ".4.2"] - # haproxy isn't installed on windows / ubuntu-arm / macos 11 - - if: - os: ["ubuntu-20.04-arm64", "windows-64-vs2017", "macos-11.00"] - auth-and-tls: "*" - async-runtime: "*" - compressor: "*" - then: - remove_tasks: ".load_balancer" -- matrix_name: "x509-auth" - matrix_spec: - os: - - ubuntu-20.04 - - ubuntu-20.04-arm64 - - macos-11.00 - - windows-64-vs2017 - auth-and-tls: - - auth-and-tls - - openssl-auth-and-tls - async-runtime: "*" - display_name: "${os} X.509 ${auth-and-tls} with ${async-runtime}" - tasks: - - "test-x509-auth" - -- matrix_name: "plain-auth" - matrix_spec: - os: - - ubuntu-20.04 - - ubuntu-20.04-arm64 - - macos-11.00 - - windows-64-vs2017 - async-runtime: "*" - display_name: "${os} PLAIN auth with ${async-runtime}" - tasks: - - "test-plain-auth" - -- matrix_name: "serverless" - matrix_spec: - os: - - ubuntu-20.04 - async-runtime: "*" - crypt-shared: - - "enabled" - tls-feature: - - "default" - - "openssl" - display_name: "Serverless ${os} with ${async-runtime} ${tls-feature}" - tasks: - - "serverless_task_group" - -- matrix_name: "csfle-mongodb-version" - matrix_spec: - os: - - ubuntu-20.04 - mongodb-version: - - "latest" - - "rapid" - - "7.0" - - "6.0" - - "5.0" - - "4.4" - topology: - - "standalone" - crypt-shared: - - "enabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -# There's no 4.2 build for ubuntu-20.04, so switch to rhel. -- matrix_name: "csfle-mongodb-version-old" - matrix_spec: - os: - - rhel-8.0 - mongodb-version: - - "4.2" - topology: - - "standalone" - crypt-shared: - - "enabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -- matrix_name: "csfle-topology" - matrix_spec: - os: - - ubuntu-20.04 - mongodb-version: - - "latest" - - "rapid" - - "7.0" - - "6.0" - topology: - - "replica-set" - crypt-shared: - - "enabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -- matrix_name: "csfle-os" - matrix_spec: - os: - - macos-11.00 - - windows-64-vs2017 - mongodb-version: - - "7.0" - topology: - - "standalone" - crypt-shared: - - "enabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -- matrix_name: "csfle-mongocryptd" - matrix_spec: - os: - - ubuntu-20.04 - mongodb-version: - - "latest" - - "rapid" - - "7.0" - - "6.0" - - "5.0" - - "4.4" - topology: - - "standalone" - crypt-shared: - - "disabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -# There's no 4.2 build for ubuntu-20.04, so switch to rhel. -- matrix_name: "csfle-mongocryptd-old" - matrix_spec: - os: - - rhel-8.0 - mongodb-version: - - "4.2" - topology: - - "standalone" - crypt-shared: - - "disabled" - tls-feature: - - "default" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -- matrix_name: "csfle-tls" - matrix_spec: - os: - - ubuntu-20.04 - - macos-11.00 - - windows-64-vs2017 - mongodb-version: - - "7.0" - topology: - - "standalone" - crypt-shared: - - "enabled" - tls-feature: - - "openssl" - display_name: "CSFLE (${crypt-shared}, ${tls-feature}) on mongodb ${mongodb-version} ${topology} / ${os}" - tasks: - - "test-csfle" - -- matrix_name: "atlas-connect" - matrix_spec: - os: - - ubuntu-20.04 - - ubuntu-20.04-arm64 - - macos-11.00 - - windows-64-vs2017 - async-runtime: "*" - display_name: "Atlas Connectivity ${os} with ${async-runtime}" - tasks: - - ".atlas-connect" - -- matrix_name: "aws-auth" - matrix_spec: - os: - - ubuntu-18.04 - async-runtime: "tokio" - display_name: "${os} AWS Auth with ${async-runtime}" - tasks: - - ".aws-auth" - - "test-connection-string" - -- matrix_name: "compile-only" - matrix_spec: - os: - - ubuntu-20.04 - extra-rust-versions: "*" - display_name: "! Compile on Rust ${extra-rust-versions}" - tasks: - - "compile-only" - -- matrix_name: "versioned-api-tests" - matrix_spec: - os: - - ubuntu-20.04 - async-runtime: "tokio" - versioned-api: "*" - display_name: "Versioned API ${versioned-api}" - tasks: - - ".latest .standalone" - - ".rapid .standalone" - - ".7.0 .standalone" - - ".6.0 .standalone" - - ".5.0 .standalone" - -- matrix_name: "azure-kms" - display_name: "Azure KMS" - matrix_spec: - os: - - ubuntu-20.04 - tasks: - - name: "azurekms_task_group" - batchtime: 20160 - -- matrix_name: "gcp-kms" - display_name: "GCP KMS" - matrix_spec: - os: - - debian-11 - tasks: - - name: testgcpkms_task_group - batchtime: 20160 - -- name: "lint" - display_name: "! Lint" - run_on: - - ubuntu1804-test - tasks: - - name: "check-clippy" - - name: "check-rustfmt" - - name: "check-rustdoc" - - name: "check-manual" - - name: "check-cargo-deny" \ No newline at end of file + set +o xtrace + export GCPKMS_GCLOUD=${GCPKMS_GCLOUD} + export GCPKMS_PROJECT=${GCPKMS_PROJECT} + export GCPKMS_ZONE=${GCPKMS_ZONE} + export GCPKMS_INSTANCENAME=${GCPKMS_INSTANCENAME} + set -o xtrace + + mkdir test-contents + cp -r $MONGOCRYPT_LIB_DIR test-contents + + echo "Building test ... begin" + . ${PROJECT_DIRECTORY}/.evergreen/env.sh + cargo test get_exe_name --features in-use-encryption,gcp-kms + cp $(cat exe_name.txt) test-contents/test-exe + echo "Building test ... end" + + echo "Copying test contents ... begin" + tar czf test-contents.tgz test-contents + GCPKMS_SRC=test-contents.tgz GCPKMS_DST=$GCPKMS_INSTANCENAME: $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/copy-file.sh + echo "Copying test contents ... end" + + echo "Untarring test contents ... begin" + GCPKMS_CMD="tar xf test-contents.tgz" $DRIVERS_TOOLS/.evergreen/csfle/gcpkms/run-command.sh + echo "Untarring test contents ... end" + + "run gcp kms test": + - command: subprocess.exec + type: test + params: + working_dir: ${DRIVERS_TOOLS} + binary: bash + include_expansions_in_env: + - GCPKMS_GCLOUD + - GCPKMS_PROJECT + - GCPKMS_ZONE + - GCPKMS_INSTANCENAME + - DRIVERS_TOOLS + env: + GCPKMS_CMD: "RUST_BACKTRACE=1 LD_LIBRARY_PATH=./test-contents/lib ./test-contents/test-exe on_demand_gcp::success -- --no-capture" + args: + - .evergreen/csfle/gcpkms/run-command.sh + + "assume ec2 role": + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + + "run oidc auth test with test credentials": + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + args: + - .evergreen/run-mongodb-oidc-test.sh + + "start happy eyeballs server": + - command: subprocess.exec + params: + working_dir: src + background: true + binary: ${PYTHON3} + args: + - ${DRIVERS_TOOLS}/.evergreen/happy_eyeballs/server.py + - command: subprocess.exec + params: + working_dir: src + binary: ${PYTHON3} + args: + - ${DRIVERS_TOOLS}/.evergreen/happy_eyeballs/server.py + - --wait + + "stop happy eyeballs server": + - command: subprocess.exec + params: + working_dir: src + binary: ${PYTHON3} + args: + - ${DRIVERS_TOOLS}/.evergreen/happy_eyeballs/server.py + - --stop + + "compile only": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + RUST_VERSION=${RUST_VERSION} .evergreen/compile-only.sh + + "check cargo deny": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + .evergreen/check-cargo-deny.sh + + "check rustfmt": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + .evergreen/check-rustfmt.sh + + "check clippy": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + .evergreen/check-clippy.sh + + "check semgrep": + - command: subprocess.exec + type: test + params: + working_dir: src + include_expansions_in_env: + - DRIVERS_TOOLS + - PROJECT_DIRECTORY + - MONGOCRYPT_LIB_DIR + binary: bash + args: + - .evergreen/check-semgrep.sh + + "check rustdoc": + - command: shell.exec + type: test + params: + working_dir: src + shell: bash + script: | + ${PREPARE_SHELL} + .evergreen/check-rustdoc.sh + + "upload-mo-artifacts": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + find $MONGO_ORCHESTRATION_HOME -name \*.log | xargs tar czf mongodb-logs.tar.gz + - command: s3.put + params: + aws_key: ${aws_key} + aws_secret: ${aws_secret} + local_file: mongodb-logs.tar.gz + remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz + bucket: mciuploads + permissions: public-read + content_type: ${content_type|application/x-gzip} + display_name: "mongodb-logs.tar.gz" + + "stop mongo orchestration": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + + cd ${MONGO_ORCHESTRATION_HOME} + # source the mongo-orchestration virtualenv if it exists + if [ -f venv/bin/activate ]; then + . venv/bin/activate + elif [ -f venv/Scripts/activate ]; then + . venv/Scripts/activate + fi + mongo-orchestration stop + + "cleanup": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + rm -rf $DRIVERS_TOOLS || true + + "fix absolute paths": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + set +o xtrace + for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do + perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename + done + + "windows fix": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + set +o xtrace + for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do + cat $i | tr -d '\r' > $i.new + mv $i.new $i + done + + # Copy client certificate because symlinks do not work on Windows. + if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin + cp ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem + fi + + "make files executable": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + set +o xtrace + for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do + chmod +x $i + done + + "init test-results": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + echo '{"results": [{ "status": "FAIL", "test_file": "Build", "log_raw": "No test-results.json found was created" } ]}' > ${PROJECT_DIRECTORY}/test-results.json + + "start load balancer": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + export MONGODB_URI="${MONGODB_URI}" + ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh start + - command: expansions.update + params: + file: lb-expansion.yml + + "stop load balancer": + - command: shell.exec + params: + script: | + ${PREPARE_SHELL} + ${DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh stop + + "tear down aws": + - command: shell.exec + params: + shell: bash + script: | + ${PREPARE_SHELL} + cd "${DRIVERS_TOOLS}/.evergreen/auth_aws" + if [ -f "./aws_e2e_setup.json" ]; then + . ./activate-authawsvenv.sh + python ./lib/aws_assign_instance_profile.py + fi + + "upload test results": + - command: attach.xunit_results + params: + file: src/results.xml + + "build static test tarball": + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - .evergreen/build-static-test-tarball.sh + include_expansions_in_env: + - PROJECT_DIRECTORY + - BUILD_FEATURES + - TEST_FILES + - command: expansions.update + params: + file: src/static-test-tarball-expansion.yml + + "run oidc k8s test": + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/setup-pod.sh + env: + K8S_VARIANT: ${VARIANT} + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - DRIVERS_TOOLS + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/run-driver-test.sh + env: + K8S_DRIVERS_TAR_FILE: ${STATIC_TEST_TARBALL} + K8S_TEST_CMD: "RUST_BACKTRACE=1 ./${STATIC_TEST_BINARY} test::spec::oidc_skip_ci::k8s -- --no-capture" + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN + - command: subprocess.exec + params: + working_dir: src + binary: bash + args: + - ${DRIVERS_TOOLS}/.evergreen/auth_oidc/k8s/teardown-pod.sh + include_expansions_in_env: + - AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY + - AWS_SESSION_TOKEN diff --git a/.evergreen/configure-rust.sh b/.evergreen/configure-rust.sh deleted file mode 100755 index 7f1d25b05..000000000 --- a/.evergreen/configure-rust.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -export RUSTUP_HOME="${PROJECT_DIRECTORY}/.rustup" -export PATH="${RUSTUP_HOME}/bin:$PATH" -export CARGO_HOME="${PROJECT_DIRECTORY}/.cargo" -export PATH="${CARGO_HOME}/bin:$PATH" - -if [[ "Windows_NT" == "$OS" ]]; then - # Update path for DLLs - export PATH="${MONGOCRYPT_LIB_DIR}/../bin:$PATH" - - # rustup/cargo need the native Windows paths; $PROJECT_DIRECTORY is a cygwin path - export RUSTUP_HOME=$(cygpath ${RUSTUP_HOME} --windows) - export CARGO_HOME=$(cygpath ${CARGO_HOME} --windows) - export MONGOCRYPT_LIB_DIR=$(cygpath ${MONGOCRYPT_LIB_DIR} --windows) -fi - -. ${CARGO_HOME}/env diff --git a/.evergreen/create-expansions.sh b/.evergreen/create-expansions.sh new file mode 100644 index 000000000..3e382428d --- /dev/null +++ b/.evergreen/create-expansions.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +# Get the current unique version of this checkout. +if [[ "$is_patch" = true ]]; then + CURRENT_VERSION=$(git describe)-patch-${version_id} +else + CURRENT_VERSION=latest +fi + +DRIVERS_TOOLS="$(pwd)/../drivers-tools" + +# Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home +# directory. +if [[ "${OS}" = "Windows_NT" ]]; then + export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS) +fi + +MONGO_ORCHESTRATION_HOME="${DRIVERS_TOOLS}/.evergreen/orchestration" +MONGODB_BINARIES="${DRIVERS_TOOLS}/mongodb/bin" +UPLOAD_BUCKET="${project}" +PROJECT_DIRECTORY="$(pwd)" +LIBMONGOCRYPT_SUFFIX_DIR="lib" +# The RHEL path is 'lib64', not 'lib' +if [ "${LIBMONGOCRYPT_OS}" = "rhel-80-64-bit" ]; then + LIBMONGOCRYPT_SUFFIX_DIR="lib64" +fi +MONGOCRYPT_LIB_DIR="${PROJECT_DIRECTORY}/libmongocrypt/${LIBMONGOCRYPT_OS}/${LIBMONGOCRYPT_SUFFIX_DIR}" +LD_LIBRARY_PATH="${MONGOCRYPT_LIB_DIR}:${LD_LIBRARY_PATH}" + +TMPDIR="${MONGO_ORCHESTRATION_HOME}/db" +PATH="${MONGODB_BINARIES}:${PATH}" +PROJECT="${project}" + +cat <expansion.yml +CURRENT_VERSION: "${CURRENT_VERSION}" +DRIVERS_TOOLS: "${DRIVERS_TOOLS}" +MONGO_ORCHESTRATION_HOME: "${MONGO_ORCHESTRATION_HOME}" +MONGODB_BINARIES: "${MONGODB_BINARIES}" +UPLOAD_BUCKET: "${UPLOAD_BUCKET}" +PROJECT_DIRECTORY: "${PROJECT_DIRECTORY}" +MONGOCRYPT_LIB_DIR: "${MONGOCRYPT_LIB_DIR}" +LD_LIBRARY_PATH: "${LD_LIBRARY_PATH}" +TMPDIR: "${TMPDIR}" +PATH: "${PATH}" +PROJECT: "${PROJECT}" +AZURE_IMDS_MOCK_PORT: 8080 +PREPARE_SHELL: | + set -o errexit + set -o xtrace + + export DRIVERS_TOOLS="${DRIVERS_TOOLS}" + export MONGO_ORCHESTRATION_HOME="${MONGO_ORCHESTRATION_HOME}" + export MONGODB_BINARIES="${MONGODB_BINARIES}" + export UPLOAD_BUCKET="${UPLOAD_BUCKET}" + export PROJECT_DIRECTORY="${PROJECT_DIRECTORY}" + export MONGOCRYPT_LIB_DIR="${MONGOCRYPT_LIB_DIR}" + export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" + + export TMPDIR="${TMPDIR}" + export PATH="${PATH}" + export PROJECT="${PROJECT}" + + if [[ "$OS" != "Windows_NT" ]]; then + ulimit -n 64000 + fi +EOT + +# Print the expansion file created. +cat expansion.yml diff --git a/.evergreen/create-ssdlc-compliance-report.sh b/.evergreen/create-ssdlc-compliance-report.sh new file mode 100755 index 000000000..24425f172 --- /dev/null +++ b/.evergreen/create-ssdlc-compliance-report.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +REPORT_FILE=".evergreen/${CRATE_VERSION}-ssdlc-compliance-report.md" +SED_REPLACE="s/RELEASE_VERSION/${CRATE_VERSION}/g" + +sed ${SED_REPLACE} .evergreen/ssdlc-compliance-report-template.md > ${REPORT_FILE} diff --git a/.evergreen/env.sh b/.evergreen/env.sh index 37741b89e..453a704ef 100644 --- a/.evergreen/env.sh +++ b/.evergreen/env.sh @@ -1,26 +1,38 @@ #!/bin/bash -source ./.evergreen/configure-rust.sh - +export RUSTUP_HOME="${PROJECT_DIRECTORY}/.rustup" +export PATH="${RUSTUP_HOME}/bin:$PATH" +export CARGO_HOME="${PROJECT_DIRECTORY}/.cargo" +export PATH="${CARGO_HOME}/bin:$PATH" NODE_ARTIFACTS_PATH="${PROJECT_DIRECTORY}/node-artifacts" export NVM_DIR="${NODE_ARTIFACTS_PATH}/nvm" -if [[ "$OS" == "Windows_NT" ]]; then - NVM_HOME=$(cygpath -w "$NVM_DIR") - export NVM_HOME - NVM_SYMLINK=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") - export NVM_SYMLINK - NVM_ARTIFACTS_PATH=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") - export NVM_ARTIFACTS_PATH - export OPENSSL_DIR="C:\\openssl" - OPENSSL_LIB_PATH=$(cygpath $OPENSSL_DIR/lib) - ls $OPENSSL_LIB_PATH - PATH=$(cygpath $NVM_SYMLINK):$(cygpath $NVM_HOME):$PATH - export PATH - echo "updated path on windows PATH=$PATH" +. ${CARGO_HOME}/env + +if [[ "$OSTYPE" == "cygwin" ]]; then + # Update path for DLLs + export PATH="${MONGOCRYPT_LIB_DIR}/../bin:$PATH" + + # rustup/cargo need the native Windows paths; $PROJECT_DIRECTORY is a cygwin path + export RUSTUP_HOME=$(cygpath ${RUSTUP_HOME} --windows) + export CARGO_HOME=$(cygpath ${CARGO_HOME} --windows) + export MONGOCRYPT_LIB_DIR=$(cygpath ${MONGOCRYPT_LIB_DIR} --windows) + + NVM_HOME=$(cygpath -w "$NVM_DIR") + export NVM_HOME + NVM_SYMLINK=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") + export NVM_SYMLINK + NVM_ARTIFACTS_PATH=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") + export NVM_ARTIFACTS_PATH + PATH=$(cygpath $NVM_SYMLINK):$(cygpath $NVM_HOME):$PATH + export PATH + echo "updated path on windows PATH=$PATH" + + export OPENSSL_INCLUDE_DIR="C:\\Program Files\\OpenSSL-Win64\\include" + export OPENSSL_LIB_DIR="C:\\Program Files\\OpenSSL-Win64\\lib\\VC\\x64\\MD" else - # Turn off tracing for the very-spammy nvm script. - set +o xtrace - [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" - set -o xtrace + # Turn off tracing for the very-spammy nvm script. + set +o xtrace + [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" + set -o xtrace fi diff --git a/.evergreen/feature-combinations.sh b/.evergreen/feature-combinations.sh deleted file mode 100755 index ac04e1f6b..000000000 --- a/.evergreen/feature-combinations.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# Only default features. -export NO_FEATURES='' -# async-std-related features that conflict with the library's default features. -export ASYNC_STD_FEATURES='--no-default-features --features async-std-runtime,sync' -# All additional features that do not conflict with the default features. New features added to the library should also be added to this list. -export ADDITIONAL_FEATURES='--features tokio-sync,zstd-compression,snappy-compression,zlib-compression,openssl-tls,aws-auth,tracing-unstable,in-use-encryption-unstable,azure-kms' - - -# Array of feature combinations that, in total, provides complete coverage of the driver. -# This is useful for linting tasks where we want to get coverage of all features. -# Since some of our features are mutually exclusive we cannot just use --all-features. -export FEATURE_COMBINATIONS=( - "$NO_FEATURES" - "$ASYNC_STD_FEATURES" - "$ADDITIONAL_FEATURES" -) diff --git a/.evergreen/fetch-drivers-tools.sh b/.evergreen/fetch-drivers-tools.sh new file mode 100755 index 000000000..aa5abe9fa --- /dev/null +++ b/.evergreen/fetch-drivers-tools.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit + +if [[ -z "$DRIVERS_TOOLS" ]]; then + echo >&2 "\$DRIVERS_TOOLS must be set" + exit 1 +fi + +rm -rf $DRIVERS_TOOLS +git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS diff --git a/.evergreen/find-python3.sh b/.evergreen/find-python3.sh new file mode 100644 index 000000000..9ee7eff20 --- /dev/null +++ b/.evergreen/find-python3.sh @@ -0,0 +1,6 @@ +source ${DRIVERS_TOOLS}/.evergreen/find-python3.sh +PYTHON3=$(find_python3) + +cat <python3.yml +PYTHON3: "${PYTHON3}" +EOT \ No newline at end of file diff --git a/.evergreen/generate-tasks/.gitignore b/.evergreen/generate-tasks/.gitignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/.evergreen/generate-tasks/.gitignore @@ -0,0 +1 @@ +target diff --git a/.evergreen/generate-tasks/Cargo.toml b/.evergreen/generate-tasks/Cargo.toml new file mode 100644 index 000000000..98f82f460 --- /dev/null +++ b/.evergreen/generate-tasks/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "generate-tasks" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/.evergreen/generate-tasks/src/main.rs b/.evergreen/generate-tasks/src/main.rs new file mode 100644 index 000000000..49550e696 --- /dev/null +++ b/.evergreen/generate-tasks/src/main.rs @@ -0,0 +1,33 @@ +static VERSIONS: &[&str] = &[ + "4.0", "4.2", "4.4", "5.0", "6.0", "7.0", "8.0", "rapid", "latest", +]; + +static TOPOLOGIES: &[(&str, &str)] = &[ + ("standalone", "server"), + ("replicaset", "replica_set"), + ("sharded", "sharded_cluster"), +]; + +fn main() { + println!( + " +# Generated by generate-tasks. Do not manually edit! + +tasks:" + ); + for &(top_name, topology) in TOPOLOGIES.iter() { + for &version in VERSIONS.iter() { + println!( + " + - name: test-{version}-{top_name} + tags: [{version}, {top_name}] + commands: + - func: \"bootstrap mongo-orchestration\" + vars: + MONGODB_VERSION: {version} + TOPOLOGY: {topology} + - func: \"run driver test suite\"" + ); + } + } +} diff --git a/.evergreen/generate-uri.sh b/.evergreen/generate-uri.sh index da09ca145..1cca6f452 100644 --- a/.evergreen/generate-uri.sh +++ b/.evergreen/generate-uri.sh @@ -1,37 +1,41 @@ -#!/usr/bin/env bash +#!/bin/bash set -o errexit -if [ "$SSL" != "ssl" ]; then - return -fi - -DRIVERS_TOOLS_X509=`echo "$DRIVERS_TOOLS_X509" | sed 's/\//%2F/g'` -CA_FILE="${DRIVERS_TOOLS_X509}%2Fca.pem" -CERT_FILE="${DRIVERS_TOOLS_X509}%2Fclient.pem" +if [[ "$SSL" = "ssl" ]]; then + DRIVERS_TOOLS_X509="${DRIVERS_TOOLS}/.evergreen/x509gen" + DRIVERS_TOOLS_X509_ENCODED=$(echo "$DRIVERS_TOOLS_X509" | sed 's/\//%2F/g') + CA_FILE="${DRIVERS_TOOLS_X509_ENCODED}%2Fca.pem" + CERT_FILE="${DRIVERS_TOOLS_X509_ENCODED}%2Fclient.pem" -update_uri() { + update_uri() { local ORIG_URI=$1 if [[ "$ORIG_URI" == "" ]]; then - return + return fi # The rustls library requires a domain name. ORIG_URI=$(echo "$ORIG_URI" | sed s/127.0.0.1/localhost/) if [[ "$ORIG_URI" == *"?"* ]]; then - ORIG_URI="${ORIG_URI}&" + ORIG_URI="${ORIG_URI}&" else - ORIG_URI="${ORIG_URI}/?" + ORIG_URI="${ORIG_URI}/?" fi if [[ "$ORIG_URI" != *"tls=true"* ]]; then - ORIG_URI="${ORIG_URI}tls=true&" + ORIG_URI="${ORIG_URI}tls=true&" fi echo "${ORIG_URI}tlsCAFile=${CA_FILE}&tlsCertificateKeyFile=${CERT_FILE}&tlsAllowInvalidCertificates=true" -} + } + + MONGODB_URI="$(update_uri ${MONGODB_URI})" + SINGLE_MONGOS_LB_URI="$(update_uri ${SINGLE_MONGOS_LB_URI})" + MULTI_MONGOS_LB_URI="$(update_uri ${MULTI_MONGOS_LB_URI})" +fi -export MONGODB_URI="$(update_uri ${MONGODB_URI})" -export SINGLE_MONGOS_LB_URI="$(update_uri ${SINGLE_MONGOS_LB_URI})" -export MULTI_MONGOS_LB_URI="$(update_uri ${MULTI_MONGOS_LB_URI})" +cat <uri-expansions.yml +MONGODB_URI: ${MONGODB_URI} +SINGLE_MONGOS_LB_URI: ${SINGLE_MONGOS_LB_URI} +MULTI_MONGOS_LB_URI: ${MULTI_MONGOS_LB_URI} +EOT -echo "MONGODB_URI: ${MONGODB_URI}" -echo "SINGLE_MONGOS_LB_URI: ${SINGLE_MONGOS_LB_URI}" -echo "MULTI_MONGOS_LB_URI: ${MULTI_MONGOS_LB_URI}" \ No newline at end of file +# Print generated URIs +cat uri-expansions.yml diff --git a/.evergreen/install-dependencies.sh b/.evergreen/install-dependencies.sh index 0ecab992d..478899e4c 100755 --- a/.evergreen/install-dependencies.sh +++ b/.evergreen/install-dependencies.sh @@ -11,65 +11,55 @@ export CARGO_HOME="${PROJECT_DIRECTORY}/.cargo" # Make sure to use msvc toolchain rather than gnu, which is the default for cygwin if [ "Windows_NT" == "$OS" ]; then - export DEFAULT_HOST_OPTIONS='--default-host x86_64-pc-windows-msvc' - # rustup/cargo need the native Windows paths; $PROJECT_DIRECTORY is a cygwin path - export RUSTUP_HOME=$(cygpath ${RUSTUP_HOME} --windows) - export CARGO_HOME=$(cygpath ${CARGO_HOME} --windows) + export DEFAULT_HOST_OPTIONS='--default-host x86_64-pc-windows-msvc' + # rustup/cargo need the native Windows paths; $PROJECT_DIRECTORY is a cygwin path + export RUSTUP_HOME=$(cygpath ${RUSTUP_HOME} --windows) + export CARGO_HOME=$(cygpath ${CARGO_HOME} --windows) fi for arg; do - if [ $arg == "rust" ]; then - curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path $DEFAULT_HOST_OPTIONS + if [ $arg == "rust" ]; then + curl https://sh.rustup.rs -sSf | sh -s -- -y --no-modify-path $DEFAULT_HOST_OPTIONS - # This file is not created by default on Windows - echo 'export PATH="$PATH:${CARGO_HOME}/bin"' >> ${CARGO_HOME}/env - echo "export CARGO_NET_GIT_FETCH_WITH_CLI=true" >> ${CARGO_HOME}/env - - source .evergreen/configure-rust.sh - rustup toolchain install nightly -c rustfmt - # TODO RUST-1674: remove this workaround - rustup default 1.69 - elif [ $arg == "mdbook" ]; then - source ${CARGO_HOME}/env - # Install the manual rendering tool - cargo install mdbook - elif [ $arg == "junit-dependencies" ]; then - source ${CARGO_HOME}/env - # Install tool for converting cargo test output to junit - cargo install cargo2junit + # Cygwin has a bug with reporting symlink paths that breaks rustup; see + # https://github.com/rust-lang/rustup/issues/4239. This works around it by replacing the + # symlinks with copies. + if [ "Windows_NT" == "$OS" ]; then + pushd ${CARGO_HOME}/bin + python3 ../../.evergreen/unsymlink.py + popd + fi - # install npm/node - ./.evergreen/install-node.sh + # This file is not created by default on Windows + echo 'export PATH="$PATH:${CARGO_HOME}/bin"' >>${CARGO_HOME}/env + echo "export CARGO_NET_GIT_FETCH_WITH_CLI=true" >>${CARGO_HOME}/env - source ./.evergreen/env.sh + source .evergreen/env.sh + rustup toolchain install nightly -c rustfmt + elif [ $arg == "junit-dependencies" ]; then + source ${CARGO_HOME}/env - # Install tool for merging different junit reports into a single one - set +o errexit - set -o pipefail + source ./.evergreen/env.sh - npm install -g junit-report-merger --cache $(mktemp -d) 2>&1 | tee npm-install-output - RESULT=$? - MATCH=$(grep -o '/\S*-debug.log' npm-install-output) - if [[ $MATCH != "" ]]; then - echo ===== BEGIN NPM LOG ===== - cat $MATCH - echo ===== END NPM LOG ===== - fi + # Install junit-compatible test runner + cargo install cargo-nextest --locked - set -o errexit - if [ $RESULT -ne 0 ]; then - exit $RESULT - fi - elif [ $arg == "libmongocrypt" ]; then - mkdir ${PROJECT_DIRECTORY}/libmongocrypt - cd ${PROJECT_DIRECTORY}/libmongocrypt - curl -sSfO https://s3.amazonaws.com/mciuploads/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz - tar xzf libmongocrypt-all.tar.gz - if [ "Windows_NT" == "$OS" ]; then - chmod +x ${MONGOCRYPT_LIB_DIR}/../bin/*.dll - fi - else - echo Missing/unknown install option: "$arg" - exit 1 + # Install tool for merging different junit reports into a single one + cargo install merge-junit + elif [ $arg == "libmongocrypt" ]; then + mkdir ${PROJECT_DIRECTORY}/libmongocrypt + cd ${PROJECT_DIRECTORY}/libmongocrypt + curl -sSfO https://s3.amazonaws.com/mciuploads/libmongocrypt/all/master/latest/libmongocrypt-all.tar.gz + tar xzf libmongocrypt-all.tar.gz + if [ "Windows_NT" == "$OS" ]; then + chmod +x ${MONGOCRYPT_LIB_DIR}/../bin/*.dll fi + elif [ $arg == "cargo-lambda" ]; then + source ${CARGO_HOME}/env + cargo install cargo-binstall + cargo binstall cargo-lambda -y + else + echo Missing/unknown install option: "$arg" + exit 1 + fi done diff --git a/.evergreen/install-node.sh b/.evergreen/install-node.sh deleted file mode 100755 index 2185f28a6..000000000 --- a/.evergreen/install-node.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/bin/bash -set -o errexit # Exit the script with error if any of the commands fail - -NVM_WINDOWS_URL="https://github.com/coreybutler/nvm-windows/releases/download/1.1.7/nvm-noinstall.zip" -NVM_URL="https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh" - -NODE_LTS_NAME=${NODE_LTS_NAME:-erbium} -MSVS_VERSION=${MSVS_VERSION:-2019} -NODE_ARTIFACTS_PATH="${PROJECT_DIRECTORY}/node-artifacts" -NPM_CACHE_DIR="${NODE_ARTIFACTS_PATH}/npm" -NPM_TMP_DIR="${NODE_ARTIFACTS_PATH}/tmp" - -# this needs to be explicitly exported for the nvm install below -export NVM_DIR="${NODE_ARTIFACTS_PATH}/nvm" -export XDG_CONFIG_HOME=${NODE_ARTIFACTS_PATH} - -# create node artifacts path if needed -mkdir -p ${NODE_ARTIFACTS_PATH} -mkdir -p ${NPM_CACHE_DIR} -mkdir -p "${NPM_TMP_DIR}" - -case $NODE_LTS_NAME in - "argon") - VERSION=4 - ;; - "boron") - VERSION=6 - ;; - "carbon") - VERSION=8 - ;; - "dubnium") - VERSION=10 - ;; - "erbium") - VERSION=12 - ;; - "fermium") - VERSION=14 - ;; - "gallium") - VERSION=16 - ;; - "hydrogen") - VERSION=18 - ;; - "iron") - VERSION=20 - ;; - *) - echo "Unsupported Node LTS version $1" - exit 1 - ;; -esac - -NODE_VERSION=$(curl --retry 8 --retry-delay 5 --max-time 50 --silent -o- https://nodejs.org/download/release/latest-v${VERSION}.x/SHASUMS256.txt | head -n 1 | awk '{print $2};' | cut -d- -f2) -export NODE_VERSION=${NODE_VERSION:1} # :1 gets rid of the leading 'v' - -# output node version to expansions file for use in subsequent scripts -cat < deps-expansion.yml - NODE_VERSION: "$NODE_VERSION" -EOT - -# install Node.js on Windows -if [[ "$OS" == "Windows_NT" ]]; then - # Delete pre-existing node to avoid version conflicts - rm -rf "/cygdrive/c/Program Files/nodejs" - - - NVM_HOME=$(cygpath -w "$NVM_DIR") - export NVM_HOME - NVM_SYMLINK=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") - export NVM_SYMLINK - NVM_ARTIFACTS_PATH=$(cygpath -w "$NODE_ARTIFACTS_PATH/bin") - export NVM_ARTIFACTS_PATH - PATH=$(cygpath $NVM_SYMLINK):$(cygpath $NVM_HOME):$PATH - export PATH - - curl -L $NVM_WINDOWS_URL -o nvm.zip - unzip -d "$NVM_DIR" nvm.zip - rm nvm.zip - - chmod 777 "$NVM_DIR" - chmod -R a+rx "$NVM_DIR" - - cat < "$NVM_DIR/settings.txt" -root: $NVM_HOME -path: $NVM_SYMLINK -EOT - nvm install "$NODE_VERSION" - nvm use "$NODE_VERSION" - which node || echo "node not found, PATH=$PATH" - which npm || echo "npm not found, PATH=$PATH" - npm config set msvs_version ${MSVS_VERSION} - npm config set scripts-prepend-node-path true - -# install Node.js on Linux/MacOS -else - curl -o- $NVM_URL | bash - [ -s "${NVM_DIR}/nvm.sh" ] && \. "${NVM_DIR}/nvm.sh" - nvm install --no-progress "$NODE_VERSION" - - # setup npm cache in a local directory - cat < .npmrc -devdir=${NPM_CACHE_DIR}/.node-gyp -init-module=${NPM_CACHE_DIR}/.npm-init.js -cache=${NPM_CACHE_DIR} -tmp=${NPM_TMP_DIR} -EOT -fi diff --git a/.evergreen/release-build-vars.sh b/.evergreen/release-build-vars.sh new file mode 100644 index 000000000..bd2ade564 --- /dev/null +++ b/.evergreen/release-build-vars.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +source ./.evergreen/env.sh + +set +x + +CRATE_VERSION=$(cargo metadata --format-version=1 --no-deps | jq --raw-output '.packages[0].version') + +. ${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh drivers/rust +rm secrets-export.sh + +PAPERTRAIL_PRODUCT="rust-driver" +TEST_PREFIX="" +if [[ "${DRY_RUN:-}" == "yes" ]]; then + PAPERTRAIL_PRODUCT="rust-driver-testing" + TEST_PREFIX="testing-" +fi + +cat <release-expansion.yml +CARGO_REGISTRY_TOKEN: "${CARGO_REGISTRY_TOKEN}" +CRATE_VERSION: "${CRATE_VERSION}" +PAPERTRAIL_KEY_ID: "${PAPERTRAIL_KEY_ID}" +PAPERTRAIL_SECRET_KEY: "${PAPERTRAIL_SECRET_KEY}" +PAPERTRAIL_PRODUCT: "${PAPERTRAIL_PRODUCT}" +ARTIFACTORY_USERNAME: "${ARTIFACTORY_USERNAME}" +ARTIFACTORY_PASSWORD: "${ARTIFACTORY_PASSWORD}" +GARASIGN_USERNAME: "${GARASIGN_USERNAME}" +GARASIGN_PASSWORD: "${GARASIGN_PASSWORD}" +S3_UPLOAD_AWS_KEY: "${S3_UPLOAD_AWS_KEY}" +S3_UPLOAD_AWS_SECRET: "${S3_UPLOAD_AWS_SECRET}" +TEST_PREFIX: "${TEST_PREFIX}" +EOT diff --git a/.evergreen/release-danger-do-not-run-manually.sh b/.evergreen/release-danger-do-not-run-manually.sh old mode 100644 new mode 100755 index 6939c0b1d..0f0534469 --- a/.evergreen/release-danger-do-not-run-manually.sh +++ b/.evergreen/release-danger-do-not-run-manually.sh @@ -13,19 +13,26 @@ set +x set -o errexit -if [[ -z "$TAG" ]]; then - >&2 echo "\$TAG must be set to the git tag of the release" - exit 1 +if [[ -z "$CARGO_REGISTRY_TOKEN" ]]; then + echo >&2 "\$CARGO_REGISTRY_TOKEN must be set to the crates.io authentication token" + exit 1 fi -if [[ -z "$TOKEN" ]]; then - >&2 echo "\$TOKEN must be set to the crates.io authentication token" - exit 1 -fi - -git fetch origin tag $TAG --no-tags -git checkout $TAG +source ./.evergreen/env.sh -source ./.evergreen/configure-rust.sh +EXTRA="" +if [[ "${DRY_RUN}" == "yes" ]]; then + EXTRA="--dry-run" +fi -cargo publish --token $TOKEN +if [[ "${PACKAGE_ONLY}" == "yes" ]]; then + pushd macros + cargo package --no-verify --allow-dirty + popd + cargo package --no-verify --allow-dirty +else + pushd macros + cargo publish ${EXTRA} + popd + cargo publish ${EXTRA} +fi diff --git a/.evergreen/release-fetch-tag.sh b/.evergreen/release-fetch-tag.sh new file mode 100755 index 000000000..5011108ef --- /dev/null +++ b/.evergreen/release-fetch-tag.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit + +if [[ -z "$GIT_TAG" ]]; then + echo >&2 "\$GIT_TAG must be set to the git tag of the release" + exit 1 +fi + +git fetch origin tag $GIT_TAG --no-tags +git checkout $GIT_TAG diff --git a/.evergreen/release-manual-trigger.sh b/.evergreen/release-manual-trigger.sh new file mode 100755 index 000000000..7bbeb12ff --- /dev/null +++ b/.evergreen/release-manual-trigger.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# This should only be used if the normal evergreen automation triggered by the +# tag push has failed. This will manually trigger the same evergreen workflow +# that the tag push would have. + +if [[ -z "$TAG" ]]; then + echo >&2 "\$TAG must be set to the git tag of the release" + exit 1 +fi +if [[ "$CONFIRM" != "YES" ]]; then + echo >&2 "THIS ACTION IS IRREVOCABLE. Set \$CONFIRM to YES to validate that you really want to release a new version of the driver." + exit 1 +fi + +evergreen patch --path .evergreen/releases.yml -t publish-release -v all -u -p mongo-rust-driver-current --browse --param triggered_by_git_tag=${TAG} \ No newline at end of file diff --git a/.evergreen/release-sign.sh b/.evergreen/release-sign.sh new file mode 100644 index 000000000..b9fbe69b3 --- /dev/null +++ b/.evergreen/release-sign.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -o errexit +set +x + +echo "${ARTIFACTORY_PASSWORD}" | docker login --password-stdin --username ${ARTIFACTORY_USERNAME} artifactory.corp.mongodb.com + +echo "GRS_CONFIG_USER1_USERNAME=${GARASIGN_USERNAME}" >> "signing-envfile" +echo "GRS_CONFIG_USER1_PASSWORD=${GARASIGN_PASSWORD}" >> "signing-envfile" + +docker run \ + --env-file=signing-envfile \ + --rm \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + artifactory.corp.mongodb.com/release-tools-container-registry-local/garasign-gpg \ + /bin/bash -c "gpgloader && gpg --yes -v --armor -o mongodb-${CRATE_VERSION}.sig --detach-sign target/package/mongodb-${CRATE_VERSION}.crate" + +docker run \ + --env-file=signing-envfile \ + --rm \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + artifactory.corp.mongodb.com/release-tools-container-registry-local/garasign-gpg \ + /bin/bash -c "gpgloader && gpg --yes -v --armor -o mongodb-internal-macros-${CRATE_VERSION}.sig --detach-sign macros/target/package/mongodb-internal-macros-${CRATE_VERSION}.crate" diff --git a/.evergreen/releases.yml b/.evergreen/releases.yml index 9de31bffd..c2b2556d6 100644 --- a/.evergreen/releases.yml +++ b/.evergreen/releases.yml @@ -1,3 +1,34 @@ +## Testing changes to this file +# +# You'll need to know the git tag of the most recent release; call that ${TAG}. +# +# The simplest way is to develop changes as a branch from the most recent release; +# in that case, a release dry-run can be executed via: +# +# evergreen patch --path .evergreen/releases.yml \ +# -t publish-release -v all \ +# -p mongo-rust-driver-current \ +# -u --browse \ +# --param triggered_by_git_tag=${TAG} \ +# --param DRY_RUN=yes +# +# If the changes need to be developed against the main branch, more steps are needed: +# +# 1. Add dummy version numbers to the Cargo.toml lines for mongodb-internal-macros, bson, +# and libmongocrypt +# 2. Comment out the "fetch tag" func call from the "publish-release" task in this file +# 3. Execute: +# +# evergreen patch --path .evergreen/releases.yml \ +# -t publish-release -v all \ +# -p mongo-rust-driver \ +# -u --browse \ +# --param triggered_by_git_tag=${TAG} \ +# --param DRY_RUN=yes \ +# --param PACKAGE_ONLY=yes +# +# Make sure to remove the changes from 1 and 2 before merging! + exec_timeout_secs: 3600 functions: @@ -12,9 +43,12 @@ functions: working_dir: "src" script: | export PROJECT_DIRECTORY="$(pwd)" + export DRIVERS_TOOLS="$(pwd)/../drivers-tools" cat < expansion.yml + DRIVERS_TOOLS: "$DRIVERS_TOOLS" PROJECT_DIRECTORY: "$PROJECT_DIRECTORY" + GIT_TAG: "${triggered_by_git_tag}" PREPARE_SHELL: | set -o errexit set -o xtrace @@ -40,34 +74,138 @@ functions: rm expansion.yml "install dependencies": - command: shell.exec + - command: shell.exec + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + .evergreen/install-dependencies.sh rust + + - command: subprocess.exec + params: + working_dir: src + include_expansions_in_env: + - DRIVERS_TOOLS + binary: bash + args: + - .evergreen/fetch-drivers-tools.sh + + "build vars": + - command: ec2.assume_role + params: + role_arn: ${aws_test_secrets_role} + + - command: subprocess.exec + params: + working_dir: src + add_expansions_to_env: true + binary: bash + args: + - .evergreen/release-build-vars.sh + + - command: expansions.update + params: + file: src/release-expansion.yml + + - command: shell.exec + params: + working_dir: "src" + script: rm release-expansion.yml + + "fetch tag": + command: subprocess.exec params: working_dir: "src" - script: | - ${PREPARE_SHELL} - .evergreen/install-dependencies.sh rust + include_expansions_in_env: + - GIT_TAG + binary: bash + args: + - .evergreen/release-fetch-tag.sh "publish release": - - command: shell.exec + - command: subprocess.exec type: test params: working_dir: "src" - script: | - set +x + add_expansions_to_env: true + binary: bash + args: + - .evergreen/release-danger-do-not-run-manually.sh + + "publish papertrail": + - command: papertrail.trace + params: + key_id: ${PAPERTRAIL_KEY_ID} + secret_key: ${PAPERTRAIL_SECRET_KEY} + product: ${PAPERTRAIL_PRODUCT} + version: ${CRATE_VERSION} + filenames: + - src/target/package/mongodb-${CRATE_VERSION}.crate + - src/macros/target/package/mongodb-internal-macros-${CRATE_VERSION}.crate - TAG=${GIT_TAG} \ - TOKEN=${CRATES_IO_TOKEN} \ - PROJECT_DIRECTORY="$(pwd)" \ - bash .evergreen/release-danger-do-not-run-manually.sh + "sign release": + - command: subprocess.exec + params: + working_dir: "src" + include_expansions_in_env: + - ARTIFACTORY_USERNAME + - ARTIFACTORY_PASSWORD + - GARASIGN_USERNAME + - GARASIGN_PASSWORD + - CRATE_VERSION + binary: bash + args: + - .evergreen/release-sign.sh + + # Note for debugging: the links generated by Evergreen for these files will + # return a "permission denied" error; this is expected and a consequence of + # s3 configuration. The files can be viewed/downloaded by replacing the host + # portion of the URL with `downloads.mongodb.org`. + "save signature": + - command: s3.put + params: + aws_key: ${S3_UPLOAD_AWS_KEY} + aws_secret: ${S3_UPLOAD_AWS_SECRET} + local_files_include_filter: + - src/mongodb-${CRATE_VERSION}.sig + - src/mongodb-internal-macros-${CRATE_VERSION}.sig + remote_file: rust-driver/${TEST_PREFIX} + bucket: cdn-origin-rust-driver + permissions: private + content_type: text/plain + display_name: signature- + + "create and upload SSDLC compliance report": + - command: subprocess.exec + params: + working_dir: "src" + include_expansions_in_env: + - CRATE_VERSION + binary: bash + args: + - .evergreen/create-ssdlc-compliance-report.sh + - command: s3.put + params: + aws_key: ${S3_UPLOAD_AWS_KEY} + aws_secret: ${S3_UPLOAD_AWS_SECRET} + local_file: src/.evergreen/${CRATE_VERSION}-ssdlc-compliance-report.md + remote_file: rust-driver/${TEST_PREFIX}${CRATE_VERSION}-ssdlc-compliance-report.md + bucket: cdn-origin-rust-driver + permissions: private + content_type: text/markdown tasks: - name: "publish-release" commands: - func: "fetch source" - func: "install dependencies" + - func: "fetch tag" + - func: "build vars" - func: "publish release" - vars: - GIT_TAG: ${triggered_by_git_tag} + - func: "publish papertrail" + - func: "sign release" + - func: "save signature" + - func: "create and upload SSDLC compliance report" axes: - id: "os" diff --git a/.evergreen/run-atlas-tests.sh b/.evergreen/run-atlas-tests.sh index 8aa002113..bd9e9d514 100755 --- a/.evergreen/run-atlas-tests.sh +++ b/.evergreen/run-atlas-tests.sh @@ -1,15 +1,24 @@ #!/bin/bash set -o errexit +set -o pipefail -if [ "$ASYNC_RUNTIME" = "tokio" ]; then - OPTIONS="" -elif [ "$ASYNC_RUNTIME" = "async-std" ]; then - OPTIONS=" --no-default-features --features async-std-runtime" -else - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 -fi - -source ./.evergreen/configure-rust.sh -RUST_BACKTRACE=1 cargo test atlas_connectivity ${OPTIONS} \ No newline at end of file +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +CARGO_OPTIONS+=("--ignore-default-filter") + +source "${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh" drivers/atlas_connect + +set +o errexit + +# Create client certificate file from base64 encoded secret: +mkdir -p .secrets +chmod 700 .secrets +echo "${ATLAS_X509_DEV_CERT_BASE64}" | base64 --decode > .secrets/clientcert.pem +ATLAS_X509_DEV_WITH_CERT="${ATLAS_X509_DEV}&tlsCertificateKeyFile=.secrets/clientcert.pem" +export ATLAS_X509_DEV_WITH_CERT + +cargo_test test::atlas_connectivity + +exit $CARGO_RESULT diff --git a/.evergreen/run-aws-lambda-test.sh b/.evergreen/run-aws-lambda-test.sh new file mode 100755 index 000000000..c4aa3d01e --- /dev/null +++ b/.evergreen/run-aws-lambda-test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Needed for cargo-lambda +pip3 install ziglang + +source ${PROJECT_DIRECTORY}/.cargo/env +rustup default stable + +${DRIVERS_TOOLS}/.evergreen/aws_lambda/run-deployed-lambda-aws-tests.sh \ No newline at end of file diff --git a/.evergreen/run-aws-tests.sh b/.evergreen/run-aws-tests.sh index 22a24546f..3eb64a79e 100755 --- a/.evergreen/run-aws-tests.sh +++ b/.evergreen/run-aws-tests.sh @@ -1,48 +1,27 @@ #!/bin/bash set -o xtrace -set -o errexit # Exit the script with error if any of the commands fail - -############################################ -# Main Program # -############################################ - -# Supported/used environment variables: -# MONGODB_URI Set the URI, including an optional username/password to use -# to connect to the server via MONGODB-AWS authentication -# mechanism. -# ASYNC_RUNTIME Specify the async runtime to use. Must be either "tokio" or -# "async-std". +set -o errexit # Exit the script with error if any of the commands fail echo "Running MONGODB-AWS authentication tests" -# ensure no secrets are printed in log files -set +x - -# load the script -shopt -s expand_aliases # needed for `urlencode` alias -[ -s "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" ] && source "${PROJECT_DIRECTORY}/prepare_mongodb_aws.sh" -MONGODB_URI=${MONGODB_URI:-"mongodb://localhost"} -MONGODB_URI="${MONGODB_URI}/aws?authMechanism=MONGODB-AWS" -if [[ -n ${SESSION_TOKEN} ]]; then - MONGODB_URI="${MONGODB_URI}&authMechanismProperties=AWS_SESSION_TOKEN:${SESSION_TOKEN}" -fi +cd $DRIVERS_TOOLS/.evergreen/auth_aws +. aws_setup.sh $AWS_AUTH_TYPE -export MONGODB_URI="$MONGODB_URI" +set -o errexit -if [ "$ASSERT_NO_URI_CREDS" = "true" ]; then - if echo "$MONGODB_URI" | grep -q "@"; then - echo "MONGODB_URI unexpectedly contains user credentials!"; - exit 1 - fi -fi +cd ${PROJECT_DIRECTORY} +source .evergreen/env.sh +source .evergreen/cargo-test.sh -# show test output -set -x +FEATURE_FLAGS+=("aws-auth") -set -o errexit +set +o errexit -source ./.evergreen/configure-rust.sh +cargo_test test::auth::aws +cargo_test lambda_examples::auth::test_handler +cargo_test spec::auth +cargo_test uri_options +cargo_test connection_string -RUST_BACKTRACE=1 cargo test --features aws-auth auth_aws::auth_aws -RUST_BACKTRACE=1 cargo test --features aws-auth lambda_examples::auth::test_handler +exit $CARGO_RESULT diff --git a/.evergreen/run-azure-kms-test.sh b/.evergreen/run-azure-kms-test.sh index 4097e57ed..3fcc6ff20 100755 --- a/.evergreen/run-azure-kms-test.sh +++ b/.evergreen/run-azure-kms-test.sh @@ -3,7 +3,7 @@ set -o errexit set -o pipefail -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh set -o xtrace @@ -19,8 +19,8 @@ cp .evergreen/azure-kms-test/target/debug/azure-kms-test azurekms_remote tar czf azurekms_remote.tgz azurekms_remote AZUREKMS_SRC=azurekms_remote.tgz \ - AZUREKMS_DST="." \ - $AZUREKMS_TOOLS/copy-file.sh + AZUREKMS_DST="." \ + $AZUREKMS_TOOLS/copy-file.sh AZUREKMS_CMD="tar xvf azurekms_remote.tgz" $AZUREKMS_TOOLS/run-command.sh -AZUREKMS_CMD="LD_LIBRARY_PATH=./azurekms_remote/lib ./azurekms_remote/azure-kms-test" \ - $AZUREKMS_TOOLS/run-command.sh \ No newline at end of file +AZUREKMS_CMD="LD_LIBRARY_PATH=./azurekms_remote/lib KEY_NAME='${AZUREKMS_KEY_NAME}' KEY_VAULT_ENDPOINT='${AZUREKMS_KEY_VAULT_ENDPOINT}' ./azurekms_remote/azure-kms-test" \ + $AZUREKMS_TOOLS/run-command.sh diff --git a/.evergreen/run-bson-benchmarks.sh b/.evergreen/run-bson-benchmarks.sh index 60ba0fb49..67ead6b1f 100755 --- a/.evergreen/run-bson-benchmarks.sh +++ b/.evergreen/run-bson-benchmarks.sh @@ -2,11 +2,11 @@ set -o errexit -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh cd benchmarks cargo run \ - --release \ - -- --output="../benchmark-results.json" --bson + --release \ + -- --output="../benchmark-results.json" --bson cat ../benchmark-results.json diff --git a/.evergreen/run-compile-benchmarks.sh b/.evergreen/run-compile-benchmarks.sh index cf991088c..5209f81f3 100755 --- a/.evergreen/run-compile-benchmarks.sh +++ b/.evergreen/run-compile-benchmarks.sh @@ -2,26 +2,17 @@ set -o errexit -source ./.evergreen/configure-rust.sh +source ./.evergreen/env.sh FEATURES="" -if [ "$ASYNC_RUNTIME" = "tokio" ]; then - FEATURES="tokio-runtime" -elif [ "$ASYNC_RUNTIME" = "async-std" ]; then - FEATURES="async-std-runtime" -else - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 -fi - rustc --version SECONDS=0 cargo build --release DURATION_IN_SECONDS="$SECONDS" -cat > benchmark-results.json <<-EOF +cat >benchmark-results.json <<-EOF [ { "info": { diff --git a/.evergreen/run-connection-string-tests.sh b/.evergreen/run-connection-string-tests.sh deleted file mode 100755 index ed928e9be..000000000 --- a/.evergreen/run-connection-string-tests.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -o errexit -set -o xtrace - -source ./.evergreen/configure-rust.sh - -RUST_BACKTRACE=1 cargo test --features aws-auth spec::auth -RUST_BACKTRACE=1 cargo test --features aws-auth uri_options -RUST_BACKTRACE=1 cargo test --features aws-auth connection_string diff --git a/.evergreen/run-csfle-kmip-servers.sh b/.evergreen/run-csfle-kmip-servers.sh deleted file mode 100755 index db0d11a1f..000000000 --- a/.evergreen/run-csfle-kmip-servers.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -if [ "$TLS_FEATURE" != "openssl-tls" ]; then - echo "Skipping kms servers: openssl-tls not enabled" - exit -fi - -cd ${DRIVERS_TOOLS}/.evergreen/csfle -. ./activate-kmstlsvenv.sh -# TMPDIR is required to avoid "AF_UNIX path too long" errors. -export TMPDIR="$(dirname ${DRIVERS_TOOLS})" - -python kms_kmip_server.py & -python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 9000 & -python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 9001 & -python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 9002 --require_client_cert \ No newline at end of file diff --git a/.evergreen/run-csfle-mock-azure-imds.sh b/.evergreen/run-csfle-mock-azure-imds.sh deleted file mode 100755 index 691110672..000000000 --- a/.evergreen/run-csfle-mock-azure-imds.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -. ${DRIVERS_TOOLS}/.evergreen/find-python3.sh -PYTHON=$(find_python3) - -function prepend() { while read line; do echo "${1}${line}"; done; } - -cd ${DRIVERS_TOOLS}/.evergreen/csfle -${PYTHON} bottle.py fake_azure:imds -b localhost:${AZURE_IMDS_MOCK_PORT} 2>&1 | prepend "[MOCK AZURE IMDS] " \ No newline at end of file diff --git a/.evergreen/run-csfle-tests.sh b/.evergreen/run-csfle-tests.sh index dab04d157..e70e5d792 100755 --- a/.evergreen/run-csfle-tests.sh +++ b/.evergreen/run-csfle-tests.sh @@ -3,46 +3,41 @@ set -o errexit set -o pipefail -source ./.evergreen/env.sh +source .evergreen/env.sh +source .evergreen/cargo-test.sh set -o xtrace -FEATURE_FLAGS="in-use-encryption-unstable,aws-auth,azure-kms,${TLS_FEATURE}" -OPTIONS="-- -Z unstable-options --format json --report-time" +export CSFLE_TLS_CERT_DIR="${DRIVERS_TOOLS}/.evergreen/x509gen" -if [ "$SINGLE_THREAD" = true ]; then - OPTIONS="$OPTIONS --test-threads=1" +FEATURE_FLAGS+=("in-use-encryption" "azure-kms") +CARGO_OPTIONS+=("--ignore-default-filter") + +if [[ "$OPENSSL" = true ]]; then + FEATURE_FLAGS+=("openssl-tls") fi if [ "$OS" = "Windows_NT" ]; then - export CSFLE_TLS_CERT_DIR=$(cygpath ${CSFLE_TLS_CERT_DIR} --windows) - export SSL_CERT_FILE=$(cygpath /etc/ssl/certs/ca-bundle.crt --windows) - export SSL_CERT_DIR=$(cygpath /etc/ssl/certs --windows) + export CSFLE_TLS_CERT_DIR=$(cygpath ${CSFLE_TLS_CERT_DIR} --windows) + export SSL_CERT_FILE=$(cygpath /etc/ssl/certs/ca-bundle.crt --windows) + export SSL_CERT_DIR=$(cygpath /etc/ssl/certs --windows) fi -export AWS_DEFAULT_REGION=us-east-1 -. ${DRIVERS_TOOLS}/.evergreen/csfle/set-temp-creds.sh - -echo "cargo test options: --features ${FEATURE_FLAGS} ${OPTIONS}" - -CARGO_RESULT=0 +. ./secrets-export.sh -cargo_test() { - RUST_BACKTRACE=1 \ - cargo test --features ${FEATURE_FLAGS} $1 ${OPTIONS} | grep -v '{"t":' | cargo2junit - (( CARGO_RESULT = ${CARGO_RESULT} || $? )) -} +# Add mongodb binaries to path for mongocryptd +PATH=${PATH}:${DRIVERS_TOOLS}/mongodb/bin set +o errexit -cargo_test test::csfle > prose.xml -cargo_test test::spec::client_side_encryption > spec.xml +cargo_test test::csfle + +FEATURE_FLAGS+=("aws-auth") +cargo_test on_demand_aws::success # Unset variables for on-demand credential failure tests. unset AWS_ACCESS_KEY_ID unset AWS_SECRET_ACCESS_KEY -cargo_test test::csfle::on_demand_aws_failure > failure.xml - -junit-report-merger results.xml prose.xml spec.xml failure.xml +cargo_test on_demand_aws::failure exit ${CARGO_RESULT} diff --git a/.evergreen/run-driver-benchmark-unresponsive.sh b/.evergreen/run-driver-benchmark-unresponsive.sh new file mode 100755 index 000000000..f7ba5693e --- /dev/null +++ b/.evergreen/run-driver-benchmark-unresponsive.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -o errexit + +source ./.evergreen/env.sh + +cd benchmarks +cargo run \ + --release \ + -- --output="../benchmark-results.json" -i 21 + +cat ../benchmark-results.json diff --git a/.evergreen/run-driver-benchmarks.sh b/.evergreen/run-driver-benchmarks.sh index 5e2bfdb8f..e2750fea4 100755 --- a/.evergreen/run-driver-benchmarks.sh +++ b/.evergreen/run-driver-benchmarks.sh @@ -2,24 +2,11 @@ set -o errexit -source ./.evergreen/configure-rust.sh - -FEATURES="" - -if [ "$ASYNC_RUNTIME" = "tokio" ]; then - FEATURES="tokio-runtime" -elif [ "$ASYNC_RUNTIME" = "async-std" ]; then - FEATURES="async-std-runtime" -else - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 -fi +source ./.evergreen/env.sh cd benchmarks cargo run \ - --release \ - --no-default-features \ - --features ${FEATURES} \ - -- --output="../benchmark-results.json" --driver + --release \ + -- --output="../benchmark-results.json" --driver cat ../benchmark-results.json diff --git a/.evergreen/run-gssapi-tests.sh b/.evergreen/run-gssapi-tests.sh new file mode 100644 index 000000000..63478b563 --- /dev/null +++ b/.evergreen/run-gssapi-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -o xtrace +set -o errexit # Exit the script with error if any of the commands fail + +echo "Running MONGODB-GSSAPI authentication tests" + +cd ${PROJECT_DIRECTORY} +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +FEATURE_FLAGS+=("gssapi-auth") + +set +o errexit + +cargo_test spec::auth +cargo_test uri_options +cargo_test connection_string + +exit $CARGO_RESULT diff --git a/.evergreen/run-happy-eyeballs-tests.sh b/.evergreen/run-happy-eyeballs-tests.sh new file mode 100644 index 000000000..4f9291981 --- /dev/null +++ b/.evergreen/run-happy-eyeballs-tests.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +CARGO_OPTIONS+=("--ignore-default-filter") + +set +o errexit + +cargo_test "test::happy_eyeballs" +exit $CARGO_RESULT \ No newline at end of file diff --git a/.evergreen/run-mongodb-aws-ecs-test.sh b/.evergreen/run-mongodb-aws-ecs-test.sh index faedd2185..770b5793e 100755 --- a/.evergreen/run-mongodb-aws-ecs-test.sh +++ b/.evergreen/run-mongodb-aws-ecs-test.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -o xtrace # Write all commands first to stderr -set -o errexit # Exit the script with error if any of the commands fail +set -o xtrace # Write all commands first to stderr +set -o errexit # Exit the script with error if any of the commands fail MONGODB_URI="$1" diff --git a/.evergreen/run-mongodb-oidc-test.sh b/.evergreen/run-mongodb-oidc-test.sh new file mode 100755 index 000000000..f488c86c3 --- /dev/null +++ b/.evergreen/run-mongodb-oidc-test.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set +x # Disable debug trace +set -o errexit # Exit the script with error if any of the commands fail + +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +CARGO_OPTIONS+=("--ignore-default-filter") + +echo "Running MONGODB-OIDC authentication tests" + +# Make sure DRIVERS_TOOLS is set. +if [ -z "$DRIVERS_TOOLS" ]; then + echo "Must specify DRIVERS_TOOLS" + exit 1 +fi + +source ${DRIVERS_TOOLS}/.evergreen/auth_oidc/secrets-export.sh + +set +o errexit + +cargo_test test::spec::oidc_skip_ci::basic + +exit $CARGO_RESULT diff --git a/.evergreen/run-plain-tests.sh b/.evergreen/run-plain-tests.sh deleted file mode 100755 index 9ec26ea2b..000000000 --- a/.evergreen/run-plain-tests.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -set -o errexit -set -o xtrace - -source ./.evergreen/configure-rust.sh - -RUST_BACKTRACE=1 MONGO_PLAIN_AUTH_TEST=1 cargo test plain diff --git a/.evergreen/run-search-index-test.sh b/.evergreen/run-search-index-test.sh new file mode 100644 index 000000000..8ed266bde --- /dev/null +++ b/.evergreen/run-search-index-test.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +source ./.evergreen/env.sh +source .evergreen/cargo-test.sh + +CARGO_OPTIONS+=("--ignore-default-filter") + +set -o xtrace + +set +o errexit + +cargo_test test::index_management::search_index + +exit ${CARGO_RESULT} diff --git a/.evergreen/run-serverless-tests.sh b/.evergreen/run-serverless-tests.sh deleted file mode 100755 index b8fcb7133..000000000 --- a/.evergreen/run-serverless-tests.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail - -source ./.evergreen/env.sh - -FEATURE_FLAGS="zstd-compression,snappy-compression,zlib-compression" -DEFAULT_FEATURES="" - -if [ "$ASYNC_RUNTIME" = "async-std" ]; then - FEATURE_FLAGS="${FEATURE_FLAGS},async-std-runtime" - DEFAULT_FEATURES="--no-default-features" -elif [ "$ASYNC_RUNTIME" != "tokio" ]; then - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 -fi - -OPTIONS="-- -Z unstable-options --format json --report-time" - -if [ "$SINGLE_THREAD" = true ]; then - OPTIONS="$OPTIONS --test-threads=1" -fi - -FEATURE_FLAGS=${FEATURE_FLAGS},${TLS_FEATURE} - -echo "cargo test options: ${DEFAULT_FEATURES} --features $FEATURE_FLAGS ${OPTIONS}" - -CARGO_RESULT=0 - -cargo_test() { - RUST_BACKTRACE=1 \ - SERVERLESS="serverless" \ - cargo test ${DEFAULT_FEATURES} --features $FEATURE_FLAGS $1 $OPTIONS | cargo2junit - (( CARGO_RESULT = $CARGO_RESULT || $? )) -} - -set +o errexit - -cargo_test test::spec::crud > crud.xml -cargo_test test::spec::retryable_reads > retryable_reads.xml -cargo_test test::spec::retryable_writes > retryable_writes.xml -cargo_test test::spec::versioned_api > versioned_api.xml -cargo_test test::spec::sessions > sessions.xml -cargo_test test::spec::transactions > transactions.xml -cargo_test test::spec::load_balancers > load_balancers.xml -cargo_test test::cursor > cursor.xml -cargo_test test::spec::collection_management > coll.xml -cargo_test test::spec::command_monitoring_unified > monitoring.xml - -junit-report-merger results.xml crud.xml retryable_reads.xml retryable_writes.xml versioned_api.xml sessions.xml transactions.xml load_balancers.xml cursor.xml coll.xml monitoring.xml - -exit $CARGO_RESULT diff --git a/.evergreen/run-sync-tests.sh b/.evergreen/run-sync-tests.sh new file mode 100644 index 000000000..ba47fe961 --- /dev/null +++ b/.evergreen/run-sync-tests.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -o errexit +set -o pipefail + +source .evergreen/env.sh +source .evergreen/cargo-test.sh + +FEATURE_FLAGS+=("sync") + +echo "cargo test options: $(cargo_test_options)" + +set +o errexit + +cargo_test sync + +exit $CARGO_RESULT diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index cf70d2193..b88a5cc36 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -3,52 +3,40 @@ set -o errexit set -o pipefail -source ./.evergreen/env.sh +source .evergreen/env.sh +source .evergreen/cargo-test.sh -OPTIONS="-- -Z unstable-options --format json --report-time" -if [ "$SINGLE_THREAD" = true ]; then - OPTIONS="$OPTIONS --test-threads=1" -fi - -FEATURE_FLAGS="tracing-unstable,${TLS_FEATURE}" +FEATURE_FLAGS+=("tracing-unstable" "cert-key-password") -if [ "$SNAPPY_COMPRESSION_ENABLED" = true ]; then - FEATURE_FLAGS="${FEATURE_FLAGS},snappy-compression" +if [ "$OPENSSL" = true ]; then + FEATURE_FLAGS+=("openssl-tls") fi -if [ "$ZLIB_COMPRESSION_ENABLED" = true ]; then - FEATURE_FLAGS="${FEATURE_FLAGS},zlib-compression" + +if [ "$ZSTD" = true ]; then + FEATURE_FLAGS+=("zstd-compression") fi -if [ "$ZSTD_COMPRESSION_ENABLED" = true ]; then - FEATURE_FLAGS="${FEATURE_FLAGS},zstd-compression" + +if [ "$ZLIB" = true ]; then + FEATURE_FLAGS+=("zlib-compression") fi -if [ "$ASYNC_RUNTIME" = "tokio" ]; then - ASYNC_FEATURE_FLAGS=${FEATURE_FLAGS} - SYNC_FEATURE_FLAGS="tokio-sync,${FEATURE_FLAGS}" -elif [ "$ASYNC_RUNTIME" = "async-std" ]; then - OPTIONS="--no-default-features ${OPTIONS}" - ASYNC_FEATURE_FLAGS="async-std-runtime,${FEATURE_FLAGS}" - SYNC_FEATURE_FLAGS="sync,${FEATURE_FLAGS}" -else - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 +if [ "$SNAPPY" = true ]; then + FEATURE_FLAGS+=("snappy-compression") fi -echo "cargo test options: --features ${ASYNC_FEATURE_FLAGS} ${OPTIONS}" +echo "cargo test options: $(cargo_test_options)" set +o errexit -CARGO_RESULT=0 -RUST_BACKTRACE=1 cargo test --features $ASYNC_FEATURE_FLAGS $OPTIONS | tee results.json -(( CARGO_RESULT = CARGO_RESULT || $? )) -cat results.json | cargo2junit > async-tests.xml -RUST_BACKTRACE=1 cargo test sync --features $SYNC_FEATURE_FLAGS $OPTIONS | tee sync-tests.json -(( CARGO_RESULT = CARGO_RESULT || $? )) -cat sync-tests.json | cargo2junit > sync-tests.xml -RUST_BACKTRACE=1 cargo test --doc sync --features $SYNC_FEATURE_FLAGS $OPTIONS | tee sync-doc-tests.json -(( CARGO_RESULT = CARGO_RESULT || $? )) -cat sync-doc-tests.json | cargo2junit > sync-doc-tests.xml +if [ "Windows_NT" == "$OS" ]; then + export SSL_CERT_FILE=$(cygpath /etc/ssl/certs/ca-bundle.crt --windows) + export SSL_CERT_DIR=$(cygpath /etc/ssl/certs --windows) +fi + +cargo_test "" -junit-report-merger results.xml async-tests.xml sync-tests.xml sync-doc-tests.xml +# cargo-nextest doesn't support doc tests +RUST_BACKTRACE=1 cargo test --doc $(cargo_test_options) +((CARGO_RESULT = ${CARGO_RESULT} || $?)) -exit $CARGO_RESULT \ No newline at end of file +exit $CARGO_RESULT diff --git a/.evergreen/run-x509-tests.sh b/.evergreen/run-x509-tests.sh index e64c4a03a..737d6b2e8 100755 --- a/.evergreen/run-x509-tests.sh +++ b/.evergreen/run-x509-tests.sh @@ -1,32 +1,24 @@ #!/bin/bash set -o errexit -set -o xtrace +set -o xtrace set -o pipefail -source ./.evergreen/env.sh +source .evergreen/env.sh +source .evergreen/cargo-test.sh -export SUBJECT=`openssl x509 -subject -nameopt RFC2253 -noout -inform PEM -in $CERT_PATH` +CARGO_OPTIONS+=("--ignore-default-filter") + +export SUBJECT=$(openssl x509 -subject -nameopt RFC2253 -noout -inform PEM -in $CERT_PATH) # Strip `subject=` prefix from the subject export SUBJECT=${SUBJECT#"subject="} # Remove any leading or trailing whitespace -export SUBJECT=`echo "$SUBJECT" | awk '{$1=$1;print}'` +export SUBJECT=$(echo "$SUBJECT" | awk '{$1=$1;print}') -if [ "$ASYNC_RUNTIME" = "tokio" ]; then - RUNTIME_FEATURE="tokio-runtime" -elif [ "$ASYNC_RUNTIME" = "async-std" ]; then - RUNTIME_FEATURE="async-std-runtime" -else - echo "invalid async runtime: ${ASYNC_RUNTIME}" >&2 - exit 1 -fi +set +o errexit -OPTIONS="--no-default-features --features ${RUNTIME_FEATURE},${TLS_FEATURE} -- -Z unstable-options --format json --report-time" +MONGO_X509_USER="$SUBJECT" cargo_test x509_auth -set +o errexit -RUST_BACKTRACE=1 MONGO_X509_USER="$SUBJECT" cargo test x509 $OPTIONS | tee results.json -CARGO_EXIT=$? -cat results.json | cargo2junit > results.xml -exit $CARGO_EXIT +exit ${CARGO_RESULT} diff --git a/.evergreen/ssdlc-compliance-report-template.md b/.evergreen/ssdlc-compliance-report-template.md new file mode 100644 index 000000000..a13f1f5b0 --- /dev/null +++ b/.evergreen/ssdlc-compliance-report-template.md @@ -0,0 +1,34 @@ +# MongoDB Rust Driver SSDLC Compliance Report + +### Release Version: RELEASE_VERSION + +**Release Creator** +The creator of this release can be determined by visiting +https://github.com/mongodb/mongo-rust-driver/releases/tag/vRELEASE_VERSION. + +**Process Document** +Not available. + +**Tool used to track third party vulnerabilities** +N/A; the Rust driver does not bundle third-party dependencies + +**Third-Party Dependency Information** +N/A; the Rust driver does not bundle third-party dependencies + +**Static Analysis Findings** +To request a copy of the static analysis report, please contact +the MongoDB Rust driver team. + +**Signature Information** +The release signature for this version can be found by visiting +https://downloads.mongodb.org/rust-driver/mongodb-RELEASE_VERSION.sig. + +**Security Testing Report** +See [Driver Security Testing Summary](https://docs.google.com/document/d/1y2K_RY4GZVXpQvv4JH_35mSzFRTawNJ3mibpvSBU8H0/edit?usp=sharing) +(internal). Available as needed from the MongoDB Rust driver team. + +**Security Assessment Report** +N/A; non-goal for client libraries + +**Known Vulnerabilities** +None diff --git a/.evergreen/suite-tasks.yml b/.evergreen/suite-tasks.yml new file mode 100644 index 000000000..b414a2700 --- /dev/null +++ b/.evergreen/suite-tasks.yml @@ -0,0 +1,247 @@ + +# Generated by generate-tasks. Do not manually edit! + +tasks: + + - name: test-4.0-standalone + tags: [4.0, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.0 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-4.2-standalone + tags: [4.2, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.2 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-4.4-standalone + tags: [4.4, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.4 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-5.0-standalone + tags: [5.0, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 5.0 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-6.0-standalone + tags: [6.0, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 6.0 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-7.0-standalone + tags: [7.0, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 7.0 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-8.0-standalone + tags: [8.0, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 8.0 + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-rapid-standalone + tags: [rapid, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-latest-standalone + tags: [latest, standalone] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: latest + TOPOLOGY: server + - func: "run driver test suite" + + - name: test-4.0-replicaset + tags: [4.0, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.0 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-4.2-replicaset + tags: [4.2, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.2 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-4.4-replicaset + tags: [4.4, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.4 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-5.0-replicaset + tags: [5.0, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 5.0 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-6.0-replicaset + tags: [6.0, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 6.0 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-7.0-replicaset + tags: [7.0, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 7.0 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-8.0-replicaset + tags: [8.0, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 8.0 + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-rapid-replicaset + tags: [rapid, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-latest-replicaset + tags: [latest, replicaset] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: latest + TOPOLOGY: replica_set + - func: "run driver test suite" + + - name: test-4.0-sharded + tags: [4.0, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.0 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-4.2-sharded + tags: [4.2, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.2 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-4.4-sharded + tags: [4.4, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 4.4 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-5.0-sharded + tags: [5.0, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 5.0 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-6.0-sharded + tags: [6.0, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 6.0 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-7.0-sharded + tags: [7.0, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 7.0 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-8.0-sharded + tags: [8.0, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: 8.0 + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-rapid-sharded + tags: [rapid, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: rapid + TOPOLOGY: sharded_cluster + - func: "run driver test suite" + + - name: test-latest-sharded + tags: [latest, sharded] + commands: + - func: "bootstrap mongo-orchestration" + vars: + MONGODB_VERSION: latest + TOPOLOGY: sharded_cluster + - func: "run driver test suite" diff --git a/.evergreen/unsymlink.py b/.evergreen/unsymlink.py new file mode 100644 index 000000000..5d199429e --- /dev/null +++ b/.evergreen/unsymlink.py @@ -0,0 +1,19 @@ +import os +import shutil + +found = [] +for entry in os.scandir(): + if not entry.is_symlink(): + print(f"Skipping {entry.name}: not a symlink") + continue + target = os.readlink(entry.name) + if target != "rustup.exe": + print(f"Skipping {entry.name}: not rustup.exe") + continue + print(f"Found {entry.name}") + found.append(entry.name) + +for name in found: + print(f"Replacing {name} symlink with copy") + os.remove(name) + shutil.copy2("rustup.exe", name) \ No newline at end of file diff --git a/.evergreen/with-secrets.sh b/.evergreen/with-secrets.sh new file mode 100755 index 000000000..30f7973ca --- /dev/null +++ b/.evergreen/with-secrets.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# This script takes an AWS Secrets Manager vault name and a command line +# to execute, e.g. +# +# ./with-secrets drivers/atlas-dev setup-atlas-cluster.sh +# +# It fetches the secrets from the vault, populates the local environment +# variables with those secrets, and then executes the command line. +# +# Secrets are cached based on the name of the vault, so if an earlier +# task has fetched the same vault those secrets will be reused. + +vault=$1 +shift + +if [ -z "${vault}" ] || [ -z "$@" ]; then + echo "At least two arguments (vault name and command) are required." + exit 1 +fi + +vault_cache_key=$(echo "${vault}" | sed -e s/\\\//_/) +vault_cache_file="secrets-${vault_cache_key}.sh" + +if [ -f "${vault_cache_file}" ]; then + # Cached, hooray + . "${vault_cache_file}" +else + # Need to actually fetch from the vault + if [ -z "${DRIVERS_TOOLS}" ]; then + echo "\$DRIVERS_TOOLS must be set." + exit 1 + fi + . "${DRIVERS_TOOLS}/.evergreen/secrets_handling/setup-secrets.sh" "${vault}" + mv secrets-export.sh "${vault_cache_file}" +fi + +exec "$@" \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..4bf1c6eec --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Listing code owners is required by DRIVERS-3098 +* @mongodb/dbx-rust diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..724e3ff6b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +version: 2 +updates: + - package-ecosystem: cargo + directory: / + schedule: + interval: weekly + # Only bump to the latest version compatible with the dependency's version + # in Cargo.toml. This is the equivalent of running `cargo update`. + versioning-strategy: lockfile-only + # Update all dependencies in a single PR. + groups: + rust-dependencies: + patterns: + - "*" + # Include transitive dependencies. + allow: + - dependency-type: all + ignore: + - dependency-name: "mongocrypt-sys" diff --git a/.github/workflows/remove_labels.yml b/.github/workflows/remove_labels.yml index 9dc473b1f..b79e9e849 100644 --- a/.github/workflows/remove_labels.yml +++ b/.github/workflows/remove_labels.yml @@ -6,7 +6,7 @@ on: types: [created, edited] jobs: remove-labels: - if: ${{ github.actor != 'Tom Selander' && github.actor != 'patrickfreed' + if: ${{ github.actor != 'Tom Selander' && github.actor != 'abr-egn' && github.actor != 'isabelatkinson'}} runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 7c1008136..d9e0911a8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ *~ /target/ **/*.rs.bk -Cargo.lock .idea *.iml .vscode @@ -12,4 +11,7 @@ Cargo.lock # we install cargo and rustup in the project directory on Evergreen. .cargo .rustup -mongocryptd.pid \ No newline at end of file +mongocryptd.pid +semgrep/ +sarif.json +.secrets diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 000000000..e5a071c74 --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,3 @@ +benchmarks/ +src/test/ +etc/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..20038bcea --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3902 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.2", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +dependencies = [ + "backtrace", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +dependencies = [ + "nodrop", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.9.0", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.101", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bson" +version = "2.15.0" +source = "git+https://github.com/mongodb/bson-rust?branch=2.15.x#f6f163095b5159ce175424b0e02f9bd7acfaddf2" +dependencies = [ + "ahash", + "base64 0.22.1", + "bitvec", + "getrandom 0.2.16", + "getrandom 0.3.2", + "hex", + "indexmap 2.9.0", + "js-sys", + "once_cell", + "rand 0.9.1", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "bson" +version = "3.0.0" +source = "git+https://github.com/mongodb/bson-rust?branch=main#266aa3039e603cad96a5dde377aaf8251be76c79" +dependencies = [ + "ahash", + "base64 0.22.1", + "bitvec", + "getrandom 0.2.16", + "getrandom 0.3.2", + "hex", + "indexmap 2.9.0", + "js-sys", + "once_cell", + "rand 0.9.1", + "serde", + "serde_bytes", + "serde_json", + "simdutf8", + "thiserror 2.0.12", + "time", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cross-krb5" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d4ddf7139e64dc916b11d434421031bcc5ba02e521a49a011652a0f68775188" +dependencies = [ + "anyhow", + "bitflags 2.9.0", + "bytes", + "libgssapi", + "windows", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.101", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "derive-where" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.101", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "function_name" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef632c665dc6e2b99ffa4d913f7160bd902c4d3e4cb732d81dc3d221f848512" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "569d2238870f92cff64fc810013b61edaf446ebcfba36b649b96bc5b4078328a" +dependencies = [ + "proc-macro-crate", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.9.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.8.5", + "thiserror 1.0.69", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand 0.8.5", + "resolv-conf", + "smallvec 1.15.0", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec 1.15.0", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.0", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.15.0", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec 1.15.0", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown 0.15.3", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lambda_runtime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a81840726d481d20b99a9ce87430f644e9590cb77715e1e66c5f4432c9b586" +dependencies = [ + "async-stream", + "bytes", + "futures", + "http 0.2.12", + "hyper 0.14.32", + "lambda_runtime_api_client", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.4.13", + "tracing", +] + +[[package]] +name = "lambda_runtime_api_client" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54698c666ffe503cb51fa66e4567e53e806128a10359de7095999d925a771ed" +dependencies = [ + "http 0.2.12", + "hyper 0.14.32", + "tokio", + "tower-service", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libgssapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834339e86b2561169d45d3b01741967fee3e5716c7d0b6e33cd4e3b34c9558cd" +dependencies = [ + "bitflags 2.9.0", + "bytes", + "lazy_static", + "libgssapi-sys", +] + +[[package]] +name = "libgssapi-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7518e6902e94f92e7c7271232684b60988b4bd813529b4ef9d97aead96956ae8" +dependencies = [ + "bindgen", + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.48.5", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "mongocrypt" +version = "0.3.1" +source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#66c4ee29a2184c26ff5d7b290a23b5fdcf9c7d26" +dependencies = [ + "bson 2.15.0", + "bson 3.0.0", + "mongocrypt-sys", + "once_cell", + "serde", +] + +[[package]] +name = "mongocrypt-sys" +version = "0.1.4+1.12.0" +source = "git+https://github.com/mongodb/libmongocrypt-rust.git?branch=main#66c4ee29a2184c26ff5d7b290a23b5fdcf9c7d26" + +[[package]] +name = "mongodb" +version = "3.2.3" +dependencies = [ + "anyhow", + "approx", + "async-trait", + "backtrace", + "base64 0.13.1", + "bitflags 1.3.2", + "bson 2.15.0", + "bson 3.0.0", + "chrono", + "cross-krb5", + "ctrlc", + "derive-where", + "derive_more", + "flate2", + "function_name", + "futures", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hickory-proto", + "hickory-resolver", + "hmac", + "home", + "lambda_runtime", + "log", + "macro_magic", + "md-5", + "mongocrypt", + "mongodb-internal-macros", + "num_cpus", + "once_cell", + "openssl", + "openssl-probe", + "pbkdf2 0.11.0", + "pem", + "percent-encoding", + "pkcs8", + "pretty_assertions", + "rand 0.8.5", + "rayon", + "regex", + "reqwest", + "rustc_version_runtime", + "rustls", + "rustversion", + "semver", + "serde", + "serde-hex", + "serde_bytes", + "serde_json", + "serde_path_to_error", + "serde_with", + "sha1", + "sha2", + "snap", + "socket2", + "stringprep", + "strsim", + "take_mut", + "thiserror 1.0.69", + "time", + "tokio", + "tokio-openssl", + "tokio-rustls", + "tokio-util", + "tracing", + "tracing-subscriber", + "typed-builder", + "uuid", + "webpki-roots 0.26.11", + "zstd", +] + +[[package]] +name = "mongodb-internal-macros" +version = "3.2.3" +dependencies = [ + "macro_magic", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec 1.15.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs5" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6" +dependencies = [ + "aes", + "cbc", + "der", + "des", + "pbkdf2 0.12.2", + "scrypt", + "sha1", + "sha2", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "pkcs5", + "rand_core 0.6.4", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn 2.0.101", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.2", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower 0.5.2", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.0", +] + +[[package]] +name = "resolv-conf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2 0.12.2", + "salsa20", + "sha2", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-hex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +dependencies = [ + "array-init", + "serde", + "smallvec 0.6.14", +] + +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "indexmap 2.9.0", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.9.0", + "schemars 0.9.0", + "schemars 1.0.4", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" +dependencies = [ + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.0", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec 1.15.0", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.2", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.0", +] + +[[package]] +name = "webpki-roots" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index cefff938b..263c7fc5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = [ "Isabel Atkinson ", "Abraham Egnor ", "Kaitlin Mahar ", + "Patrick Meredith ", ] description = "The official MongoDB driver for Rust" edition = "2021" @@ -15,7 +16,8 @@ homepage = "https://www.mongodb.com/docs/drivers/rust/" license = "Apache-2.0" readme = "README.md" name = "mongodb" -version = "2.5.0" +version = "3.2.3" +rust-version = "1.82" exclude = [ "etc/**", @@ -26,116 +28,120 @@ exclude = [ "tests/**", ] -# NOTE: any new features added to this list should also be added to the features -# list in the [package.metadata.docs.rs] section below. [features] -default = ["tokio-runtime"] -tokio-runtime = [ - "tokio/macros", - "tokio/net", - "tokio/process", - "tokio/rt", - "tokio/time", - "serde_bytes", -] -async-std-runtime = [ - "async-std", - "async-std/attributes", - "async-std/unstable", - "async-std-resolver", - "tokio-util/compat", -] -sync = ["async-std-runtime"] -tokio-sync = ["tokio-runtime"] -openssl-tls = ["openssl", "openssl-probe", "tokio-openssl"] - -# Enable support for v0.4 of the chrono crate in the public API of the BSON library. -bson-chrono-0_4 = ["bson/chrono-0_4"] - -# Enable support for the serde_with crate in the BSON library. -bson-serde_with = ["bson/serde_with"] - -# Enable support for v0.8 of the uuid crate in the public API of the BSON library. -bson-uuid-0_8 = ["bson/uuid-0_8"] - -# Enable support for v1.x of the uuid crate in the public API of the BSON library. -bson-uuid-1 = ["bson/uuid-1"] +default = ["compat-3-0-0", "rustls-tls", "dns-resolver"] +compat-3-0-0 = ["compat-3-3-0", "bson-2"] +compat-3-3-0 = [] +bson-2 = ["dep:bson2", "mongocrypt/bson-2"] +bson-3 = ["dep:bson3", "mongocrypt/bson-3"] +sync = [] +rustls-tls = ["dep:rustls", "dep:tokio-rustls"] +openssl-tls = ["dep:openssl", "dep:openssl-probe", "dep:tokio-openssl"] +dns-resolver = ["dep:hickory-resolver", "dep:hickory-proto"] +cert-key-password = ["dep:pem", "dep:pkcs8"] # Enable support for MONGODB-AWS authentication. -# This can only be used with the tokio-runtime feature flag. -aws-auth = ["reqwest"] +aws-auth = ["dep:reqwest"] # Enable support for on-demand Azure KMS credentials. -# This can only be used with the tokio-runtime feature flag. -azure-kms = ["reqwest"] +azure-kms = ["dep:reqwest"] + +# Enable support for azure OIDC authentication. +azure-oidc = ["dep:reqwest"] + +# Enable support for gcp OIDC authentication. +gcp-oidc = ["dep:reqwest"] # Enable support for on-demand GCP KMS credentials. -# This can only be used with the tokio-runtime feature flag. -gcp-kms = ["reqwest"] +gcp-kms = ["dep:reqwest"] + +# Enable support for GSSAPI (Kerberos) authentication. +gssapi-auth = ["dep:cross-krb5", "dns-resolver"] -zstd-compression = ["zstd"] -zlib-compression = ["flate2"] -snappy-compression = ["snap"] +zstd-compression = ["dep:zstd"] +zlib-compression = ["dep:flate2"] +snappy-compression = ["dep:snap"] # Enables support for client-side field level encryption and queryable encryption. -# The In Use Encryption API is unstable and may have backwards-incompatible changes in minor version updates. -in-use-encryption-unstable = ["mongocrypt", "rayon", "num_cpus"] +in-use-encryption = ["dep:mongocrypt", "dep:rayon", "dep:num_cpus"] +# The in-use encryption API is stable; this is for backwards compatibility. +in-use-encryption-unstable = ["in-use-encryption"] # Enables support for emitting tracing events. # The tracing API is unstable and may have backwards-incompatible changes in minor version updates. # TODO: pending https://github.com/tokio-rs/tracing/issues/2036 stop depending directly on log. -tracing-unstable = ["tracing", "log"] +tracing-unstable = ["dep:tracing", "dep:log", "bson3?/serde_json-1"] [dependencies] async-trait = "0.1.42" base64 = "0.13.0" bitflags = "1.1.0" -bson = { git = "https://github.com/mongodb/bson-rust", branch = "main" } -chrono = { version = "0.4.7", default-features = false, features = ["clock", "std"] } -derivative = "2.1.1" +chrono = { version = "0.4.7", default-features = false, features = [ + "clock", + "std", +] } +cross-krb5 = { version = "0.4.2", optional = true, default-features = false } derive_more = "0.99.17" +derive-where = "1.2.7" flate2 = { version = "1.0", optional = true } futures-io = "0.3.21" futures-core = "0.3.14" futures-util = { version = "0.3.14", features = ["io"] } futures-executor = "0.3.14" hex = "0.4.0" +hickory-proto = { version = "0.24.2", optional = true } +hickory-resolver = { version = "0.24.2", optional = true } hmac = "0.12.1" -lazy_static = "1.4.0" +once_cell = "1.19.0" log = { version = "0.4.17", optional = true } md-5 = "0.10.1" -mongocrypt = { git = "https://github.com/mongodb/libmongocrypt-rust.git", branch = "main", optional = true } +mongodb-internal-macros = { path = "macros", version = "3.2.3" } num_cpus = { version = "1.13.1", optional = true } openssl = { version = "0.10.38", optional = true } openssl-probe = { version = "0.1.5", optional = true } +pem = { version = "3.0.4", optional = true } percent-encoding = "2.0.0" +pkcs8 = { version = "0.10.2", features = ["encryption", "pkcs5"], optional = true } rand = { version = "0.8.3", features = ["small_rng"] } rayon = { version = "1.5.3", optional = true } -rustc_version_runtime = "0.2.1" -rustls-pemfile = "1.0.1" -serde_with = "1.3.1" -sha-1 = "0.10.0" +rustc_version_runtime = "0.3.0" +serde_with = "3.8.1" +sha1 = "0.10.0" sha2 = "0.10.2" snap = { version = "1.0.5", optional = true } -socket2 = "0.4.0" +socket2 = "0.5.5" stringprep = "0.1.2" -strsim = "0.10.0" +strsim = "0.11.1" take_mut = "0.2.2" thiserror = "1.0.24" tokio-openssl = { version = "0.6.3", optional = true } tracing = { version = "0.1.36", optional = true } -trust-dns-proto = "0.21.2" -trust-dns-resolver = "0.21.2" -typed-builder = "0.10.0" -webpki-roots = "0.22.4" +typed-builder = "0.20.0" +webpki-roots = "0.26" zstd = { version = "0.11.2", optional = true } +macro_magic = "0.5.1" +rustversion = "1.0.20" + +[dependencies.bson2] +git = "https://github.com/mongodb/bson-rust" +branch = "2.15.x" +package = "bson" +version = "2.15.0" +optional = true -[dependencies.async-std] -version = "1.9.0" +[dependencies.bson3] +git = "https://github.com/mongodb/bson-rust" +branch = "main" +package = "bson" +version = "3.0.0" optional = true +features = ["serde"] -[dependencies.async-std-resolver] -version = "0.21.1" +[dependencies.mongocrypt] +git = "https://github.com/mongodb/libmongocrypt-rust.git" +branch = "main" +version = "0.3.1" +default-features = false optional = true [dependencies.pbkdf2] @@ -143,14 +149,16 @@ version = "0.11.0" default-features = false [dependencies.reqwest] -version = "0.11.2" +version = "0.12.12" optional = true default-features = false features = ["json", "rustls-tls"] [dependencies.rustls] -version = "0.20.4" -features = ["dangerous_configuration"] +version = "0.23.20" +optional = true +default-features = false +features = ["logging", "ring", "std", "tls12"] [dependencies.serde] version = "1.0.125" @@ -158,15 +166,16 @@ features = ["derive"] [dependencies.serde_bytes] version = "0.11.5" -optional = true [dependencies.tokio] version = "1.17.0" -features = ["io-util", "sync", "macros"] +features = ["io-util", "sync", "macros", "net", "process", "rt", "time", "fs"] [dependencies.tokio-rustls] -version = "0.23.2" -features = ["dangerous_configuration"] +version = "0.26" +optional = true +default-features = false +features = ["logging", "ring", "tls12"] [dependencies.tokio-util] version = "0.7.0" @@ -179,33 +188,38 @@ features = ["v4"] [dev-dependencies] anyhow = { version = "1.0", features = ["backtrace"] } approx = "0.5.1" -async_once = "0.2.6" +backtrace = { version = "0.3.68" } ctrlc = "3.2.2" function_name = "0.2.1" futures = "0.3" hex = "0.4" home = "0.5" lambda_runtime = "0.6.0" +pkcs8 = { version = "0.10.2", features = ["3des", "des-insecure", "sha1-insecure"] } pretty_assertions = "1.3.0" serde = { version = ">= 0.0.0", features = ["rc"] } serde_json = "1.0.64" semver = "1.0.0" time = "0.3.9" +tokio = { version = ">= 0.0.0", features = ["fs", "parking_lot"] } tracing-subscriber = "0.3.16" regex = "1.6.0" +reqwest = { version = "0.12.2", features = ["rustls-tls"] } +serde-hex = "0.1.0" +serde_path_to_error = "0.1" + +[dev-dependencies.bson3] +git = "https://github.com/mongodb/bson-rust" +branch = "main" +package = "bson" +version = "3.0.0" +features = ["serde", "serde_json-1"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] -# async-std-runtime and sync are excluded here because they conflict with the default features. -# Neither feature has any unique documentation associated with it, so we do not need to -# include them in our documentation build. -features = [ - "tokio-sync", - "zstd-compression", - "snappy-compression", - "zlib-compression", - "openssl-tls", - "aws-auth", - "tracing-unstable", - "in-use-encryption-unstable" -] \ No newline at end of file +all-features = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(mongodb_internal_tracking_arc)', +] } diff --git a/README.md b/README.md index 5f498e6d8..9248ce8ad 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,74 @@ # MongoDB Rust Driver -[![Crates.io](https://img.shields.io/crates/v/mongodb.svg)](https://crates.io/crates/mongodb) [![docs.rs](https://docs.rs/mongodb/badge.svg)](https://docs.rs/mongodb) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) - -This repository contains the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the [`bson`](https://docs.rs/bson/latest) crate for BSON support. The driver contains a fully async API that supports either [`tokio`](https://crates.io/crates/tokio) (default) or [`async-std`](https://crates.io/crates/async-std), depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag. - -For more detailed documentation, see [the manual](https://mongodb.github.io/mongo-rust-driver/manual/). - -## Index -- [Installation](#installation) - - [Requirements](#requirements) - - [Supported platforms](#supported-platforms) - - [Importing](#importing) - - [Configuring the async runtime](#configuring-the-async-runtime) - - [Enabling the sync API](#enabling-the-sync-api) - - [All feature flags](#all-feature-flags) -- [Example Usage](#example-usage) - - [Using the async API](#using-the-async-api) - - [Connecting to a MongoDB deployment](#connecting-to-a-mongodb-deployment) - - [Getting a handle to a database](#getting-a-handle-to-a-database) - - [Inserting documents into a collection](#inserting-documents-into-a-collection) - - [Finding documents in a collection](#finding-documents-in-a-collection) - - [Using the sync API](#using-the-sync-api) -- [Web Framework Examples](#web-framework-examples) -- [Note on connecting to Atlas deployments](#note-on-connecting-to-atlas-deployments) -- [Windows DNS note](#windows-dns-note) -- [Warning about timeouts / cancellation](#warning-about-timeouts--cancellation) -- [Bug Reporting / Feature Requests](#bug-reporting--feature-requests) -- [Contributing](#contributing) -- [Running the tests](#running-the-tests) -- [Continuous Integration](#continuous-integration) -- [Minimum supported Rust version (MSRV) policy](#minimum-supported-rust-version-msrv-policy) -- [License](#license) + +[![Crates.io](https://img.shields.io/crates/v/mongodb.svg)](https://crates.io/crates/mongodb) [![docs.rs](https://docs.rs/mongodb/badge.svg)](https://docs.rs/mongodb) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/mongodb/mongo-rust-driver/blob/main/LICENSE) + +This is the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the [`bson`](https://docs.rs/bson/latest) crate for BSON support. The driver contains a fully async API that requires [`tokio`](https://docs.rs/tokio). The driver also has a sync API that may be enabled via feature flags. The MongoDB Rust driver follows [semantic versioning](https://semver.org/) for its releases. + +For more details, including features, runnable examples, troubleshooting resources, and more, please see the [official documentation](https://www.mongodb.com/docs/drivers/rust/current/). ## Installation + ### Requirements -- Rust 1.57+ (See the [MSRV policy](#minimum-supported-rust-version-msrv-policy) for more information) -- MongoDB 3.6+ + +- Rust 1.82.0+ (See the [MSRV policy](#minimum-supported-rust-version-msrv-policy) for more information) +- MongoDB 4.0+ #### Supported Platforms The driver tests against Linux, MacOS, and Windows in CI. ### Importing + The driver is available on [crates.io](https://crates.io/crates/mongodb). To use the driver in your application, simply add it to your project's `Cargo.toml`. + ```toml [dependencies] -mongodb = "2.5.0" +mongodb = "3.2.3" ``` Version 1 of this crate has reached end of life and will no longer be receiving any updates or bug fixes, so all users are recommended to always depend on the latest 2.x release. See the [2.0.0 release notes](https://github.com/mongodb/mongo-rust-driver/releases/tag/v2.0.0) for migration information if upgrading from a 1.x version. -#### Configuring the async runtime -The driver supports both of the most popular async runtime crates, namely [`tokio`](https://crates.io/crates/tokio) and [`async-std`](https://crates.io/crates/async-std). By default, the driver will use [`tokio`](https://crates.io/crates/tokio), but you can explicitly choose a runtime by specifying one of `"tokio-runtime"` or `"async-std-runtime"` feature flags in your `Cargo.toml`. +#### Enabling the sync API -For example, to instruct the driver to work with [`async-std`](https://crates.io/crates/async-std), add the following to your `Cargo.toml`: -```toml -[dependencies.mongodb] -version = "2.5.0" -default-features = false -features = ["async-std-runtime"] -``` +The driver also provides a blocking sync API. To enable this, add the `"sync"` feature to your `Cargo.toml`: -#### Enabling the sync API -The driver also provides a blocking sync API. To enable this, add the `"sync"` or `"tokio-sync"` feature to your `Cargo.toml`: ```toml [dependencies.mongodb] -version = "2.5.0" -features = ["tokio-sync"] +version = "3.2.3" +features = ["sync"] ``` -Using the `"sync"` feature also requires using `default-features = false`. + **Note:** The sync-specific types can be imported from `mongodb::sync` (e.g. `mongodb::sync::Client`). ### All Feature Flags -| Feature | Description | Extra dependencies | Default | -|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------|:--------| -| `tokio-runtime` | Enable support for the `tokio` async runtime | `tokio` 1.0 with the `full` feature | yes | -| `async-std-runtime` | Enable support for the `async-std` runtime | `async-std` 1.0 | no | -| `sync` | Expose the synchronous API (`mongodb::sync`). This flag cannot be used in conjunction with either of the async runtime feature flags. | `async-std` 1.0 | no | -| `aws-auth` | Enable support for the MONGODB-AWS authentication mechanism. | `reqwest` 0.11 | no | -| `bson-uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-uuid-1` | Enable support for v1.x of the [`uuid`](docs.rs/uuid/1.0) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-serde_with` | Enable support for the [`serde_with`](docs.rs/serde_with/latest) crate in the public API of the re-exported `bson` crate. | `serde_with` 1.0 | no | -| `zlib-compression` | Enable support for compressing messages with [`zlib`](https://zlib.net/) | `flate2` 1.0 | no | -| `zstd-compression` | Enable support for compressing messages with [`zstd`](http://facebook.github.io/zstd/). This flag requires Rust version 1.54. | `zstd` 0.9.0 | no | -| `snappy-compression` | Enable support for compressing messages with [`snappy`](http://google.github.io/snappy/) | `snap` 1.0.5 | no | -| `openssl-tls` | Switch TLS connection handling to use ['openssl'](https://docs.rs/openssl/0.10.38/). | `openssl` 0.10.38 | no | - -## Example Usage -Below are simple examples of using the driver. For more specific examples and the API reference, see the driver's [docs.rs page](https://docs.rs/mongodb/latest). - -### Using the async API -#### Connecting to a MongoDB deployment -```rust -use mongodb::{Client, options::ClientOptions}; -``` -```rust -// Parse a connection string into an options struct. -let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?; - -// Manually set an option. -client_options.app_name = Some("My App".to_string()); +| Feature | Description | +| :------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dns-resolver` | Enable DNS resolution to allow `mongodb+srv` URI handling. **Enabled by default.** | +| `rustls-tls` | Use [`rustls`](https://docs.rs/rustls/latest/rustls/) for TLS connection handling. **Enabled by default.** | +| `openssl-tls` | Use [`openssl`](https://docs.rs/openssl/latest/openssl/) for TLS connection handling. | +| `sync` | Expose the synchronous API (`mongodb::sync`). | +| `aws-auth` | Enable support for the MONGODB-AWS authentication mechanism. | +| `zlib-compression` | Enable support for compressing messages with [`zlib`](https://zlib.net/). | +| `zstd-compression` | Enable support for compressing messages with [`zstd`](http://facebook.github.io/zstd/). | +| `snappy-compression` | Enable support for compressing messages with [`snappy`](http://google.github.io/snappy/). | +| `in-use-encryption` | Enable support for client-side field level encryption and queryable encryption. Note that re-exports from the `mongocrypt` crate may change in backwards-incompatible ways while that crate is below version 1.0. | +| `tracing-unstable` | Enable support for emitting [`tracing`](https://docs.rs/tracing/latest/tracing/) events. This API is unstable and may be subject to breaking changes in minor releases. | +| `compat-3-0-0` | Required for future compatibility if default features are disabled. | -// Get a handle to the deployment. -let client = Client::with_options(client_options)?; - -// List the names of the databases in that deployment. -for db_name in client.list_database_names(None, None).await? { - println!("{}", db_name); -} -``` -#### Getting a handle to a database -```rust -// Get a handle to a database. -let db = client.database("mydb"); - -// List the names of the collections in that database. -for collection_name in db.list_collection_names(None).await? { - println!("{}", collection_name); -} -``` -#### Inserting documents into a collection -```rust -use mongodb::bson::{doc, Document}; -``` -```rust -// Get a handle to a collection in the database. -let collection = db.collection::("books"); - -let docs = vec![ - doc! { "title": "1984", "author": "George Orwell" }, - doc! { "title": "Animal Farm", "author": "George Orwell" }, - doc! { "title": "The Great Gatsby", "author": "F. Scott Fitzgerald" }, -]; - -// Insert some documents into the "mydb.books" collection. -collection.insert_many(docs, None).await?; -``` - -A [`Collection`](https://docs.rs/mongodb/latest/mongodb/struct.Collection.html) can be parameterized with any type that implements the `Serialize` and `Deserialize` traits from the [`serde`](https://serde.rs/) crate, not just `Document`: - -``` toml -# In Cargo.toml, add the following dependency. -serde = { version = "1.0", features = ["derive"] } -``` - -``` rust -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -struct Book { - title: String, - author: String, -} -``` - -``` rust -// Get a handle to a collection of `Book`. -let typed_collection = db.collection::("books"); - -let books = vec![ - Book { - title: "The Grapes of Wrath".to_string(), - author: "John Steinbeck".to_string(), - }, - Book { - title: "To Kill a Mockingbird".to_string(), - author: "Harper Lee".to_string(), - }, -]; - -// Insert the books into "mydb.books" collection, no manual conversion to BSON necessary. -typed_collection.insert_many(books, None).await?; -``` +## Web Framework Examples -#### Finding documents in a collection -Results from queries are generally returned via [`Cursor`](https://docs.rs/mongodb/latest/mongodb/struct.Cursor.html), a struct which streams the results back from the server as requested. The [`Cursor`](https://docs.rs/mongodb/latest/mongodb/struct.Cursor.html) type implements the [`Stream`](https://docs.rs/futures/latest/futures/stream/index.html) trait from the [`futures`](https://crates.io/crates/futures) crate, and in order to access its streaming functionality you need to import at least one of the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) or [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html) traits. +### Actix -``` toml -# In Cargo.toml, add the following dependency. -futures = "0.3" -``` -```rust -// This trait is required to use `try_next()` on the cursor -use futures::stream::TryStreamExt; -use mongodb::{bson::doc, options::FindOptions}; -``` -```rust -// Query the books in the collection with a filter and an option. -let filter = doc! { "author": "George Orwell" }; -let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build(); -let mut cursor = typed_collection.find(filter, find_options).await?; - -// Iterate over the results of the cursor. -while let Some(book) = cursor.try_next().await? { - println!("title: {}", book.title); -} -``` +The driver can be used easily with the Actix web framework by storing a `Client` in Actix application data. A full example application for using MongoDB with Actix can be found [here](https://github.com/actix/examples/tree/master/databases/mongodb). -### Using the sync API -The driver also provides a blocking sync API. See the [Installation](#enabling-the-sync-api) section for instructions on how to enable it. - -The various sync-specific types are found in the `mongodb::sync` submodule rather than in the crate's top level like in the async API. The sync API calls through to the async API internally though, so it looks and behaves similarly to it. -```rust -use mongodb::{ - bson::doc, - sync::Client, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -struct Book { - title: String, - author: String, -} -``` -```rust -let client = Client::with_uri_str("mongodb://localhost:27017")?; -let database = client.database("mydb"); -let collection = database.collection::("books"); - -let docs = vec![ - Book { - title: "1984".to_string(), - author: "George Orwell".to_string(), - }, - Book { - title: "Animal Farm".to_string(), - author: "George Orwell".to_string(), - }, - Book { - title: "The Great Gatsby".to_string(), - author: "F. Scott Fitzgerald".to_string(), - }, -]; - -// Insert some books into the "mydb.books" collection. -collection.insert_many(docs, None)?; - -let cursor = collection.find(doc! { "author": "George Orwell" }, None)?; -for result in cursor { - println!("title: {}", result?.title); -} -``` +### Axum -## Web Framework Examples -### Actix -The driver can be used easily with the Actix web framework by storing a `Client` in Actix application data. A full example application for using MongoDB with Actix can be found [here](https://github.com/actix/examples/tree/master/databases/mongodb). +A simple CRUD API example using Axum and MongoDB can be found [here](https://github.com/tokio-rs/axum/tree/main/examples/mongodb). ### Rocket -The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the [`rocket_db_pools`](https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html) crate contains instructions for using MongoDB with your Rocket application. + +The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the [`rocket_db_pools`](https://api.rocket.rs/v0.5/rocket_db_pools/index.html) crate contains instructions for using MongoDB with your Rocket application. ## Note on connecting to Atlas deployments @@ -257,24 +76,7 @@ In order to connect to a pre-4.2 Atlas instance that's M2 or bigger, the `openss ## Windows DNS note -On Windows, there is a known issue in the `trust-dns-resolver` crate, which the driver uses to perform DNS lookups, that causes severe performance degradation in resolvers that use the system configuration. Since the driver uses the system configuration by default, users are recommended to specify an alternate resolver configuration on Windows until that issue is resolved. This only has an effect when connecting to deployments using a `mongodb+srv` connection string. - -e.g. - -``` rust -use mongodb::{ - options::{ClientOptions, ResolverConfig}, - Client, -}; -``` -``` rust -let options = ClientOptions::parse_with_resolver_config( - "mongodb+srv://my.host.com", - ResolverConfig::cloudflare(), -) -.await?; -let client = Client::with_options(options)?; -``` +On Windows, there is a known issue in the `trust-dns-resolver` crate, which the driver uses to perform DNS lookups, that causes severe performance degradation in resolvers that use the system configuration. Since the driver uses the system configuration by default, users are recommended to specify an alternate resolver configuration on Windows (e.g. `ResolverConfig::cloudflare()`) until that issue is resolved. This only has an effect when connecting to deployments using a `mongodb+srv` connection string. ## Warning about timeouts / cancellation @@ -282,7 +84,7 @@ In async Rust, it is common to implement cancellation and timeouts by dropping a certain period of time instead of polling it to completion. This is how [`tokio::time::timeout`](https://docs.rs/tokio/1.10.1/tokio/time/fn.timeout.html) works, for example. However, doing this with futures returned by the driver can leave the driver's internals in -an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more +an inconsistent state, which may lead to unpredictable or incorrect behavior (see [RUST-937](https://jira.mongodb.org/browse/RUST-937) for more details). As such, it is **_highly_** recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like `tokio::time::timeout` with the driver, one option is to spawn tasks and time out on their @@ -290,18 +92,10 @@ one option is to spawn tasks and time out on their the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout. -e.g. -``` rust -let collection = client.database("ok").collection("ok"); -let handle = tokio::task::spawn(async move { - collection.insert_one(doc! { "x": 1 }, None).await -}); - -tokio::time::timeout(Duration::from_secs(5), handle).await???; -``` - ## Bug Reporting / Feature Requests + To file a bug report or submit a feature request, please open a ticket on our [Jira project](https://jira.mongodb.org/browse/RUST): + - Create an account and login at [jira.mongodb.org](https://jira.mongodb.org) - Navigate to the RUST project at [jira.mongodb.org/browse/RUST](https://jira.mongodb.org/browse/RUST) - Click **Create Issue** - If the ticket you are filing is a bug report, please include as much detail as possible about the issue and how to reproduce it. @@ -313,12 +107,15 @@ Before filing a ticket, please use the search functionality of Jira to see if a We encourage and would happily accept contributions in the form of GitHub pull requests. Before opening one, be sure to run the tests locally; check out the [testing section](#running-the-tests) for information on how to do that. Once you open a pull request, your branch will be run against the same testing matrix that we use for our [continuous integration](#continuous-integration) system, so it is usually sufficient to only run the integration tests locally against a standalone. Remember to always run the linter tests before opening a pull request. ## Running the tests + ### Integration and unit tests + In order to run the tests (which are mostly integration tests), you must have access to a MongoDB deployment. You may specify a [MongoDB connection string](https://www.mongodb.com/docs/manual/reference/connection-string/) in the `MONGODB_URI` environment variable, and the tests will use it to connect to the deployment. If `MONGODB_URI` is unset, the tests will attempt to connect to a local deployment on port 27017. **Note:** The integration tests will clear out the databases/collections they need to use, but they do not clean up after themselves. To actually run the tests, you can use `cargo` like you would in any other crate: + ```bash cargo test --verbose # runs against localhost:27017 export MONGODB_URI="mongodb://localhost:123" @@ -326,16 +123,22 @@ cargo test --verbose # runs against localhost:123 ``` #### Auth tests + The authentication tests will only be included in the test run if certain requirements are met: + - The deployment must have `--auth` enabled - Credentials must be specified in `MONGODB_URI` - The credentials specified in `MONGODB_URI` must be valid and have root privileges on the deployment + ```bash export MONGODB_URI="mongodb://user:pass@localhost:27017" cargo test --verbose # auth tests included ``` + #### Topology-specific tests + Certain tests will only be run against certain topologies. To ensure that the entire test suite is run, make sure to run the tests separately against standalone, replicated, and sharded deployments. + ```bash export MONGODB_URI="mongodb://my-standalone-host:27017" # mongod running on 27017 cargo test --verbose @@ -346,37 +149,44 @@ cargo test --verbose ``` #### Run the tests with TLS/SSL + To run the tests with TLS/SSL enabled, you must enable it on the deployment and in `MONGODB_URI`. + ```bash export MONGODB_URI="mongodb://localhost:27017/?tls=true&tlsCertificateKeyFile=cert.pem&tlsCAFile=ca.pem" cargo test --verbose ``` + **Note:** When you open a pull request, your code will be run against a comprehensive testing matrix, so it is usually not necessary to run the integration tests against all combinations of topology/auth/TLS locally. ### Linter Tests + Our linter tests use the nightly version of `rustfmt` to verify that the source is formatted properly and the stable version of `clippy` to statically detect any common mistakes. You can use `rustup` to install them both: + ```bash rustup component add clippy --toolchain stable rustup component add rustfmt --toolchain nightly ``` + Our linter tests also use `rustdoc` to verify that all necessary documentation is present and properly formatted. `rustdoc` is included in the standard Rust distribution. To run the linter tests, run the `check-clippy.sh`, `check-rustfmt.sh`, and `check-rustdoc.sh` scripts in the `.evergreen` directory. To run all three, use the `check-all.sh` script. + ```bash bash .evergreen/check-all.sh ``` ## Continuous Integration + Commits to main are run automatically on [evergreen](https://evergreen.mongodb.com/waterfall/mongo-rust-driver). ## Minimum supported Rust version (MSRV) policy -The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, -it will only happen in a minor or major version release. +The MSRV for this crate is currently 1.82.0. Increases to the MSRV will only happen in a minor or major version release, and will be to a Rust version at least six months old. ## License This project is licensed under the [Apache License 2.0](https://github.com/10gen/mongo-rust-driver/blob/main/LICENSE). -This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/). +This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (). diff --git a/benchmarks/Cargo.lock b/benchmarks/Cargo.lock new file mode 100644 index 000000000..404b076f2 --- /dev/null +++ b/benchmarks/Cargo.lock @@ -0,0 +1,2394 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "async-trait" +version = "0.1.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bson" +version = "2.13.0" +source = "git+https://github.com/mongodb/bson-rust?branch=main#098e1a17e34cbe7f28aac87609eeea2a191b93a3" +dependencies = [ + "ahash", + "base64 0.13.1", + "bitvec", + "hex", + "indexmap 2.7.1", + "js-sys", + "once_cell", + "rand", + "serde", + "serde_bytes", + "serde_json", + "time", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" + +[[package]] +name = "cc" +version = "1.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width 0.1.14", + "vec_map", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.96", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "data-encoding" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.96", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hickory-proto" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447afdcdb8afb9d0a852af6dc65d9b285ce720ed7a59e42a8bf2e931c67bc1b5" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand", + "thiserror", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "lru-cache", + "once_cell", + "parking_lot", + "rand", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown 0.15.2", + "serde", +] + +[[package]] +name = "indicatif" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7baab56125e25686df467fe470785512329883aab42696d661247aca2a2896e4" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mongodb" +version = "3.2.0" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bitflags 1.3.2", + "bson", + "chrono", + "derive-where", + "derive_more", + "futures-core", + "futures-executor", + "futures-io", + "futures-util", + "hex", + "hickory-proto", + "hickory-resolver", + "hmac", + "macro_magic", + "md-5", + "mongodb-internal-macros", + "once_cell", + "pbkdf2", + "percent-encoding", + "rand", + "rustc_version_runtime", + "rustls", + "rustls-pemfile", + "serde", + "serde_bytes", + "serde_with", + "sha-1", + "sha2", + "socket2", + "stringprep", + "strsim 0.11.1", + "take_mut", + "thiserror", + "tokio", + "tokio-rustls", + "tokio-util", + "typed-builder", + "uuid", + "webpki-roots", +] + +[[package]] +name = "mongodb-internal-macros" +version = "3.2.0" +dependencies = [ + "macro_magic", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "number_prefix" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.8.0", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rust-driver-bench" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "futures", + "futures-util", + "indicatif", + "mongodb", + "num_enum", + "once_cell", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tokio-util", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustc_version_runtime" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd18cd2bae1820af0b6ad5e54f4a51d0f3fcc53b05f845675074efcc7af071d" +dependencies = [ + "rustc_version", + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" +dependencies = [ + "indexmap 2.7.1", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.7.1", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "sha-1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "time" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.7.1", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typed-builder" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 69ee831fb..ad00100e5 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -4,31 +4,18 @@ version = "0.1.0" authors = ["benjirewis "] edition = "2018" -[features] -default = ["tokio-runtime"] -tokio-runtime = [ - "tokio/fs", - "tokio/macros", - "tokio/rt", - "tokio/rt-multi-thread", - "tokio-stream", - "mongodb/tokio-runtime" -] -async-std-runtime = ["async-std", "mongodb/async-std-runtime"] - [dependencies] -mongodb = { path = "..", default-features = false } +mongodb = { path = ".." } serde_json = "1.0.59" -lazy_static = "1.4.0" +once_cell = "1.19.0" clap = "2.33.3" indicatif = "0.15.0" async-trait = "0.1.41" -tokio = { version = "1.6", features = ["sync"] } +tokio = { version = "1.6", features = ["sync", "fs"] } tokio-util = "0.7" -tokio-stream = { version = "0.1.6", features = ["io-util"], optional = true } -# "unstable" feature is needed for `spawn_blocking`, which is only used in task setup -async-std = { version = "1.9.0", optional = true, features = ["attributes", "unstable"] } +tokio-stream = { version = "0.1.6", features = ["io-util"] } futures = "0.3.8" anyhow = "1.0.34" serde = "1" num_enum = "0.5" +futures-util = "0.3.30" diff --git a/benchmarks/README.md b/benchmarks/README.md index 218764dbb..5fbb54423 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,6 +1,6 @@ # Rust Driver Benchmark Suite -This suite implements the benchmarks described in this (spec)[https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.rst]. +This suite implements the benchmarks described in this (spec)[https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.md]. In order to run the microbenchmarks, first run `./download-data.sh`. (NOTE: the data for the deeply nested BSON encoding and decoding is currently broken, so these benchmarks will not be runnable until that's fixed). diff --git a/benchmarks/src/bench.rs b/benchmarks/src/bench.rs new file mode 100644 index 000000000..f597873f2 --- /dev/null +++ b/benchmarks/src/bench.rs @@ -0,0 +1,178 @@ +pub mod bson_decode; +pub mod bson_encode; +pub mod bulk_write; +pub mod find_many; +pub mod find_one; +pub mod gridfs_download; +pub mod gridfs_multi_download; +pub mod gridfs_multi_upload; +pub mod gridfs_upload; +pub mod insert_many; +pub mod insert_one; +pub mod json_multi_export; +pub mod json_multi_import; +pub mod run_command; + +use std::{ + convert::TryInto, + sync::Arc, + time::{Duration, Instant}, +}; + +use anyhow::{bail, Result}; +use futures::stream::TryStreamExt; +use indicatif::{ProgressBar, ProgressStyle}; +use mongodb::{ + bson::{doc, Bson, Document}, + options::{Acknowledgment, ClientOptions, SelectionCriteria, WriteConcern}, + Client, +}; +use once_cell::sync::Lazy; +use serde_json::Value; + +use crate::fs::{BufReader, File}; + +static DATABASE_NAME: Lazy = Lazy::new(|| { + option_env!("DATABASE_NAME") + .unwrap_or("perftest") + .to_string() +}); +static COLL_NAME: Lazy = + Lazy::new(|| option_env!("COLL_NAME").unwrap_or("corpus").to_string()); +static MAX_EXECUTION_TIME: Lazy = Lazy::new(|| { + option_env!("MAX_EXECUTION_TIME") + .unwrap_or("300") + .parse::() + .expect("invalid MAX_EXECUTION_TIME") +}); +static MIN_EXECUTION_TIME: Lazy = Lazy::new(|| { + option_env!("MIN_EXECUTION_TIME") + .unwrap_or("60") + .parse::() + .expect("invalid MIN_EXECUTION_TIME") +}); +pub static TARGET_ITERATION_COUNT: Lazy = Lazy::new(|| { + option_env!("TARGET_ITERATION_COUNT") + .unwrap_or("100") + .parse::() + .expect("invalid TARGET_ITERATION_COUNT") +}); + +#[async_trait::async_trait] +pub trait Benchmark: Sized { + /// The options used to construct the benchmark. + type Options; + /// The state needed to perform the benchmark task. + type TaskState: Default; + + /// execute once before benchmarking + async fn setup(options: Self::Options) -> Result; + + /// execute at the beginning of every iteration + async fn before_task(&self) -> Result { + Ok(Default::default()) + } + + async fn do_task(&self, state: Self::TaskState) -> Result<()>; + + /// execute at the end of every iteration + async fn after_task(&self) -> Result<()> { + Ok(()) + } + + /// execute once after benchmarking + async fn teardown(&self) -> Result<()> { + Ok(()) + } +} + +pub(crate) async fn parse_json_file_to_documents(file: File) -> Result> { + let mut docs: Vec = Vec::new(); + + let mut lines = BufReader::new(file).lines(); + + while let Some(line) = lines.try_next().await? { + let json: Value = serde_json::from_str(&line)?; + + docs.push(match json.try_into()? { + Bson::Document(doc) => doc, + _ => bail!("invalid json document"), + }); + } + + Ok(docs) +} + +fn finished(duration: Duration, iter: usize) -> bool { + let elapsed = duration.as_secs(); + elapsed >= *MAX_EXECUTION_TIME + || (iter >= *TARGET_ITERATION_COUNT && elapsed > *MIN_EXECUTION_TIME) +} + +pub async fn run_benchmark( + options: B::Options, +) -> Result> { + let test = B::setup(options).await?; + + let mut test_durations = Vec::new(); + + let progress_bar = ProgressBar::new(*TARGET_ITERATION_COUNT as u64); + progress_bar.set_style( + ProgressStyle::default_bar() + .template( + "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos:>2}/{len:2} \ + ({eta})", + ) + .progress_chars("#>-"), + ); + + let benchmark_timer = Instant::now(); + let mut iter = 0; + while !finished(benchmark_timer.elapsed(), iter) { + progress_bar.inc(1); + + let state = test.before_task().await?; + let timer = Instant::now(); + test.do_task(state).await?; + test_durations.push(timer.elapsed()); + test.after_task().await?; + + iter += 1; + } + test.teardown().await?; + progress_bar.finish(); + + test_durations.sort(); + Ok(test_durations) +} + +pub async fn drop_database(uri: &str, database: &str) -> Result<()> { + let mut options = ClientOptions::parse(uri).await?; + options.write_concern = Some(WriteConcern::builder().w(Acknowledgment::Majority).build()); + let client = Client::with_options(options.clone())?; + + let hello = client + .database("admin") + .run_command(doc! { "hello": true }) + .await?; + + client.database(database).drop().await?; + + // in sharded clusters, take additional steps to ensure database is dropped completely. + // see: https://www.mongodb.com/docs/manual/reference/method/db.dropDatabase/#replica-set-and-sharded-clusters + let is_sharded = hello.get_str("msg").ok() == Some("isdbgrid"); + if is_sharded { + client.database(database).drop().await?; + for host in options.hosts { + client + .database("admin") + .run_command(doc! { "flushRouterConfig": 1 }) + .selection_criteria(SelectionCriteria::Predicate(Arc::new(move |s| { + s.address() == &host + }))) + .await?; + } + } + + Ok(()) +} diff --git a/benchmarks/src/bench/bson_decode.rs b/benchmarks/src/bench/bson_decode.rs index a3f62f54e..8447c994d 100644 --- a/benchmarks/src/bench/bson_decode.rs +++ b/benchmarks/src/bench/bson_decode.rs @@ -1,10 +1,7 @@ -use std::{convert::TryInto, path::PathBuf}; +use anyhow::Result; +use mongodb::bson::Document; -use anyhow::{bail, Result}; -use mongodb::bson::{Bson, Document}; -use serde_json::Value; - -use crate::{bench::Benchmark, fs::read_to_string}; +use crate::bench::Benchmark; pub struct BsonDecodeBenchmark { num_iter: usize, @@ -13,24 +10,17 @@ pub struct BsonDecodeBenchmark { pub struct Options { pub num_iter: usize, - pub path: PathBuf, + pub doc: Document, } #[async_trait::async_trait] impl Benchmark for BsonDecodeBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { - let mut file = read_to_string(&options.path).await?; - - let json: Value = serde_json::from_str(&mut file)?; - let doc = match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }; - let mut bytes: Vec = Vec::new(); - doc.to_writer(&mut bytes)?; + options.doc.to_writer(&mut bytes)?; Ok(BsonDecodeBenchmark { num_iter: options.num_iter, @@ -38,11 +28,9 @@ impl Benchmark for BsonDecodeBenchmark { }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { for _ in 0..self.num_iter { - // `&[u8]` implements `Read`, and `from_reader` needs a `&mut R: Read`, so we need a - // `&mut &[u8]`. - let _doc = Document::from_reader(&mut &self.bytes[..])?; + let _doc = Document::from_reader(&self.bytes[..])?; } Ok(()) diff --git a/benchmarks/src/bench/bson_encode.rs b/benchmarks/src/bench/bson_encode.rs index 1e21ad1b1..d1616893a 100644 --- a/benchmarks/src/bench/bson_encode.rs +++ b/benchmarks/src/bench/bson_encode.rs @@ -1,10 +1,7 @@ -use std::{convert::TryInto, path::PathBuf}; +use anyhow::Result; +use mongodb::bson::Document; -use anyhow::{bail, Result}; -use mongodb::bson::{Bson, Document}; -use serde_json::Value; - -use crate::{bench::Benchmark, fs::read_to_string}; +use crate::bench::Benchmark; pub struct BsonEncodeBenchmark { num_iter: usize, @@ -13,29 +10,22 @@ pub struct BsonEncodeBenchmark { pub struct Options { pub num_iter: usize, - pub path: PathBuf, + pub doc: Document, } #[async_trait::async_trait] impl Benchmark for BsonEncodeBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { - let mut file = read_to_string(&options.path).await?; - - let json: Value = serde_json::from_str(&mut file)?; - let doc = match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }; - Ok(BsonEncodeBenchmark { num_iter: options.num_iter, - doc, + doc: options.doc, }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { for _ in 0..self.num_iter { let mut bytes: Vec = Vec::new(); self.doc.to_writer(&mut bytes)?; diff --git a/benchmarks/src/bench/bulk_write.rs b/benchmarks/src/bench/bulk_write.rs new file mode 100644 index 000000000..3fda57d9c --- /dev/null +++ b/benchmarks/src/bench/bulk_write.rs @@ -0,0 +1,149 @@ +use anyhow::Result; +use mongodb::{ + bson::{doc, Document}, + options::{DeleteOneModel, InsertOneModel, ReplaceOneModel, WriteModel}, + Client, + Namespace, +}; +use once_cell::sync::Lazy; + +use super::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}; + +pub struct InsertBulkWriteBenchmark { + client: Client, + uri: String, + write_models: Vec, +} + +pub struct Options { + pub uri: String, + pub doc: Document, + pub num_models: usize, +} + +#[async_trait::async_trait] +impl Benchmark for InsertBulkWriteBenchmark { + type Options = Options; + type TaskState = Vec; + + async fn setup(options: Self::Options) -> Result { + let client = Client::with_uri_str(&options.uri).await?; + drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; + + let write_models = vec![ + WriteModel::InsertOne( + InsertOneModel::builder() + .namespace(Namespace::new(DATABASE_NAME.as_str(), COLL_NAME.as_str())) + .document(options.doc.clone()) + .build() + ); + options.num_models + ]; + + Ok(Self { + client, + uri: options.uri, + write_models, + }) + } + + async fn before_task(&self) -> Result { + self.client + .database(&DATABASE_NAME) + .collection::(&COLL_NAME) + .drop() + .await?; + self.client + .database(&DATABASE_NAME) + .create_collection(COLL_NAME.as_str()) + .await?; + Ok(self.write_models.clone()) + } + + async fn do_task(&self, write_models: Self::TaskState) -> Result<()> { + self.client.bulk_write(write_models).await?; + Ok(()) + } + + async fn teardown(&self) -> Result<()> { + drop_database(self.uri.as_str(), DATABASE_NAME.as_str()).await?; + Ok(()) + } +} + +static COLLECTION_NAMES: Lazy> = + Lazy::new(|| (1..=10).map(|i| format!("corpus_{}", i)).collect()); + +pub struct MixedBulkWriteBenchmark { + client: Client, + uri: String, + write_models: Vec, +} + +#[async_trait::async_trait] +impl Benchmark for MixedBulkWriteBenchmark { + type Options = Options; + type TaskState = Vec; + + async fn setup(options: Self::Options) -> Result { + let client = Client::with_uri_str(&options.uri).await?; + drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; + + let mut write_models = Vec::new(); + for i in 0..options.num_models { + let collection_name = COLLECTION_NAMES.get(i % 10).unwrap(); + let namespace = Namespace::new(DATABASE_NAME.as_str(), collection_name); + if i % 3 == 0 { + write_models.push( + InsertOneModel::builder() + .namespace(namespace) + .document(options.doc.clone()) + .build() + .into(), + ); + } else if i % 3 == 1 { + write_models.push( + ReplaceOneModel::builder() + .namespace(namespace) + .filter(doc! {}) + .replacement(options.doc.clone()) + .build() + .into(), + ); + } else { + write_models.push( + DeleteOneModel::builder() + .namespace(namespace) + .filter(doc! {}) + .build() + .into(), + ); + } + } + + Ok(Self { + client, + uri: options.uri, + write_models, + }) + } + + async fn before_task(&self) -> Result { + let database = self.client.database(&DATABASE_NAME); + database.drop().await?; + for collection_name in COLLECTION_NAMES.iter() { + database.create_collection(collection_name).await?; + } + Ok(self.write_models.clone()) + } + + async fn do_task(&self, write_models: Self::TaskState) -> Result<()> { + self.client.bulk_write(write_models).await?; + Ok(()) + } + + async fn teardown(&self) -> Result<()> { + drop_database(self.uri.as_str(), DATABASE_NAME.as_str()).await?; + Ok(()) + } +} diff --git a/benchmarks/src/bench/find_many.rs b/benchmarks/src/bench/find_many.rs index 07f1eb77f..779615c4d 100644 --- a/benchmarks/src/bench/find_many.rs +++ b/benchmarks/src/bench/find_many.rs @@ -1,19 +1,15 @@ -use std::{convert::TryInto, path::PathBuf}; - -use anyhow::{bail, Result}; +use anyhow::Result; use futures::stream::StreamExt; use mongodb::{ - bson::{Bson, Document, RawDocumentBuf}, + bson::{doc, Document, RawDocumentBuf}, Client, Collection, Database, }; use serde::de::DeserializeOwned; -use serde_json::Value; use crate::{ bench::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}, - fs::read_to_string, models::tweet::Tweet, }; @@ -27,7 +23,7 @@ pub struct FindManyBenchmark { // Specifies the options to `FindManyBenchmark::setup` operation. pub struct Options { pub num_iter: usize, - pub path: PathBuf, + pub doc: Document, pub uri: String, pub mode: Mode, } @@ -41,25 +37,16 @@ pub enum Mode { #[async_trait::async_trait] impl Benchmark for FindManyBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; let db = client.database(&DATABASE_NAME); drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; - let num_iter = options.num_iter; - - let mut file = read_to_string(&options.path).await?; - - let json: Value = serde_json::from_str(&mut file)?; - let doc = match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }; - let coll = db.collection(&COLL_NAME); - let docs = vec![doc.clone(); num_iter]; - coll.insert_many(docs, None).await?; + let docs = vec![options.doc.clone(); options.num_iter]; + coll.insert_many(docs).await?; Ok(FindManyBenchmark { db, @@ -69,12 +56,12 @@ impl Benchmark for FindManyBenchmark { }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { async fn execute( bench: &FindManyBenchmark, ) -> Result<()> { let coll = bench.coll.clone_with_type::(); - let mut cursor = coll.find(None, None).await?; + let mut cursor = coll.find(doc! {}).await?; while let Some(doc) = cursor.next().await { doc?; } diff --git a/benchmarks/src/bench/find_one.rs b/benchmarks/src/bench/find_one.rs index 3e579c414..97475a040 100644 --- a/benchmarks/src/bench/find_one.rs +++ b/benchmarks/src/bench/find_one.rs @@ -1,18 +1,12 @@ -use std::{convert::TryInto, path::PathBuf}; - -use anyhow::{bail, Result}; +use anyhow::Result; use mongodb::{ - bson::{doc, Bson, Document}, + bson::{doc, Document}, Client, Collection, Database, }; -use serde_json::Value; -use crate::{ - bench::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}, - fs::read_to_string, -}; +use crate::bench::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}; pub struct FindOneBenchmark { db: Database, @@ -24,48 +18,38 @@ pub struct FindOneBenchmark { /// Specifies the options to a `FindOneBenchmark::setup` operation. pub struct Options { pub num_iter: usize, - pub path: PathBuf, + pub doc: Document, pub uri: String, } #[async_trait::async_trait] impl Benchmark for FindOneBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; let db = client.database(&DATABASE_NAME); drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; - let num_iter = options.num_iter; - - let mut file = read_to_string(&options.path).await?; - - let json: Value = serde_json::from_str(&mut file)?; - let mut doc = match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }; - let coll = db.collection(&COLL_NAME); - for i in 0..num_iter { + for i in 0..options.num_iter { + let mut doc = options.doc.clone(); doc.insert("_id", i as i32); - coll.insert_one(doc.clone(), None).await?; + coll.insert_one(doc).await?; } Ok(FindOneBenchmark { db, - num_iter, + num_iter: options.num_iter, coll, uri: options.uri, }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { for i in 0..self.num_iter { - self.coll - .find_one(Some(doc! { "_id": i as i32 }), None) - .await?; + self.coll.find_one(doc! { "_id": i as i32 }).await?; } Ok(()) diff --git a/benchmarks/src/bench/gridfs_download.rs b/benchmarks/src/bench/gridfs_download.rs index 97e05b577..c80aabd24 100644 --- a/benchmarks/src/bench/gridfs_download.rs +++ b/benchmarks/src/bench/gridfs_download.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; +use futures::{AsyncReadExt, AsyncWriteExt}; use mongodb::{bson::Bson, gridfs::GridFsBucket, Client}; use crate::{ @@ -22,6 +23,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for GridFsDownloadBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -33,24 +35,25 @@ impl Benchmark for GridFsDownloadBenchmark { let bucket = db.gridfs_bucket(None); let file = open_async_read_compat(&options.path).await?; - let file_id = bucket - .upload_from_futures_0_3_reader("gridfstest", file, None) - .await - .context("upload file")?; + let mut upload = bucket.open_upload_stream("gridfstest").await?; + let file_id = upload.id().clone(); + futures_util::io::copy(file, &mut upload).await?; + upload.close().await?; Ok(Self { uri: options.uri, bucket, - file_id: file_id.into(), + file_id, }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let mut buf = vec![]; - self.bucket - .download_to_futures_0_3_writer(self.file_id.clone(), &mut buf) - .await - .context("download file")?; + let mut download = self + .bucket + .open_download_stream(self.file_id.clone()) + .await?; + download.read_to_end(&mut buf).await?; Ok(()) } diff --git a/benchmarks/src/bench/gridfs_multi_download.rs b/benchmarks/src/bench/gridfs_multi_download.rs index d02d070a2..10331d22f 100644 --- a/benchmarks/src/bench/gridfs_multi_download.rs +++ b/benchmarks/src/bench/gridfs_multi_download.rs @@ -5,23 +5,21 @@ use std::{ use anyhow::{Context, Result}; use futures::AsyncWriteExt; -use lazy_static::lazy_static; -use mongodb::{bson::oid::ObjectId, gridfs::GridFsBucket, Client}; +use mongodb::{bson::Bson, gridfs::GridFsBucket, Client}; +use once_cell::sync::Lazy; use crate::{ bench::{drop_database, Benchmark, DATABASE_NAME}, fs::{open_async_read_compat, open_async_write_compat}, }; -lazy_static! { - static ref DOWNLOAD_PATH: PathBuf = - Path::new(env!("CARGO_MANIFEST_DIR")).join("gridfs_multi_download"); -} +static DOWNLOAD_PATH: Lazy = + Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("gridfs_multi_download")); pub struct GridFsMultiDownloadBenchmark { uri: String, bucket: GridFsBucket, - ids: Vec, + ids: Vec, } pub struct Options { @@ -32,6 +30,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for GridFsMultiDownloadBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -48,10 +47,12 @@ impl Benchmark for GridFsMultiDownloadBenchmark { let path = entry?.path(); let file = open_async_read_compat(&path).await?; - let id = bucket - .upload_from_futures_0_3_reader(path.display().to_string(), file, None) - .await - .context("upload file")?; + let mut upload = bucket + .open_upload_stream(path.display().to_string()) + .await?; + let id = upload.id().clone(); + futures_util::io::copy(file, &mut upload).await?; + upload.close().await?; ids.push(id); } @@ -64,9 +65,9 @@ impl Benchmark for GridFsMultiDownloadBenchmark { }) } - async fn before_task(&mut self) -> Result<()> { + async fn before_task(&self) -> Result { for id in &self.ids { - let path = get_filename(id.clone()); + let path = get_filename(id); if Path::try_exists(&path)? { remove_file(path)?; } @@ -75,7 +76,7 @@ impl Benchmark for GridFsMultiDownloadBenchmark { Ok(()) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let mut tasks = vec![]; for id in &self.ids { @@ -83,15 +84,12 @@ impl Benchmark for GridFsMultiDownloadBenchmark { let id = id.clone(); tasks.push(crate::spawn(async move { - let download_path = get_filename(id); + let download_path = get_filename(&id); let mut file = open_async_write_compat(&download_path) .await .context("open file")?; - - bucket - .download_to_futures_0_3_writer(id.into(), &mut file) - .await - .context("download file")?; + let download = bucket.open_download_stream(id).await?; + futures_util::io::copy(download, &mut file).await?; file.flush().await?; @@ -116,6 +114,6 @@ impl Benchmark for GridFsMultiDownloadBenchmark { } } -fn get_filename(id: ObjectId) -> PathBuf { +fn get_filename(id: &Bson) -> PathBuf { DOWNLOAD_PATH.join(format!("file{}.txt", id)) } diff --git a/benchmarks/src/bench/gridfs_multi_upload.rs b/benchmarks/src/bench/gridfs_multi_upload.rs index bd9dec51a..aae9aea9a 100644 --- a/benchmarks/src/bench/gridfs_multi_upload.rs +++ b/benchmarks/src/bench/gridfs_multi_upload.rs @@ -1,6 +1,7 @@ use std::{fs::read_dir, path::PathBuf}; use anyhow::{Context, Result}; +use futures::AsyncWriteExt; use mongodb::{gridfs::GridFsBucket, Client}; use crate::{ @@ -22,6 +23,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for GridFsMultiUploadBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -37,18 +39,16 @@ impl Benchmark for GridFsMultiUploadBenchmark { }) } - async fn before_task(&mut self) -> Result<()> { + async fn before_task(&self) -> Result { self.bucket.drop().await.context("bucket drop")?; - - self.bucket - .upload_from_futures_0_3_reader("beforetask", &[11u8][..], None) - .await - .context("single byte upload")?; + let mut upload = self.bucket.open_upload_stream("beforetask").await?; + upload.write_all(&[11u8][..]).await?; + upload.close().await?; Ok(()) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let mut tasks = vec![]; for entry in read_dir(&self.path)? { @@ -57,10 +57,11 @@ impl Benchmark for GridFsMultiUploadBenchmark { tasks.push(crate::spawn(async move { let path = entry?.path(); let file = open_async_read_compat(&path).await?; - bucket - .upload_from_futures_0_3_reader(path.display().to_string(), file, None) - .await - .context("upload file")?; + let mut upload = bucket + .open_upload_stream(path.display().to_string()) + .await?; + futures_util::io::copy(file, &mut upload).await?; + upload.close().await?; let ok: anyhow::Result<()> = Ok(()); ok diff --git a/benchmarks/src/bench/gridfs_upload.rs b/benchmarks/src/bench/gridfs_upload.rs index 4c600b4c0..26ba49599 100644 --- a/benchmarks/src/bench/gridfs_upload.rs +++ b/benchmarks/src/bench/gridfs_upload.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use futures::AsyncReadExt; +use futures::{AsyncReadExt, AsyncWriteExt}; use mongodb::{gridfs::GridFsBucket, Client}; use crate::{ @@ -23,6 +23,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for GridFsUploadBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -42,22 +43,19 @@ impl Benchmark for GridFsUploadBenchmark { }) } - async fn before_task(&mut self) -> Result<()> { + async fn before_task(&self) -> Result { self.bucket.drop().await.context("bucket drop")?; - - self.bucket - .upload_from_futures_0_3_reader("beforetask", &[11u8][..], None) - .await - .context("single byte upload")?; + let mut upload = self.bucket.open_upload_stream("beforetask").await?; + upload.write_all(&[11u8][..]).await?; + upload.close().await?; Ok(()) } - async fn do_task(&self) -> Result<()> { - self.bucket - .upload_from_futures_0_3_reader("gridfstest", &self.bytes[..], None) - .await - .context("upload bytes")?; + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { + let mut upload = self.bucket.open_upload_stream("gridfstest").await?; + upload.write_all(&self.bytes[..]).await?; + upload.close().await?; Ok(()) } diff --git a/benchmarks/src/bench/insert_many.rs b/benchmarks/src/bench/insert_many.rs index 7c6184de6..55b9d2152 100644 --- a/benchmarks/src/bench/insert_many.rs +++ b/benchmarks/src/bench/insert_many.rs @@ -1,13 +1,5 @@ -use std::{convert::TryInto, fs::File, path::PathBuf}; - -use anyhow::{bail, Context, Result}; -use mongodb::{ - bson::{Bson, Document}, - Client, - Collection, - Database, -}; -use serde_json::Value; +use anyhow::{Context, Result}; +use mongodb::{bson::Document, Client, Collection, Database}; use crate::bench::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}; @@ -22,59 +14,45 @@ pub struct InsertManyBenchmark { /// Specifies the options to a `InsertManyBenchmark::setup` operation. pub struct Options { pub num_copies: usize, - pub path: PathBuf, + pub doc: Document, pub uri: String, } #[async_trait::async_trait] impl Benchmark for InsertManyBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; let db = client.database(&DATABASE_NAME); drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; - let num_copies = options.num_copies; - let uri = options.uri.clone(); - - // This benchmark uses a file that's quite large, and unfortunately `serde_json` has no - // async version of `from_reader`, so rather than read the whole file into memory at once, - // we use the runtime's `spawn_blocking` functionality to do this efficiently. - // - // Note that the setup is _not_ measured as part of the benchmark runtime, so even if - // `spawn_blocking` turned out not to be super efficient, it wouldn't be a big deal. - let mut file = spawn_blocking_and_await!(File::open(options.path))?; - let json: Value = spawn_blocking_and_await!(serde_json::from_reader(&mut file))?; - let coll = db.collection(COLL_NAME.as_str()); Ok(InsertManyBenchmark { db, - num_copies, + num_copies: options.num_copies, coll, - doc: match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }, - uri, + doc: options.doc, + uri: options.uri, }) } - async fn before_task(&mut self) -> Result<()> { - self.coll.drop(None).await?; + async fn before_task(&self) -> Result { + self.coll.drop().await?; self.db - .create_collection(COLL_NAME.as_str(), None) + .create_collection(COLL_NAME.as_str()) .await .context("create in before")?; Ok(()) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let insertions = vec![&self.doc; self.num_copies]; self.coll - .insert_many(insertions, None) + .insert_many(insertions) .await .context("insert many")?; diff --git a/benchmarks/src/bench/insert_one.rs b/benchmarks/src/bench/insert_one.rs index 33295b84e..5d6f29c2b 100644 --- a/benchmarks/src/bench/insert_one.rs +++ b/benchmarks/src/bench/insert_one.rs @@ -1,13 +1,5 @@ -use std::{convert::TryInto, fs::File, path::PathBuf}; - -use anyhow::{bail, Context, Result}; -use mongodb::{ - bson::{Bson, Document}, - Client, - Collection, - Database, -}; -use serde_json::Value; +use anyhow::{Context, Result}; +use mongodb::{bson::Document, Client, Collection, Database}; use crate::bench::{drop_database, Benchmark, COLL_NAME, DATABASE_NAME}; @@ -22,59 +14,45 @@ pub struct InsertOneBenchmark { /// Specifies the options to a `InsertOneBenchmark::setup` operation. pub struct Options { pub num_iter: usize, - pub path: PathBuf, + pub doc: Document, pub uri: String, } #[async_trait::async_trait] impl Benchmark for InsertOneBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; let db = client.database(&DATABASE_NAME); drop_database(&options.uri, &DATABASE_NAME).await?; - let num_iter = options.num_iter; - let uri = options.uri.clone(); - - // This benchmark uses a file that's quite large, and unfortunately `serde_json` has no - // async version of `from_reader`, so rather than read the whole file into memory at once, - // we use the runtime's `spawn_blocking` functionality to do this efficiently. - // - // Note that the setup is _not_ measured as part of the benchmark runtime, so even if - // `spawn_blocking` turned out not to be super efficient, it wouldn't be a big deal. - let mut file = spawn_blocking_and_await!(File::open(options.path))?; - let json: Value = spawn_blocking_and_await!(serde_json::from_reader(&mut file))?; - let coll = db.collection(&COLL_NAME); Ok(InsertOneBenchmark { db, - num_iter, + num_iter: options.num_iter, coll, - doc: match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json test file"), - }, - uri, + doc: options.doc, + uri: options.uri, }) } - async fn before_task(&mut self) -> Result<()> { - self.coll.drop(None).await?; + async fn before_task(&self) -> Result { + self.coll.drop().await?; self.db - .create_collection(COLL_NAME.as_str(), None) + .create_collection(COLL_NAME.as_str()) .await .context("create collection")?; Ok(()) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { for _ in 0..self.num_iter { self.coll - .insert_one(&self.doc, None) + .insert_one(&self.doc) .await .context("insert one")?; } diff --git a/benchmarks/src/bench/json_multi_export.rs b/benchmarks/src/bench/json_multi_export.rs index 5457252bc..3ccae9afb 100644 --- a/benchmarks/src/bench/json_multi_export.rs +++ b/benchmarks/src/bench/json_multi_export.rs @@ -33,6 +33,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for JsonMultiExportBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -55,7 +56,7 @@ impl Benchmark for JsonMultiExportBenchmark { for mut doc in docs { doc.insert("file", i as i32); - coll.insert_one(doc, None).await?; + coll.insert_one(doc).await?; } let ok: anyhow::Result<()> = Ok(()); @@ -74,7 +75,7 @@ impl Benchmark for JsonMultiExportBenchmark { }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let mut tasks = Vec::new(); for i in 0..TOTAL_FILES { @@ -89,10 +90,7 @@ impl Benchmark for JsonMultiExportBenchmark { let file_name = path.join(format!("ldjson{:03}.txt", i)); let mut file = File::open_write(&file_name).await.unwrap(); - let mut cursor = coll_ref - .find(Some(doc! { "file": i as i32 }), None) - .await - .unwrap(); + let mut cursor = coll_ref.find(doc! { "file": i as i32 }).await.unwrap(); while let Some(doc) = cursor.try_next().await.unwrap() { file.write_line(serde_json::to_string(&doc).unwrap().as_str()) diff --git a/benchmarks/src/bench/json_multi_import.rs b/benchmarks/src/bench/json_multi_import.rs index b32d0c5c7..5e3693118 100644 --- a/benchmarks/src/bench/json_multi_import.rs +++ b/benchmarks/src/bench/json_multi_import.rs @@ -2,7 +2,7 @@ use std::path::PathBuf; use anyhow::Result; use futures::stream::TryStreamExt; -use mongodb::{options::InsertManyOptions, Client, Collection, Database}; +use mongodb::{Client, Collection, Database}; use crate::{ bench::{Benchmark, COLL_NAME, DATABASE_NAME}, @@ -30,6 +30,7 @@ pub struct Options { #[async_trait::async_trait] impl Benchmark for JsonMultiImportBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { let client = Client::with_uri_str(&options.uri).await?; @@ -46,14 +47,14 @@ impl Benchmark for JsonMultiImportBenchmark { }) } - async fn before_task(&mut self) -> Result<()> { - self.coll.drop(None).await?; - self.db.create_collection(COLL_NAME.as_str(), None).await?; + async fn before_task(&self) -> Result { + self.coll.drop().await?; + self.db.create_collection(COLL_NAME.as_str()).await?; Ok(()) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { let mut tasks = Vec::new(); for i in 0..TOTAL_FILES { @@ -74,8 +75,7 @@ impl Benchmark for JsonMultiImportBenchmark { docs.push(serde_json::from_str(&line).unwrap()); } - let opts = Some(InsertManyOptions::builder().ordered(false).build()); - coll_ref.insert_many(docs, opts).await.unwrap(); + coll_ref.insert_many(docs).ordered(false).await.unwrap(); })); } diff --git a/benchmarks/src/bench/mod.rs b/benchmarks/src/bench/mod.rs deleted file mode 100644 index 56c4c6e35..000000000 --- a/benchmarks/src/bench/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -pub mod bson_decode; -pub mod bson_encode; -pub mod find_many; -pub mod find_one; -pub mod gridfs_download; -pub mod gridfs_multi_download; -pub mod gridfs_multi_upload; -pub mod gridfs_upload; -pub mod insert_many; -pub mod insert_one; -pub mod json_multi_export; -pub mod json_multi_import; -pub mod run_command; - -use std::{ - convert::TryInto, - sync::Arc, - time::{Duration, Instant}, -}; - -use anyhow::{bail, Result}; -use futures::stream::TryStreamExt; -use indicatif::{ProgressBar, ProgressStyle}; -use lazy_static::lazy_static; -use mongodb::{ - bson::{doc, Bson, Document}, - options::{Acknowledgment, ClientOptions, SelectionCriteria, WriteConcern}, - Client, -}; -use serde_json::Value; - -use crate::fs::{BufReader, File}; - -lazy_static! { - static ref DATABASE_NAME: String = option_env!("DATABASE_NAME") - .unwrap_or("perftest") - .to_string(); - static ref COLL_NAME: String = option_env!("COLL_NAME").unwrap_or("corpus").to_string(); - static ref MAX_EXECUTION_TIME: u64 = option_env!("MAX_EXECUTION_TIME") - .unwrap_or("300") - .parse::() - .expect("invalid MAX_EXECUTION_TIME"); - static ref MIN_EXECUTION_TIME: u64 = option_env!("MIN_EXECUTION_TIME") - .unwrap_or("60") - .parse::() - .expect("invalid MIN_EXECUTION_TIME"); - pub static ref TARGET_ITERATION_COUNT: usize = option_env!("TARGET_ITERATION_COUNT") - .unwrap_or("100") - .parse::() - .expect("invalid TARGET_ITERATION_COUNT"); -} - -#[async_trait::async_trait] -pub trait Benchmark: Sized { - type Options; - - /// execute once before benchmarking - async fn setup(options: Self::Options) -> Result; - - /// execute at the beginning of every iteration - async fn before_task(&mut self) -> Result<()> { - Ok(()) - } - - async fn do_task(&self) -> Result<()>; - - /// execute at the end of every iteration - async fn after_task(&self) -> Result<()> { - Ok(()) - } - - /// execute once after benchmarking - async fn teardown(&self) -> Result<()> { - Ok(()) - } -} - -pub(crate) async fn parse_json_file_to_documents(file: File) -> Result> { - let mut docs: Vec = Vec::new(); - - let mut lines = BufReader::new(file).lines(); - - while let Some(line) = lines.try_next().await? { - let json: Value = serde_json::from_str(&line)?; - - docs.push(match json.try_into()? { - Bson::Document(doc) => doc, - _ => bail!("invalid json document"), - }); - } - - Ok(docs) -} - -fn finished(duration: Duration, iter: usize) -> bool { - let elapsed = duration.as_secs(); - elapsed >= *MAX_EXECUTION_TIME - || (iter >= *TARGET_ITERATION_COUNT && elapsed > *MIN_EXECUTION_TIME) -} - -pub async fn run_benchmark( - options: B::Options, -) -> Result> { - let mut test = B::setup(options).await?; - - let mut test_durations = Vec::new(); - - let progress_bar = ProgressBar::new(*TARGET_ITERATION_COUNT as u64); - progress_bar.set_style( - ProgressStyle::default_bar() - .template( - "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos:>2}/{len:2} \ - ({eta})", - ) - .progress_chars("#>-"), - ); - - let benchmark_timer = Instant::now(); - let mut iter = 0; - while !finished(benchmark_timer.elapsed(), iter) { - progress_bar.inc(1); - - test.before_task().await?; - let timer = Instant::now(); - test.do_task().await?; - test_durations.push(timer.elapsed()); - test.after_task().await?; - - iter += 1; - } - test.teardown().await?; - progress_bar.finish(); - - test_durations.sort(); - Ok(test_durations) -} - -pub async fn drop_database(uri: &str, database: &str) -> Result<()> { - let mut options = ClientOptions::parse(uri).await?; - options.write_concern = Some(WriteConcern::builder().w(Acknowledgment::Majority).build()); - let client = Client::with_options(options.clone())?; - - let hello = client - .database("admin") - .run_command(doc! { "hello": true }, None) - .await?; - - client.database(&database).drop(None).await?; - - // in sharded clusters, take additional steps to ensure database is dropped completely. - // see: https://www.mongodb.com/docs/manual/reference/method/db.dropDatabase/#replica-set-and-sharded-clusters - let is_sharded = hello.get_str("msg").ok() == Some("isdbgrid"); - if is_sharded { - client.database(&database).drop(None).await?; - for host in options.hosts { - client - .database("admin") - .run_command( - doc! { "flushRouterConfig": 1 }, - SelectionCriteria::Predicate(Arc::new(move |s| s.address() == &host)), - ) - .await?; - } - } - - Ok(()) -} diff --git a/benchmarks/src/bench/run_command.rs b/benchmarks/src/bench/run_command.rs index 3cfa6b29d..727948740 100644 --- a/benchmarks/src/bench/run_command.rs +++ b/benchmarks/src/bench/run_command.rs @@ -8,7 +8,7 @@ use mongodb::{ use crate::bench::{drop_database, Benchmark, DATABASE_NAME}; pub struct RunCommandBenchmark { - db: Database, + db: Option, num_iter: usize, cmd: Document, uri: String, @@ -17,16 +17,34 @@ pub struct RunCommandBenchmark { pub struct Options { pub num_iter: usize, pub uri: String, + pub cold_start: bool, +} + +impl RunCommandBenchmark { + async fn init_db(uri: &str) -> Result { + let client = Client::with_uri_str(uri).await?; + Ok(client.database(&DATABASE_NAME)) + } + + async fn get_db(&self) -> Result { + match self.db.as_ref() { + Some(db) => Ok(db.clone()), + None => Self::init_db(&self.uri).await, + } + } } #[async_trait::async_trait] impl Benchmark for RunCommandBenchmark { type Options = Options; + type TaskState = (); async fn setup(options: Self::Options) -> Result { - let client = Client::with_uri_str(&options.uri).await?; - let db = client.database(&DATABASE_NAME); - drop_database(options.uri.as_str(), DATABASE_NAME.as_str()).await?; + let db = if options.cold_start { + None + } else { + Some(Self::init_db(&options.uri).await?) + }; Ok(RunCommandBenchmark { db, @@ -36,11 +54,12 @@ impl Benchmark for RunCommandBenchmark { }) } - async fn do_task(&self) -> Result<()> { + async fn do_task(&self, _state: Self::TaskState) -> Result<()> { for _ in 0..self.num_iter { let _doc = self - .db - .run_command(self.cmd.clone(), None) + .get_db() + .await? + .run_command(self.cmd.clone()) .await .context("run command")?; } @@ -49,7 +68,7 @@ impl Benchmark for RunCommandBenchmark { } async fn teardown(&self) -> Result<()> { - drop_database(self.uri.as_str(), self.db.name()).await?; + drop_database(self.uri.as_str(), &DATABASE_NAME).await?; Ok(()) } diff --git a/benchmarks/src/data.rs b/benchmarks/src/data.rs new file mode 100644 index 000000000..a8693dbc3 --- /dev/null +++ b/benchmarks/src/data.rs @@ -0,0 +1,59 @@ +use std::{ + fs::File, + path::{Path, PathBuf}, +}; + +use mongodb::bson::Document; +use once_cell::sync::Lazy; +use tokio::sync::OnceCell; + +pub static DATA_PATH: Lazy = + Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("data")); + +async fn get_data(once_cell: &OnceCell, path: &[&str]) -> Document { + once_cell + .get_or_init(|| async { + let mut data_path = DATA_PATH.clone(); + data_path.extend(path); + let file = tokio::task::spawn_blocking(|| File::open(data_path)) + .await + .unwrap() + .unwrap(); + tokio::task::spawn_blocking(|| serde_json::from_reader(file)) + .await + .unwrap() + .unwrap() + }) + .await + .clone() +} + +pub async fn get_small_doc() -> Document { + static SMALL_DOC: OnceCell = OnceCell::const_new(); + get_data(&SMALL_DOC, &["single_and_multi_document", "small_doc.json"]).await +} + +pub async fn get_large_doc() -> Document { + static LARGE_DOC: OnceCell = OnceCell::const_new(); + get_data(&LARGE_DOC, &["single_and_multi_document", "large_doc.json"]).await +} + +pub async fn get_flat_bson() -> Document { + static FLAT_BSON: OnceCell = OnceCell::const_new(); + get_data(&FLAT_BSON, &["extended_bson", "flat_bson.json"]).await +} + +pub async fn get_deep_bson() -> Document { + static DEEP_BSON: OnceCell = OnceCell::const_new(); + get_data(&DEEP_BSON, &["extended_bson", "deep_bson.json"]).await +} + +pub async fn get_full_bson() -> Document { + static FULL_BSON: OnceCell = OnceCell::const_new(); + get_data(&FULL_BSON, &["extended_bson", "full_bson.json"]).await +} + +pub async fn get_tweet() -> Document { + static TWEET: OnceCell = OnceCell::const_new(); + get_data(&TWEET, &["single_and_multi_document", "tweet.json"]).await +} diff --git a/benchmarks/src/fs.rs b/benchmarks/src/fs.rs index c17f081ba..95cf196f0 100644 --- a/benchmarks/src/fs.rs +++ b/benchmarks/src/fs.rs @@ -1,60 +1,31 @@ use std::path::Path; use anyhow::Result; -#[cfg(feature = "async-std-runtime")] -use async_std::{ - fs::{self, OpenOptions}, - io::{ - self, - prelude::{BufReadExt, WriteExt}, - }, -}; -use futures::stream::Stream; -#[cfg(feature = "tokio-runtime")] use tokio::{ fs::{self, OpenOptions}, io::{self, AsyncBufReadExt, AsyncWriteExt}, }; -#[cfg(feature = "tokio-runtime")] use tokio_util::compat::TokioAsyncReadCompatExt; -pub(crate) async fn read_to_string(path: &Path) -> Result { - let s = fs::read_to_string(path).await?; - Ok(s) +pub(crate) struct File { + inner: fs::File, } pub(crate) async fn open_async_read_compat(path: &Path) -> Result { let file = File::open_read(path).await?; - #[cfg(feature = "tokio-runtime")] - { - Ok(file.inner.compat()) - } - #[cfg(feature = "async-std-runtime")] - { - Ok(file.inner) - } + Ok(file.inner.compat()) } pub(crate) async fn open_async_write_compat(path: &Path) -> Result { let file = File::open_write(path).await?; - #[cfg(feature = "tokio-runtime")] - { - Ok(file.inner.compat()) - } - #[cfg(feature = "async-std-runtime")] - { - Ok(file.inner) - } -} - -pub(crate) struct File { - inner: fs::File, + Ok(file.inner.compat()) } impl File { pub(crate) async fn open_write(name: &Path) -> Result { let inner = OpenOptions::new() .create(true) + .truncate(true) .write(true) .open(name) .await?; @@ -89,15 +60,7 @@ impl BufReader { } } - pub(crate) fn lines(self) -> impl Stream> { - #[cfg(feature = "tokio-runtime")] - { - tokio_stream::wrappers::LinesStream::new(self.inner.lines()) - } - - #[cfg(feature = "async-std-runtime")] - { - self.inner.lines() - } + pub(crate) fn lines(self) -> impl tokio_stream::Stream> { + tokio_stream::wrappers::LinesStream::new(self.inner.lines()) } } diff --git a/benchmarks/src/main.rs b/benchmarks/src/main.rs index dd027e0ea..a0089d7e3 100644 --- a/benchmarks/src/main.rs +++ b/benchmarks/src/main.rs @@ -1,58 +1,29 @@ -macro_rules! spawn_blocking_and_await { - ($blocking_call:expr) => {{ - #[cfg(feature = "tokio-runtime")] - { - tokio::task::spawn_blocking(move || $blocking_call) - .await - .unwrap() - } - - #[cfg(feature = "async-std-runtime")] - { - async_std::task::spawn_blocking(move || $blocking_call).await - } - }}; -} - fn spawn(future: T) -> impl Future::Output> where T: Future + Send + 'static, T::Output: Send + 'static, { - #[cfg(feature = "tokio-runtime")] - { - tokio::task::spawn(future).map(|result| result.unwrap()) - } - - #[cfg(feature = "async-std-runtime")] - { - async_std::task::spawn(future) - } + tokio::task::spawn(future).map(|result| result.unwrap()) } mod bench; +mod data; mod fs; mod models; mod score; -use std::{ - collections::HashSet, - convert::TryFrom, - path::{Path, PathBuf}, -}; +use std::{collections::HashSet, convert::TryFrom, path::PathBuf}; use anyhow::Result; use clap::{App, Arg, ArgMatches}; -use futures::Future; -#[cfg(feature = "tokio-runtime")] -use futures::FutureExt; -use lazy_static::lazy_static; +use futures::{Future, FutureExt}; use mongodb::options::ClientOptions; use crate::{ bench::{ bson_decode::BsonDecodeBenchmark, bson_encode::BsonEncodeBenchmark, + bulk_write::{InsertBulkWriteBenchmark, MixedBulkWriteBenchmark}, find_many::FindManyBenchmark, find_one::FindOneBenchmark, gridfs_download::GridFsDownloadBenchmark, @@ -65,73 +36,87 @@ use crate::{ json_multi_import::JsonMultiImportBenchmark, run_command::RunCommandBenchmark, }, + data::{ + get_deep_bson, + get_flat_bson, + get_full_bson, + get_large_doc, + get_small_doc, + get_tweet, + DATA_PATH, + }, fs::File, score::{score_test, BenchmarkResult, CompositeScore}, }; -lazy_static! { - static ref DATA_PATH: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR")).join("data"); -} - // benchmark names -const FLAT_BSON_ENCODING: &'static str = "Flat BSON Encoding"; -const FLAT_BSON_DECODING: &'static str = "Flat BSON Decoding"; -const DEEP_BSON_ENCODING: &'static str = "Deep BSON Encoding"; -const DEEP_BSON_DECODING: &'static str = "Deep BSON Decoding"; -const FULL_BSON_ENCODING: &'static str = "Full BSON Encoding"; -const FULL_BSON_DECODING: &'static str = "Full BSON Decoding"; -const RUN_COMMAND_BENCH: &'static str = "Run Command"; -const FIND_ONE_BENCH: &'static str = "Find one"; -const FIND_MANY_BENCH: &'static str = "Find many and empty cursor"; -const FIND_MANY_BENCH_RAW: &'static str = "Find many and empty cursor (raw BSON)"; -const FIND_MANY_BENCH_SERDE: &'static str = "Find many and empty cursor (serde structs)"; -const GRIDFS_DOWNLOAD_BENCH: &'static str = "GridFS download"; -const LDJSON_MULTI_EXPORT_BENCH: &'static str = "LDJSON multi-file export"; -const GRIDFS_MULTI_DOWNLOAD_BENCH: &'static str = "GridFS multi-file download"; -const SMALL_DOC_INSERT_ONE_BENCH: &'static str = "Small doc insertOne"; -const LARGE_DOC_INSERT_ONE_BENCH: &'static str = "Large doc insertOne"; -const SMALL_DOC_BULK_INSERT_BENCH: &'static str = "Small doc bulk insert"; -const LARGE_DOC_BULK_INSERT_BENCH: &'static str = "Large doc bulk insert"; -const GRIDFS_UPLOAD_BENCH: &'static str = "GridFS upload"; -const LDJSON_MULTI_IMPORT_BENCH: &'static str = "LDJSON multi-file import"; -const GRIDFS_MULTI_UPLOAD_BENCH: &'static str = "GridFS multi-file upload"; +const FLAT_BSON_ENCODING: &str = "Flat BSON Encoding"; +const FLAT_BSON_DECODING: &str = "Flat BSON Decoding"; +const DEEP_BSON_ENCODING: &str = "Deep BSON Encoding"; +const DEEP_BSON_DECODING: &str = "Deep BSON Decoding"; +const FULL_BSON_ENCODING: &str = "Full BSON Encoding"; +const FULL_BSON_DECODING: &str = "Full BSON Decoding"; +const RUN_COMMAND_BENCH: &str = "Run Command"; +const RUN_COMMAND_COLD_START_BENCH: &str = "Run Command (cold start)"; +const FIND_ONE_BENCH: &str = "Find one"; +const FIND_MANY_BENCH: &str = "Find many and empty cursor"; +const FIND_MANY_BENCH_RAW: &str = "Find many and empty cursor (raw BSON)"; +const FIND_MANY_BENCH_SERDE: &str = "Find many and empty cursor (serde structs)"; +const GRIDFS_DOWNLOAD_BENCH: &str = "GridFS download"; +const LDJSON_MULTI_EXPORT_BENCH: &str = "LDJSON multi-file export"; +const GRIDFS_MULTI_DOWNLOAD_BENCH: &str = "GridFS multi-file download"; +const SMALL_DOC_INSERT_ONE_BENCH: &str = "Small doc insertOne"; +const LARGE_DOC_INSERT_ONE_BENCH: &str = "Large doc insertOne"; +const SMALL_DOC_INSERT_MANY_BENCH: &str = "Small doc insertMany"; +const LARGE_DOC_INSERT_MANY_BENCH: &str = "Large doc insertMany"; +const GRIDFS_UPLOAD_BENCH: &str = "GridFS upload"; +const LDJSON_MULTI_IMPORT_BENCH: &str = "LDJSON multi-file import"; +const GRIDFS_MULTI_UPLOAD_BENCH: &str = "GridFS multi-file upload"; +const SMALL_DOC_INSERT_BULK_WRITE_BENCH: &str = "Small doc insert-only bulkWrite"; +const LARGE_DOC_INSERT_BULK_WRITE_BENCH: &str = "Large doc insert-only bulkWrite"; +const MIXED_BULK_WRITE_BENCH: &str = "Mixed bulkWrite"; #[derive(Copy, Clone, num_enum::TryFromPrimitive, PartialEq, Eq, Hash, PartialOrd, Ord)] #[repr(u8)] enum BenchmarkId { RunCommand = 1, - FindOneById, - SmallDocInsertOne, - LargeDocInsertOne, - FindMany, - SmallDocBulkInsert, - LargeDocBulkInsert, - LdJsonMultiFileImport, - LdJsonMultiFileExport, - BsonFlatDocumentDecode, - BsonFlatDocumentEncode, - BsonDeepDocumentDecode, - BsonDeepDocumentEncode, - BsonFullDocumentDecode, - BsonFullDocumentEncode, - FindManyRawBson, - FindManySerde, - GridFsDownload, - GridFsUpload, - GridFsMultiDownload, - GridFsMultiUpload, + FindOneById, // 2 + SmallDocInsertOne, // 3 + LargeDocInsertOne, // 4 + FindMany, // 5 + SmallDocInsertMany, // 6 + LargeDocInsertMany, // 7 + LdJsonMultiFileImport, // 8 + LdJsonMultiFileExport, // 9 + BsonFlatDocumentDecode, // 10 + BsonFlatDocumentEncode, // 11 + BsonDeepDocumentDecode, // 12 + BsonDeepDocumentEncode, // 13 + BsonFullDocumentDecode, // 14 + BsonFullDocumentEncode, // 15 + FindManyRawBson, // 16 + FindManySerde, // 17 + GridFsDownload, // 18 + GridFsUpload, // 19 + GridFsMultiDownload, // 20 + GridFsMultiUpload, // 21 + RunCommandColdStart, // 22 + SmallDocInsertBulkWrite, // 23 + LargeDocInsertBulkWrite, // 24 + MixedBulkWrite, // 25 } impl BenchmarkId { fn name(self) -> &'static str { match self { BenchmarkId::RunCommand => RUN_COMMAND_BENCH, + BenchmarkId::RunCommandColdStart => RUN_COMMAND_COLD_START_BENCH, BenchmarkId::FindOneById => FIND_ONE_BENCH, BenchmarkId::SmallDocInsertOne => SMALL_DOC_INSERT_ONE_BENCH, BenchmarkId::LargeDocInsertOne => LARGE_DOC_INSERT_ONE_BENCH, BenchmarkId::FindMany => FIND_MANY_BENCH, - BenchmarkId::SmallDocBulkInsert => SMALL_DOC_BULK_INSERT_BENCH, - BenchmarkId::LargeDocBulkInsert => LARGE_DOC_BULK_INSERT_BENCH, + BenchmarkId::SmallDocInsertMany => SMALL_DOC_INSERT_MANY_BENCH, + BenchmarkId::LargeDocInsertMany => LARGE_DOC_INSERT_MANY_BENCH, BenchmarkId::LdJsonMultiFileImport => LDJSON_MULTI_IMPORT_BENCH, BenchmarkId::LdJsonMultiFileExport => LDJSON_MULTI_EXPORT_BENCH, BenchmarkId::BsonFlatDocumentDecode => FLAT_BSON_DECODING, @@ -146,12 +131,15 @@ impl BenchmarkId { BenchmarkId::GridFsUpload => GRIDFS_UPLOAD_BENCH, BenchmarkId::GridFsMultiDownload => GRIDFS_MULTI_DOWNLOAD_BENCH, BenchmarkId::GridFsMultiUpload => GRIDFS_MULTI_UPLOAD_BENCH, + BenchmarkId::SmallDocInsertBulkWrite => SMALL_DOC_INSERT_BULK_WRITE_BENCH, + BenchmarkId::LargeDocInsertBulkWrite => LARGE_DOC_INSERT_BULK_WRITE_BENCH, + BenchmarkId::MixedBulkWrite => MIXED_BULK_WRITE_BENCH, } } } /// Benchmarks included in the "BSONBench" composite. -const BSON_BENCHES: &[&'static str] = &[ +const BSON_BENCHES: &[&str] = &[ FLAT_BSON_ENCODING, FLAT_BSON_DECODING, DEEP_BSON_ENCODING, @@ -162,23 +150,26 @@ const BSON_BENCHES: &[&'static str] = &[ /// Benchmarkes included in the "SingleBench" composite. /// This consists of all the single-doc benchmarks except Run Command. -const SINGLE_BENCHES: &[&'static str] = &[ +const SINGLE_BENCHES: &[&str] = &[ FIND_ONE_BENCH, SMALL_DOC_INSERT_ONE_BENCH, LARGE_DOC_INSERT_ONE_BENCH, ]; /// Benchmarks included in the "MultiBench" composite. -const MULTI_BENCHES: &[&'static str] = &[ +const MULTI_BENCHES: &[&str] = &[ FIND_MANY_BENCH_RAW, - SMALL_DOC_BULK_INSERT_BENCH, - LARGE_DOC_BULK_INSERT_BENCH, + SMALL_DOC_INSERT_MANY_BENCH, + LARGE_DOC_INSERT_MANY_BENCH, GRIDFS_UPLOAD_BENCH, GRIDFS_DOWNLOAD_BENCH, + SMALL_DOC_INSERT_BULK_WRITE_BENCH, + LARGE_DOC_INSERT_BULK_WRITE_BENCH, + MIXED_BULK_WRITE_BENCH, ]; /// Benchmarks included in the "ParallelBench" composite. -const PARALLEL_BENCHES: &[&'static str] = &[ +const PARALLEL_BENCHES: &[&str] = &[ LDJSON_MULTI_IMPORT_BENCH, LDJSON_MULTI_EXPORT_BENCH, GRIDFS_MULTI_UPLOAD_BENCH, @@ -186,7 +177,7 @@ const PARALLEL_BENCHES: &[&'static str] = &[ ]; /// Benchmarks included in the "ReadBench" composite. -const READ_BENCHES: &[&'static str] = &[ +const READ_BENCHES: &[&str] = &[ FIND_ONE_BENCH, FIND_MANY_BENCH_RAW, GRIDFS_DOWNLOAD_BENCH, @@ -195,17 +186,20 @@ const READ_BENCHES: &[&'static str] = &[ ]; /// Benchmarks included in the "WriteBench" composite. -const WRITE_BENCHES: &[&'static str] = &[ +const WRITE_BENCHES: &[&str] = &[ SMALL_DOC_INSERT_ONE_BENCH, LARGE_DOC_INSERT_ONE_BENCH, - SMALL_DOC_BULK_INSERT_BENCH, - LARGE_DOC_BULK_INSERT_BENCH, + SMALL_DOC_INSERT_MANY_BENCH, + LARGE_DOC_INSERT_MANY_BENCH, GRIDFS_UPLOAD_BENCH, LDJSON_MULTI_IMPORT_BENCH, GRIDFS_MULTI_UPLOAD_BENCH, + SMALL_DOC_INSERT_BULK_WRITE_BENCH, + LARGE_DOC_INSERT_BULK_WRITE_BENCH, + MIXED_BULK_WRITE_BENCH, ]; -const MAX_ID: u8 = BenchmarkId::GridFsMultiUpload as u8; +const MAX_ID: u8 = BenchmarkId::MixedBulkWrite as u8; async fn run_benchmarks( uri: &str, @@ -227,6 +221,7 @@ async fn run_benchmarks( let run_command_options = bench::run_command::Options { num_iter: 10000, uri: uri.to_string(), + cold_start: false, }; let run_command = bench::run_benchmark::(run_command_options).await?; @@ -234,13 +229,25 @@ async fn run_benchmarks( comp_score += score_test(run_command, RUN_COMMAND_BENCH, 0.13, more_info); } + // Run command, including client setup time + BenchmarkId::RunCommandColdStart => { + let run_command_options = bench::run_command::Options { + num_iter: 100, + uri: uri.to_string(), + cold_start: true, + }; + let run_command = + bench::run_benchmark::(run_command_options).await?; + + comp_score += + score_test(run_command, RUN_COMMAND_COLD_START_BENCH, 0.13, more_info); + } + // Small doc insertOne BenchmarkId::SmallDocInsertOne => { let small_insert_one_options = bench::insert_one::Options { num_iter: 10000, - path: DATA_PATH - .join("single_and_multi_document") - .join("small_doc.json"), + doc: get_small_doc().await, uri: uri.to_string(), }; let small_insert_one = @@ -258,9 +265,7 @@ async fn run_benchmarks( BenchmarkId::LargeDocInsertOne => { let large_insert_one_options = bench::insert_one::Options { num_iter: 10, - path: DATA_PATH - .join("single_and_multi_document") - .join("large_doc.json"), + doc: get_large_doc().await, uri: uri.to_string(), }; let large_insert_one = @@ -275,12 +280,10 @@ async fn run_benchmarks( } // Small doc bulk insert - BenchmarkId::SmallDocBulkInsert => { + BenchmarkId::SmallDocInsertMany => { let small_insert_many_options = bench::insert_many::Options { num_copies: 10000, - path: DATA_PATH - .join("single_and_multi_document") - .join("small_doc.json"), + doc: get_small_doc().await, uri: uri.to_string(), }; let small_insert_many = @@ -288,19 +291,17 @@ async fn run_benchmarks( comp_score += score_test( small_insert_many, - SMALL_DOC_BULK_INSERT_BENCH, + SMALL_DOC_INSERT_MANY_BENCH, 2.75, more_info, ); } // Large doc bulk insert - BenchmarkId::LargeDocBulkInsert => { + BenchmarkId::LargeDocInsertMany => { let large_insert_many_options = bench::insert_many::Options { num_copies: 10, - path: DATA_PATH - .join("single_and_multi_document") - .join("large_doc.json"), + doc: get_large_doc().await, uri: uri.to_string(), }; let large_insert_many = @@ -308,7 +309,7 @@ async fn run_benchmarks( comp_score += score_test( large_insert_many, - LARGE_DOC_BULK_INSERT_BENCH, + LARGE_DOC_INSERT_MANY_BENCH, 27.31, more_info, ); @@ -365,7 +366,7 @@ async fn run_benchmarks( BenchmarkId::BsonFlatDocumentDecode => { let bson_flat_decode_options = bench::bson_decode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("flat_bson.json"), + doc: get_flat_bson().await, }; let bson_flat_decode = bench::run_benchmark::(bson_flat_decode_options).await?; @@ -377,7 +378,7 @@ async fn run_benchmarks( BenchmarkId::BsonFlatDocumentEncode => { let bson_flat_encode_options = bench::bson_encode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("flat_bson.json"), + doc: get_flat_bson().await, }; let bson_flat_encode = bench::run_benchmark::(bson_flat_encode_options).await?; @@ -389,7 +390,7 @@ async fn run_benchmarks( BenchmarkId::BsonDeepDocumentDecode => { let bson_deep_decode_options = bench::bson_decode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("deep_bson.json"), + doc: get_deep_bson().await, }; let bson_deep_decode = bench::run_benchmark::(bson_deep_decode_options).await?; @@ -401,7 +402,7 @@ async fn run_benchmarks( BenchmarkId::BsonDeepDocumentEncode => { let bson_deep_encode_options = bench::bson_encode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("deep_bson.json"), + doc: get_deep_bson().await, }; let bson_deep_encode = bench::run_benchmark::(bson_deep_encode_options).await?; @@ -413,7 +414,7 @@ async fn run_benchmarks( BenchmarkId::BsonFullDocumentDecode => { let bson_full_decode_options = bench::bson_decode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("full_bson.json"), + doc: get_full_bson().await, }; let bson_full_decode = bench::run_benchmark::(bson_full_decode_options).await?; @@ -425,7 +426,7 @@ async fn run_benchmarks( BenchmarkId::BsonFullDocumentEncode => { let bson_full_encode_options = bench::bson_encode::Options { num_iter: 10_000, - path: DATA_PATH.join("extended_bson").join("full_bson.json"), + doc: get_full_bson().await, }; let bson_full_encode = bench::run_benchmark::(bson_full_encode_options).await?; @@ -437,9 +438,7 @@ async fn run_benchmarks( BenchmarkId::FindOneById => { let find_one_options = bench::find_one::Options { num_iter: 10000, - path: DATA_PATH - .join("single_and_multi_document") - .join("tweet.json"), + doc: get_tweet().await, uri: uri.to_string(), }; let find_one = bench::run_benchmark::(find_one_options).await?; @@ -457,9 +456,7 @@ async fn run_benchmarks( }; let find_many_options = bench::find_many::Options { num_iter: 10000, - path: DATA_PATH - .join("single_and_multi_document") - .join("tweet.json"), + doc: get_tweet().await, uri: uri.to_string(), mode, }; @@ -524,6 +521,42 @@ async fn run_benchmarks( comp_score += score_test(gridfs_multi_upload, id.name(), 262.144, more_info); } + + // Small doc insert-only bulk write + BenchmarkId::SmallDocInsertBulkWrite => { + let bulk_write_options = bench::bulk_write::Options { + uri: uri.to_string(), + doc: get_small_doc().await, + num_models: 10_000, + }; + let small_doc_insert_bulk_write = + bench::run_benchmark::(bulk_write_options).await?; + comp_score += score_test(small_doc_insert_bulk_write, id.name(), 2.75, more_info); + } + + // Large doc insert-only bulk write + BenchmarkId::LargeDocInsertBulkWrite => { + let bulk_write_options = bench::bulk_write::Options { + uri: uri.to_string(), + doc: get_large_doc().await, + num_models: 10, + }; + let large_doc_insert_bulk_write = + bench::run_benchmark::(bulk_write_options).await?; + comp_score += score_test(large_doc_insert_bulk_write, id.name(), 27.31, more_info); + } + + // Mixed bulk write + BenchmarkId::MixedBulkWrite => { + let bulk_write_options = bench::bulk_write::Options { + uri: uri.to_string(), + doc: get_small_doc().await, + num_models: 30_000, + }; + let mixed_bulk_write = + bench::run_benchmark::(bulk_write_options).await?; + comp_score += score_test(mixed_bulk_write, id.name(), 5.5, more_info); + } } } @@ -533,7 +566,7 @@ async fn run_benchmarks( fn parse_ids(matches: ArgMatches) -> HashSet { let mut ids: HashSet = match matches.value_of("ids") { Some("all") => (1..=MAX_ID) - .map(|id| BenchmarkId::try_from(id as u8).unwrap()) + .map(|id| BenchmarkId::try_from(id).unwrap()) .collect(), Some(id_list) => id_list .split(',') @@ -549,22 +582,27 @@ fn parse_ids(matches: ArgMatches) -> HashSet { if matches.is_present("single") { ids.insert(BenchmarkId::RunCommand); + ids.insert(BenchmarkId::RunCommandColdStart); ids.insert(BenchmarkId::FindOneById); ids.insert(BenchmarkId::SmallDocInsertOne); ids.insert(BenchmarkId::LargeDocInsertOne); } if matches.is_present("multi") { ids.insert(BenchmarkId::FindManyRawBson); - ids.insert(BenchmarkId::SmallDocBulkInsert); - ids.insert(BenchmarkId::LargeDocBulkInsert); + ids.insert(BenchmarkId::SmallDocInsertMany); + ids.insert(BenchmarkId::LargeDocInsertMany); ids.insert(BenchmarkId::GridFsDownload); ids.insert(BenchmarkId::GridFsUpload); + ids.insert(BenchmarkId::SmallDocInsertBulkWrite); + ids.insert(BenchmarkId::LargeDocInsertBulkWrite); + ids.insert(BenchmarkId::MixedBulkWrite); } if matches.is_present("parallel") { ids.insert(BenchmarkId::LdJsonMultiFileImport); ids.insert(BenchmarkId::LdJsonMultiFileExport); ids.insert(BenchmarkId::GridFsMultiDownload); - ids.insert(BenchmarkId::GridFsMultiUpload); + // TODO RUST-2010 Re-enable this benchmark + //ids.insert(BenchmarkId::GridFsMultiUpload); } if matches.is_present("bson") { ids.insert(BenchmarkId::BsonFlatDocumentDecode); @@ -576,34 +614,40 @@ fn parse_ids(matches: ArgMatches) -> HashSet { } if matches.is_present("driver") { ids.insert(BenchmarkId::RunCommand); + ids.insert(BenchmarkId::RunCommandColdStart); ids.insert(BenchmarkId::FindOneById); ids.insert(BenchmarkId::SmallDocInsertOne); ids.insert(BenchmarkId::LargeDocInsertOne); ids.insert(BenchmarkId::FindMany); ids.insert(BenchmarkId::FindManyRawBson); ids.insert(BenchmarkId::FindManySerde); - ids.insert(BenchmarkId::SmallDocBulkInsert); - ids.insert(BenchmarkId::LargeDocBulkInsert); + ids.insert(BenchmarkId::SmallDocInsertMany); + ids.insert(BenchmarkId::LargeDocInsertMany); ids.insert(BenchmarkId::LdJsonMultiFileImport); ids.insert(BenchmarkId::LdJsonMultiFileExport); ids.insert(BenchmarkId::GridFsDownload); ids.insert(BenchmarkId::GridFsUpload); ids.insert(BenchmarkId::GridFsMultiDownload); - ids.insert(BenchmarkId::GridFsMultiUpload); + ids.insert(BenchmarkId::SmallDocInsertBulkWrite); + ids.insert(BenchmarkId::LargeDocInsertBulkWrite); + ids.insert(BenchmarkId::MixedBulkWrite); + // TODO RUST-2010 Re-enable this benchmark + //ids.insert(BenchmarkId::GridFsMultiUpload); } // if none were enabled, that means no arguments were provided and all should be enabled. if ids.is_empty() { ids = (1..=MAX_ID) - .map(|id| BenchmarkId::try_from(id as u8).unwrap()) + .map(|id| BenchmarkId::try_from(id).unwrap()) + // TODO RUST-2010 Re-enable this benchmark + .filter(|id| *id != BenchmarkId::GridFsMultiUpload) .collect() } ids } -#[cfg_attr(feature = "tokio-runtime", tokio::main)] -#[cfg_attr(feature = "async-std-runtime", async_std::main)] +#[tokio::main] async fn main() { // ensure MAX_ID is kept up to date. assert!( @@ -611,6 +655,13 @@ async fn main() { "MAX_ID not up to date" ); + let mut id_help = String::from("\nRun benchmarks by id number (comma-separated):\n"); + for ix in 1..=MAX_ID { + let id = BenchmarkId::try_from(ix).unwrap(); + id_help.push_str(&format!(" {}: {}\n", ix, id.name())); + } + id_help.push_str(" all: All benchmarks\n "); + let matches = App::new("RustDriverBenchmark") .version(env!("CARGO_PKG_VERSION")) .about("Runs performance micro-bench") @@ -657,33 +708,7 @@ async fn main() { .long("ids") .takes_value(true) .help("Run benchmarks by id number (comma-separated)") - .long_help( - " -Run benchmarks by id number (comma-separated): - 1: Run command - 2: Find one by ID - 3: Small doc insertOne - 4: Large doc insertOne - 5: Find many and empty the cursor - 6: Small doc bulk insert - 7: Large doc bulk insert - 8: LDJSON multi-file import - 9: LDJSON multi-file export - 10: BSON flat document decode - 11: BSON flat document encode - 12: BSON deeply nested document decode - 13: BSON deeply nested document encode - 14: BSON full document decode - 15: BSON full document encode - 16: Find many and empty the cursor (raw BSON) - 17: Find many and empty the cursor (serde structs) - 18: GridFS download - 19: GridFS upload - 20: GridFS multi-file download - 21: GridFS multi-file upload - all: All benchmarks - ", - ), + .long_help(&id_help), ) .arg( Arg::with_name("output") diff --git a/benchmarks/src/score.rs b/benchmarks/src/score.rs index cb38887b1..07bd12554 100644 --- a/benchmarks/src/score.rs +++ b/benchmarks/src/score.rs @@ -3,7 +3,7 @@ use std::time::Duration; use mongodb::bson::Document; use serde::Serialize; -const SCORE_VALUE_NAME: &'static str = "score"; +const SCORE_VALUE_NAME: &str = "score"; #[derive(Debug, Clone)] pub(crate) struct BenchmarkScore { diff --git a/clippy.toml b/clippy.toml index c9e0b58bf..c3aa6421b 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.57" \ No newline at end of file +msrv = "1.82.0" diff --git a/deny.toml b/deny.toml index 541b98ee5..c2152c26c 100644 --- a/deny.toml +++ b/deny.toml @@ -9,24 +9,6 @@ # The values provided in this template are the default values that will be used # when any section or field is not specified in your own configuration -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - #{ triple = "x86_64-unknown-linux-musl" }, - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] - # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html @@ -35,35 +17,16 @@ targets = [ # db-path = "~/.cargo/advisory-db" # The url(s) of the advisory databases to use db-urls = ["https://github.com/rustsec/advisory-db"] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "deny" # The lint level for crates that have been yanked from their source registry yanked = "deny" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "deny" # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. ignore = [] -# Threshold for security vulnerabilities, any vulnerability with a CVSS score -# lower than the range specified will be ignored. Note that ignored advisories -# will still output a note when they are encountered. -# * None - CVSS Score 0.0 -# * Low - CVSS Score 0.1 - 3.9 -# * Medium - CVSS Score 4.0 - 6.9 -# * High - CVSS Score 7.0 - 8.9 -# * Critical - CVSS Score 9.0 - 10.0 -#severity-threshold = # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -# The lint level for crates which do not have a detectable license -unlicensed = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -71,32 +34,16 @@ allow = [ "MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", + "CC0-1.0", + "CDLA-Permissive-2.0", "ISC", "OpenSSL", + "BSD-2-Clause", "BSD-3-Clause", "MPL-2.0", "Unicode-DFS-2016", + "Unicode-3.0", ] -# List of explicitly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -deny = [ - #"Nokia", -] -# Lint level for licenses considered copyleft -copyleft = "deny" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "neither" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -209,3 +156,22 @@ allow-git = [ # gitlab = [""] # 1 or more bitbucket.org organizations to allow git sources for # bitbucket = [""] + +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #{ triple = "x86_64-unknown-linux-musl" }, + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] diff --git a/docs/.gitattributes b/docs/.gitattributes deleted file mode 100644 index f2e2bb58e..000000000 --- a/docs/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -manual/** linguist-generated \ No newline at end of file diff --git a/docs/manual/.nojekyll b/docs/manual/.nojekyll deleted file mode 100644 index f17311098..000000000 --- a/docs/manual/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/docs/manual/404.html b/docs/manual/404.html deleted file mode 100644 index 3a29f3d9f..000000000 --- a/docs/manual/404.html +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - Page not found - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Document not found (404)

-

This URL is invalid, sorry. Please use the navigation bar or search to continue.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/FontAwesome/css/font-awesome.css b/docs/manual/FontAwesome/css/font-awesome.css deleted file mode 100644 index 540440ce8..000000000 --- a/docs/manual/FontAwesome/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/docs/manual/FontAwesome/fonts/FontAwesome.ttf b/docs/manual/FontAwesome/fonts/FontAwesome.ttf deleted file mode 100644 index 35acda2fa..000000000 Binary files a/docs/manual/FontAwesome/fonts/FontAwesome.ttf and /dev/null differ diff --git a/docs/manual/FontAwesome/fonts/fontawesome-webfont.eot b/docs/manual/FontAwesome/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca95..000000000 Binary files a/docs/manual/FontAwesome/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/manual/FontAwesome/fonts/fontawesome-webfont.svg b/docs/manual/FontAwesome/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e5..000000000 --- a/docs/manual/FontAwesome/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/FontAwesome/fonts/fontawesome-webfont.ttf b/docs/manual/FontAwesome/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2fa..000000000 Binary files a/docs/manual/FontAwesome/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff b/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4b..000000000 Binary files a/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff2 b/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc604..000000000 Binary files a/docs/manual/FontAwesome/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/manual/ayu-highlight.css b/docs/manual/ayu-highlight.css deleted file mode 100644 index 32c943222..000000000 --- a/docs/manual/ayu-highlight.css +++ /dev/null @@ -1,78 +0,0 @@ -/* -Based off of the Ayu theme -Original by Dempfi (https://github.com/dempfi/ayu) -*/ - -.hljs { - display: block; - overflow-x: auto; - background: #191f26; - color: #e6e1cf; -} - -.hljs-comment, -.hljs-quote { - color: #5c6773; - font-style: italic; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-attr, -.hljs-regexp, -.hljs-link, -.hljs-selector-id, -.hljs-selector-class { - color: #ff7733; -} - -.hljs-number, -.hljs-meta, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ffee99; -} - -.hljs-string, -.hljs-bullet { - color: #b8cc52; -} - -.hljs-title, -.hljs-built_in, -.hljs-section { - color: #ffb454; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-symbol { - color: #ff7733; -} - -.hljs-name { - color: #36a3d9; -} - -.hljs-tag { - color: #00568d; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-addition { - color: #91b362; -} - -.hljs-deletion { - color: #d96c75; -} diff --git a/docs/manual/book.js b/docs/manual/book.js deleted file mode 100644 index e303ebb45..000000000 --- a/docs/manual/book.js +++ /dev/null @@ -1,688 +0,0 @@ -"use strict"; - -// Fix back button cache problem -window.onunload = function () { }; - -// Global variable, shared between modules -function playground_text(playground, hidden = true) { - let code_block = playground.querySelector("code"); - - if (window.ace && code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - return editor.getValue(); - } else if (hidden) { - return code_block.textContent; - } else { - return code_block.innerText; - } -} - -(function codeSnippets() { - function fetch_with_timeout(url, options, timeout = 6000) { - return Promise.race([ - fetch(url, options), - new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) - ]); - } - - var playgrounds = Array.from(document.querySelectorAll(".playground")); - if (playgrounds.length > 0) { - fetch_with_timeout("https://play.rust-lang.org/meta/crates", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - }) - .then(response => response.json()) - .then(response => { - // get list of crates available in the rust playground - let playground_crates = response.crates.map(item => item["id"]); - playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); - }); - } - - function handle_crate_list_update(playground_block, playground_crates) { - // update the play buttons after receiving the response - update_play_button(playground_block, playground_crates); - - // and install on change listener to dynamically update ACE editors - if (window.ace) { - let code_block = playground_block.querySelector("code"); - if (code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - editor.addEventListener("change", function (e) { - update_play_button(playground_block, playground_crates); - }); - // add Ctrl-Enter command to execute rust code - editor.commands.addCommand({ - name: "run", - bindKey: { - win: "Ctrl-Enter", - mac: "Ctrl-Enter" - }, - exec: _editor => run_rust_code(playground_block) - }); - } - } - } - - // updates the visibility of play button based on `no_run` class and - // used crates vs ones available on http://play.rust-lang.org - function update_play_button(pre_block, playground_crates) { - var play_button = pre_block.querySelector(".play-button"); - - // skip if code is `no_run` - if (pre_block.querySelector('code').classList.contains("no_run")) { - play_button.classList.add("hidden"); - return; - } - - // get list of `extern crate`'s from snippet - var txt = playground_text(pre_block); - var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; - var snippet_crates = []; - var item; - while (item = re.exec(txt)) { - snippet_crates.push(item[1]); - } - - // check if all used crates are available on play.rust-lang.org - var all_available = snippet_crates.every(function (elem) { - return playground_crates.indexOf(elem) > -1; - }); - - if (all_available) { - play_button.classList.remove("hidden"); - } else { - play_button.classList.add("hidden"); - } - } - - function run_rust_code(code_block) { - var result_block = code_block.querySelector(".result"); - if (!result_block) { - result_block = document.createElement('code'); - result_block.className = 'result hljs language-bash'; - - code_block.append(result_block); - } - - let text = playground_text(code_block); - let classes = code_block.querySelector('code').classList; - let edition = "2015"; - if(classes.contains("edition2018")) { - edition = "2018"; - } else if(classes.contains("edition2021")) { - edition = "2021"; - } - var params = { - version: "stable", - optimize: "0", - code: text, - edition: edition - }; - - if (text.indexOf("#![feature") !== -1) { - params.version = "nightly"; - } - - result_block.innerText = "Running..."; - - fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - body: JSON.stringify(params) - }) - .then(response => response.json()) - .then(response => { - if (response.result.trim() === '') { - result_block.innerText = "No output"; - result_block.classList.add("result-no-output"); - } else { - result_block.innerText = response.result; - result_block.classList.remove("result-no-output"); - } - }) - .catch(error => result_block.innerText = "Playground Communication: " + error.message); - } - - // Syntax highlighting Configuration - hljs.configure({ - tabReplace: ' ', // 4 spaces - languages: [], // Languages used for auto-detection - }); - - let code_nodes = Array - .from(document.querySelectorAll('code')) - // Don't highlight `inline code` blocks in headers. - .filter(function (node) {return !node.parentElement.classList.contains("header"); }); - - if (window.ace) { - // language-rust class needs to be removed for editable - // blocks or highlightjs will capture events - code_nodes - .filter(function (node) {return node.classList.contains("editable"); }) - .forEach(function (block) { block.classList.remove('language-rust'); }); - - code_nodes - .filter(function (node) {return !node.classList.contains("editable"); }) - .forEach(function (block) { hljs.highlightBlock(block); }); - } else { - code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); - } - - // Adding the hljs class gives code blocks the color css - // even if highlighting doesn't apply - code_nodes.forEach(function (block) { block.classList.add('hljs'); }); - - Array.from(document.querySelectorAll("code.language-rust")).forEach(function (block) { - - var lines = Array.from(block.querySelectorAll('.boring')); - // If no lines were hidden, return - if (!lines.length) { return; } - block.classList.add("hide-boring"); - - var buttons = document.createElement('div'); - buttons.className = 'buttons'; - buttons.innerHTML = ""; - - // add expand button - var pre_block = block.parentNode; - pre_block.insertBefore(buttons, pre_block.firstChild); - - pre_block.querySelector('.buttons').addEventListener('click', function (e) { - if (e.target.classList.contains('fa-eye')) { - e.target.classList.remove('fa-eye'); - e.target.classList.add('fa-eye-slash'); - e.target.title = 'Hide lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.remove('hide-boring'); - } else if (e.target.classList.contains('fa-eye-slash')) { - e.target.classList.remove('fa-eye-slash'); - e.target.classList.add('fa-eye'); - e.target.title = 'Show hidden lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.add('hide-boring'); - } - }); - }); - - if (window.playground_copyable) { - Array.from(document.querySelectorAll('pre code')).forEach(function (block) { - var pre_block = block.parentNode; - if (!pre_block.classList.contains('playground')) { - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var clipButton = document.createElement('button'); - clipButton.className = 'fa fa-copy clip-button'; - clipButton.title = 'Copy to clipboard'; - clipButton.setAttribute('aria-label', clipButton.title); - clipButton.innerHTML = ''; - - buttons.insertBefore(clipButton, buttons.firstChild); - } - }); - } - - // Process playground code blocks - Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { - // Add play button - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var runCodeButton = document.createElement('button'); - runCodeButton.className = 'fa fa-play play-button'; - runCodeButton.hidden = true; - runCodeButton.title = 'Run this code'; - runCodeButton.setAttribute('aria-label', runCodeButton.title); - - buttons.insertBefore(runCodeButton, buttons.firstChild); - runCodeButton.addEventListener('click', function (e) { - run_rust_code(pre_block); - }); - - if (window.playground_copyable) { - var copyCodeClipboardButton = document.createElement('button'); - copyCodeClipboardButton.className = 'fa fa-copy clip-button'; - copyCodeClipboardButton.innerHTML = ''; - copyCodeClipboardButton.title = 'Copy to clipboard'; - copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); - - buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); - } - - let code_block = pre_block.querySelector("code"); - if (window.ace && code_block.classList.contains("editable")) { - var undoChangesButton = document.createElement('button'); - undoChangesButton.className = 'fa fa-history reset-button'; - undoChangesButton.title = 'Undo changes'; - undoChangesButton.setAttribute('aria-label', undoChangesButton.title); - - buttons.insertBefore(undoChangesButton, buttons.firstChild); - - undoChangesButton.addEventListener('click', function () { - let editor = window.ace.edit(code_block); - editor.setValue(editor.originalCode); - editor.clearSelection(); - }); - } - }); -})(); - -(function themes() { - var html = document.querySelector('html'); - var themeToggleButton = document.getElementById('theme-toggle'); - var themePopup = document.getElementById('theme-list'); - var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); - var stylesheets = { - ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), - tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), - highlight: document.querySelector("[href$='highlight.css']"), - }; - - function showThemes() { - themePopup.style.display = 'block'; - themeToggleButton.setAttribute('aria-expanded', true); - themePopup.querySelector("button#" + get_theme()).focus(); - } - - function updateThemeSelected() { - themePopup.querySelectorAll('.theme-selected').forEach(function (el) { - el.classList.remove('theme-selected'); - }); - themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); - } - - function hideThemes() { - themePopup.style.display = 'none'; - themeToggleButton.setAttribute('aria-expanded', false); - themeToggleButton.focus(); - } - - function get_theme() { - var theme; - try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } - if (theme === null || theme === undefined) { - return default_theme; - } else { - return theme; - } - } - - function set_theme(theme, store = true) { - let ace_theme; - - if (theme == 'coal' || theme == 'navy') { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = false; - stylesheets.highlight.disabled = true; - - ace_theme = "ace/theme/tomorrow_night"; - } else if (theme == 'ayu') { - stylesheets.ayuHighlight.disabled = false; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = true; - ace_theme = "ace/theme/tomorrow_night"; - } else { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = false; - ace_theme = "ace/theme/dawn"; - } - - setTimeout(function () { - themeColorMetaTag.content = getComputedStyle(document.body).backgroundColor; - }, 1); - - if (window.ace && window.editors) { - window.editors.forEach(function (editor) { - editor.setTheme(ace_theme); - }); - } - - var previousTheme = get_theme(); - - if (store) { - try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } - } - - html.classList.remove(previousTheme); - html.classList.add(theme); - updateThemeSelected(); - } - - // Set theme - var theme = get_theme(); - - set_theme(theme, false); - - themeToggleButton.addEventListener('click', function () { - if (themePopup.style.display === 'block') { - hideThemes(); - } else { - showThemes(); - } - }); - - themePopup.addEventListener('click', function (e) { - var theme; - if (e.target.className === "theme") { - theme = e.target.id; - } else if (e.target.parentElement.className === "theme") { - theme = e.target.parentElement.id; - } else { - return; - } - set_theme(theme); - }); - - themePopup.addEventListener('focusout', function(e) { - // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) - if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { - hideThemes(); - } - }); - - // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 - document.addEventListener('click', function(e) { - if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { - hideThemes(); - } - }); - - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (!themePopup.contains(e.target)) { return; } - - switch (e.key) { - case 'Escape': - e.preventDefault(); - hideThemes(); - break; - case 'ArrowUp': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.previousElementSibling) { - li.previousElementSibling.querySelector('button').focus(); - } - break; - case 'ArrowDown': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.nextElementSibling) { - li.nextElementSibling.querySelector('button').focus(); - } - break; - case 'Home': - e.preventDefault(); - themePopup.querySelector('li:first-child button').focus(); - break; - case 'End': - e.preventDefault(); - themePopup.querySelector('li:last-child button').focus(); - break; - } - }); -})(); - -(function sidebar() { - var html = document.querySelector("html"); - var sidebar = document.getElementById("sidebar"); - var sidebarLinks = document.querySelectorAll('#sidebar a'); - var sidebarToggleButton = document.getElementById("sidebar-toggle"); - var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); - var firstContact = null; - - function showSidebar() { - html.classList.remove('sidebar-hidden') - html.classList.add('sidebar-visible'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', 0); - }); - sidebarToggleButton.setAttribute('aria-expanded', true); - sidebar.setAttribute('aria-hidden', false); - try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } - } - - - var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); - - function toggleSection(ev) { - ev.currentTarget.parentElement.classList.toggle('expanded'); - } - - Array.from(sidebarAnchorToggles).forEach(function (el) { - el.addEventListener('click', toggleSection); - }); - - function hideSidebar() { - html.classList.remove('sidebar-visible') - html.classList.add('sidebar-hidden'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', -1); - }); - sidebarToggleButton.setAttribute('aria-expanded', false); - sidebar.setAttribute('aria-hidden', true); - try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } - } - - // Toggle sidebar - sidebarToggleButton.addEventListener('click', function sidebarToggle() { - if (html.classList.contains("sidebar-hidden")) { - var current_width = parseInt( - document.documentElement.style.getPropertyValue('--sidebar-width'), 10); - if (current_width < 150) { - document.documentElement.style.setProperty('--sidebar-width', '150px'); - } - showSidebar(); - } else if (html.classList.contains("sidebar-visible")) { - hideSidebar(); - } else { - if (getComputedStyle(sidebar)['transform'] === 'none') { - hideSidebar(); - } else { - showSidebar(); - } - } - }); - - sidebarResizeHandle.addEventListener('mousedown', initResize, false); - - function initResize(e) { - window.addEventListener('mousemove', resize, false); - window.addEventListener('mouseup', stopResize, false); - html.classList.add('sidebar-resizing'); - } - function resize(e) { - var pos = (e.clientX - sidebar.offsetLeft); - if (pos < 20) { - hideSidebar(); - } else { - if (html.classList.contains("sidebar-hidden")) { - showSidebar(); - } - pos = Math.min(pos, window.innerWidth - 100); - document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); - } - } - //on mouseup remove windows functions mousemove & mouseup - function stopResize(e) { - html.classList.remove('sidebar-resizing'); - window.removeEventListener('mousemove', resize, false); - window.removeEventListener('mouseup', stopResize, false); - } - - document.addEventListener('touchstart', function (e) { - firstContact = { - x: e.touches[0].clientX, - time: Date.now() - }; - }, { passive: true }); - - document.addEventListener('touchmove', function (e) { - if (!firstContact) - return; - - var curX = e.touches[0].clientX; - var xDiff = curX - firstContact.x, - tDiff = Date.now() - firstContact.time; - - if (tDiff < 250 && Math.abs(xDiff) >= 150) { - if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) - showSidebar(); - else if (xDiff < 0 && curX < 300) - hideSidebar(); - - firstContact = null; - } - }, { passive: true }); - - // Scroll sidebar to current active section - var activeSection = document.getElementById("sidebar").querySelector(".active"); - if (activeSection) { - // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView - activeSection.scrollIntoView({ block: 'center' }); - } -})(); - -(function chapterNavigation() { - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (window.search && window.search.hasFocus()) { return; } - - switch (e.key) { - case 'ArrowRight': - e.preventDefault(); - var nextButton = document.querySelector('.nav-chapters.next'); - if (nextButton) { - window.location.href = nextButton.href; - } - break; - case 'ArrowLeft': - e.preventDefault(); - var previousButton = document.querySelector('.nav-chapters.previous'); - if (previousButton) { - window.location.href = previousButton.href; - } - break; - } - }); -})(); - -(function clipboard() { - var clipButtons = document.querySelectorAll('.clip-button'); - - function hideTooltip(elem) { - elem.firstChild.innerText = ""; - elem.className = 'fa fa-copy clip-button'; - } - - function showTooltip(elem, msg) { - elem.firstChild.innerText = msg; - elem.className = 'fa fa-copy tooltipped'; - } - - var clipboardSnippets = new ClipboardJS('.clip-button', { - text: function (trigger) { - hideTooltip(trigger); - let playground = trigger.closest("pre"); - return playground_text(playground, false); - } - }); - - Array.from(clipButtons).forEach(function (clipButton) { - clipButton.addEventListener('mouseout', function (e) { - hideTooltip(e.currentTarget); - }); - }); - - clipboardSnippets.on('success', function (e) { - e.clearSelection(); - showTooltip(e.trigger, "Copied!"); - }); - - clipboardSnippets.on('error', function (e) { - showTooltip(e.trigger, "Clipboard error!"); - }); -})(); - -(function scrollToTop () { - var menuTitle = document.querySelector('.menu-title'); - - menuTitle.addEventListener('click', function () { - document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); - }); -})(); - -(function controllMenu() { - var menu = document.getElementById('menu-bar'); - - (function controllPosition() { - var scrollTop = document.scrollingElement.scrollTop; - var prevScrollTop = scrollTop; - var minMenuY = -menu.clientHeight - 50; - // When the script loads, the page can be at any scroll (e.g. if you reforesh it). - menu.style.top = scrollTop + 'px'; - // Same as parseInt(menu.style.top.slice(0, -2), but faster - var topCache = menu.style.top.slice(0, -2); - menu.classList.remove('sticky'); - var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster - document.addEventListener('scroll', function () { - scrollTop = Math.max(document.scrollingElement.scrollTop, 0); - // `null` means that it doesn't need to be updated - var nextSticky = null; - var nextTop = null; - var scrollDown = scrollTop > prevScrollTop; - var menuPosAbsoluteY = topCache - scrollTop; - if (scrollDown) { - nextSticky = false; - if (menuPosAbsoluteY > 0) { - nextTop = prevScrollTop; - } - } else { - if (menuPosAbsoluteY > 0) { - nextSticky = true; - } else if (menuPosAbsoluteY < minMenuY) { - nextTop = prevScrollTop + minMenuY; - } - } - if (nextSticky === true && stickyCache === false) { - menu.classList.add('sticky'); - stickyCache = true; - } else if (nextSticky === false && stickyCache === true) { - menu.classList.remove('sticky'); - stickyCache = false; - } - if (nextTop !== null) { - menu.style.top = nextTop + 'px'; - topCache = nextTop; - } - prevScrollTop = scrollTop; - }, { passive: true }); - })(); - (function controllBorder() { - menu.classList.remove('bordered'); - document.addEventListener('scroll', function () { - if (menu.offsetTop === 0) { - menu.classList.remove('bordered'); - } else { - menu.classList.add('bordered'); - } - }, { passive: true }); - })(); -})(); diff --git a/docs/manual/clipboard.min.js b/docs/manual/clipboard.min.js deleted file mode 100644 index 02c549e35..000000000 --- a/docs/manual/clipboard.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * clipboard.js v2.0.4 - * https://zenorocha.github.io/clipboard.js - * - * Licensed MIT © Zeno Rocha - */ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n - - - - - Connecting to the Database - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Connecting to the Database

-

Connection String

-

Connecting to a MongoDB database requires using a connection string, a URI of the form:

-
mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
-
-

At its simplest this can just specify the host and port, e.g.

-
mongodb://mongodb0.example.com:27017
-
-

For the full range of options supported by the Rust driver, see the documentation for the ClientOptions::parse method. That method will return a ClientOptions struct, allowing for directly querying or setting any of the options supported by the Rust driver:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::options::ClientOptions;
-async fn run() -> mongodb::error::Result<()> {
-let mut options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
-options.app_name = Some("My App".to_string());
-Ok(())
-}
-}
-

Creating a Client

-

The Client struct is the main entry point for the driver. You can create one from a ClientOptions struct:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{Client, options::ClientOptions};
-async fn run() -> mongodb::error::Result<()> {
-let options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
-let client = Client::with_options(options)?;
-Ok(())
-}
-}
-

As a convenience, if you don't need to modify the ClientOptions before creating the Client, you can directly create one from the connection string:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::Client;
-async fn run() -> mongodb::error::Result<()> {
-let client = Client::with_uri_str("mongodb://mongodb0.example.com:27017").await?;
-Ok(())
-}
-}
-

Client uses std::sync::Arc internally, so it can safely be shared across threads or async tasks. For example:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-
-for i in 0..5 {
-    let client_ref = client.clone();
-
-    task::spawn(async move {
-        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
-

Client Performance

-

While cloning a Client is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single Client and persist it for the lifetime of your application. For more information, see the Performance chapter.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/css/chrome.css b/docs/manual/css/chrome.css deleted file mode 100644 index 59eae11fd..000000000 --- a/docs/manual/css/chrome.css +++ /dev/null @@ -1,538 +0,0 @@ -/* CSS for UI elements (a.k.a. chrome) */ - -@import 'variables.css'; - -::-webkit-scrollbar { - background: var(--bg); -} -::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} -html { - scrollbar-color: var(--scrollbar) var(--bg); -} -#searchresults a, -.content a:link, -a:visited, -a > .hljs { - color: var(--links); -} - -/* Menu Bar */ - -#menu-bar, -#menu-bar-hover-placeholder { - z-index: 101; - margin: auto calc(0px - var(--page-padding)); -} -#menu-bar { - position: relative; - display: flex; - flex-wrap: wrap; - background-color: var(--bg); - border-bottom-color: var(--bg); - border-bottom-width: 1px; - border-bottom-style: solid; -} -#menu-bar.sticky, -.js #menu-bar-hover-placeholder:hover + #menu-bar, -.js #menu-bar:hover, -.js.sidebar-visible #menu-bar { - position: -webkit-sticky; - position: sticky; - top: 0 !important; -} -#menu-bar-hover-placeholder { - position: sticky; - position: -webkit-sticky; - top: 0; - height: var(--menu-bar-height); -} -#menu-bar.bordered { - border-bottom-color: var(--table-border-color); -} -#menu-bar i, #menu-bar .icon-button { - position: relative; - padding: 0 8px; - z-index: 10; - line-height: var(--menu-bar-height); - cursor: pointer; - transition: color 0.5s; -} -@media only screen and (max-width: 420px) { - #menu-bar i, #menu-bar .icon-button { - padding: 0 5px; - } -} - -.icon-button { - border: none; - background: none; - padding: 0; - color: inherit; -} -.icon-button i { - margin: 0; -} - -.right-buttons { - margin: 0 15px; -} -.right-buttons a { - text-decoration: none; -} - -.left-buttons { - display: flex; - margin: 0 5px; -} -.no-js .left-buttons { - display: none; -} - -.menu-title { - display: inline-block; - font-weight: 200; - font-size: 2.4rem; - line-height: var(--menu-bar-height); - text-align: center; - margin: 0; - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.js .menu-title { - cursor: pointer; -} - -.menu-bar, -.menu-bar:visited, -.nav-chapters, -.nav-chapters:visited, -.mobile-nav-chapters, -.mobile-nav-chapters:visited, -.menu-bar .icon-button, -.menu-bar a i { - color: var(--icons); -} - -.menu-bar i:hover, -.menu-bar .icon-button:hover, -.nav-chapters:hover, -.mobile-nav-chapters i:hover { - color: var(--icons-hover); -} - -/* Nav Icons */ - -.nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - - position: fixed; - top: 0; - bottom: 0; - margin: 0; - max-width: 150px; - min-width: 90px; - - display: flex; - justify-content: center; - align-content: center; - flex-direction: column; - - transition: color 0.5s, background-color 0.5s; -} - -.nav-chapters:hover { - text-decoration: none; - background-color: var(--theme-hover); - transition: background-color 0.15s, color 0.15s; -} - -.nav-wrapper { - margin-top: 50px; - display: none; -} - -.mobile-nav-chapters { - font-size: 2.5em; - text-align: center; - text-decoration: none; - width: 90px; - border-radius: 5px; - background-color: var(--sidebar-bg); -} - -.previous { - float: left; -} - -.next { - float: right; - right: var(--page-padding); -} - -@media only screen and (max-width: 1080px) { - .nav-wide-wrapper { display: none; } - .nav-wrapper { display: block; } -} - -@media only screen and (max-width: 1380px) { - .sidebar-visible .nav-wide-wrapper { display: none; } - .sidebar-visible .nav-wrapper { display: block; } -} - -/* Inline code */ - -:not(pre) > .hljs { - display: inline; - padding: 0.1em 0.3em; - border-radius: 3px; -} - -:not(pre):not(a) > .hljs { - color: var(--inline-code-color); - overflow-x: initial; -} - -a:hover > .hljs { - text-decoration: underline; -} - -pre { - position: relative; -} -pre > .buttons { - position: absolute; - z-index: 100; - right: 0px; - top: 2px; - margin: 0px; - padding: 2px 0px; - - color: var(--sidebar-fg); - cursor: pointer; - visibility: hidden; - opacity: 0; - transition: visibility 0.1s linear, opacity 0.1s linear; -} -pre:hover > .buttons { - visibility: visible; - opacity: 1 -} -pre > .buttons :hover { - color: var(--sidebar-active); - border-color: var(--icons-hover); - background-color: var(--theme-hover); -} -pre > .buttons i { - margin-left: 8px; -} -pre > .buttons button { - cursor: inherit; - margin: 0px 5px; - padding: 3px 5px; - font-size: 14px; - - border-style: solid; - border-width: 1px; - border-radius: 4px; - border-color: var(--icons); - background-color: var(--theme-popup-bg); - transition: 100ms; - transition-property: color,border-color,background-color; - color: var(--icons); -} -@media (pointer: coarse) { - pre > .buttons button { - /* On mobile, make it easier to tap buttons. */ - padding: 0.3rem 1rem; - } -} -pre > code { - padding: 1rem; -} - -/* FIXME: ACE editors overlap their buttons because ACE does absolute - positioning within the code block which breaks padding. The only solution I - can think of is to move the padding to the outer pre tag (or insert a div - wrapper), but that would require fixing a whole bunch of CSS rules. -*/ -.hljs.ace_editor { - padding: 0rem 0rem; -} - -pre > .result { - margin-top: 10px; -} - -/* Search */ - -#searchresults a { - text-decoration: none; -} - -mark { - border-radius: 2px; - padding: 0 3px 1px 3px; - margin: 0 -3px -1px -3px; - background-color: var(--search-mark-bg); - transition: background-color 300ms linear; - cursor: pointer; -} - -mark.fade-out { - background-color: rgba(0,0,0,0) !important; - cursor: auto; -} - -.searchbar-outer { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); -} - -#searchbar { - width: 100%; - margin: 5px auto 0px auto; - padding: 10px 16px; - transition: box-shadow 300ms ease-in-out; - border: 1px solid var(--searchbar-border-color); - border-radius: 3px; - background-color: var(--searchbar-bg); - color: var(--searchbar-fg); -} -#searchbar:focus, -#searchbar.active { - box-shadow: 0 0 3px var(--searchbar-shadow-color); -} - -.searchresults-header { - font-weight: bold; - font-size: 1em; - padding: 18px 0 0 5px; - color: var(--searchresults-header-fg); -} - -.searchresults-outer { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); - border-bottom: 1px dashed var(--searchresults-border-color); -} - -ul#searchresults { - list-style: none; - padding-left: 20px; -} -ul#searchresults li { - margin: 10px 0px; - padding: 2px; - border-radius: 2px; -} -ul#searchresults li.focus { - background-color: var(--searchresults-li-bg); -} -ul#searchresults span.teaser { - display: block; - clear: both; - margin: 5px 0 0 20px; - font-size: 0.8em; -} -ul#searchresults span.teaser em { - font-weight: bold; - font-style: normal; -} - -/* Sidebar */ - -.sidebar { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--sidebar-width); - font-size: 0.875em; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - background-color: var(--sidebar-bg); - color: var(--sidebar-fg); -} -.sidebar-resizing { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; -} -.js:not(.sidebar-resizing) .sidebar { - transition: transform 0.3s; /* Animation: slide away */ -} -.sidebar code { - line-height: 2em; -} -.sidebar .sidebar-scrollbox { - overflow-y: auto; - position: absolute; - top: 0; - bottom: 0; - left: 0; - right: 0; - padding: 10px 10px; -} -.sidebar .sidebar-resize-handle { - position: absolute; - cursor: col-resize; - width: 0; - right: 0; - top: 0; - bottom: 0; -} -.js .sidebar .sidebar-resize-handle { - cursor: col-resize; - width: 5px; -} -.sidebar-hidden .sidebar { - transform: translateX(calc(0px - var(--sidebar-width))); -} -.sidebar::-webkit-scrollbar { - background: var(--sidebar-bg); -} -.sidebar::-webkit-scrollbar-thumb { - background: var(--scrollbar); -} - -.sidebar-visible .page-wrapper { - transform: translateX(var(--sidebar-width)); -} -@media only screen and (min-width: 620px) { - .sidebar-visible .page-wrapper { - transform: none; - margin-left: var(--sidebar-width); - } -} - -.chapter { - list-style: none outside none; - padding-left: 0; - line-height: 2.2em; -} - -.chapter ol { - width: 100%; -} - -.chapter li { - display: flex; - color: var(--sidebar-non-existant); -} -.chapter li a { - display: block; - padding: 0; - text-decoration: none; - color: var(--sidebar-fg); -} - -.chapter li a:hover { - color: var(--sidebar-active); -} - -.chapter li a.active { - color: var(--sidebar-active); -} - -.chapter li > a.toggle { - cursor: pointer; - display: block; - margin-left: auto; - padding: 0 10px; - user-select: none; - opacity: 0.68; -} - -.chapter li > a.toggle div { - transition: transform 0.5s; -} - -/* collapse the section */ -.chapter li:not(.expanded) + li > ol { - display: none; -} - -.chapter li.chapter-item { - line-height: 1.5em; - margin-top: 0.6em; -} - -.chapter li.expanded > a.toggle div { - transform: rotate(90deg); -} - -.spacer { - width: 100%; - height: 3px; - margin: 5px 0px; -} -.chapter .spacer { - background-color: var(--sidebar-spacer); -} - -@media (-moz-touch-enabled: 1), (pointer: coarse) { - .chapter li a { padding: 5px 0; } - .spacer { margin: 10px 0; } -} - -.section { - list-style: none outside none; - padding-left: 20px; - line-height: 1.9em; -} - -/* Theme Menu Popup */ - -.theme-popup { - position: absolute; - left: 10px; - top: var(--menu-bar-height); - z-index: 1000; - border-radius: 4px; - font-size: 0.7em; - color: var(--fg); - background: var(--theme-popup-bg); - border: 1px solid var(--theme-popup-border); - margin: 0; - padding: 0; - list-style: none; - display: none; - /* Don't let the children's background extend past the rounded corners. */ - overflow: hidden; -} -.theme-popup .default { - color: var(--icons); -} -.theme-popup .theme { - width: 100%; - border: 0; - margin: 0; - padding: 2px 20px; - line-height: 25px; - white-space: nowrap; - text-align: left; - cursor: pointer; - color: inherit; - background: inherit; - font-size: inherit; -} -.theme-popup .theme:hover { - background-color: var(--theme-hover); -} - -.theme-selected::before { - display: inline-block; - content: "✓"; - margin-left: -14px; - width: 14px; -} diff --git a/docs/manual/css/general.css b/docs/manual/css/general.css deleted file mode 100644 index 344b53eb7..000000000 --- a/docs/manual/css/general.css +++ /dev/null @@ -1,203 +0,0 @@ -/* Base styles and content styles */ - -@import 'variables.css'; - -:root { - /* Browser default font-size is 16px, this way 1 rem = 10px */ - font-size: 62.5%; -} - -html { - font-family: "Open Sans", sans-serif; - color: var(--fg); - background-color: var(--bg); - text-size-adjust: none; - -webkit-text-size-adjust: none; -} - -body { - margin: 0; - font-size: 1.6rem; - overflow-x: hidden; -} - -code { - font-family: var(--mono-font) !important; - font-size: var(--code-font-size); -} - -/* make long words/inline code not x overflow */ -main { - overflow-wrap: break-word; -} - -/* make wide tables scroll if they overflow */ -.table-wrapper { - overflow-x: auto; -} - -/* Don't change font size in headers. */ -h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { - font-size: unset; -} - -.left { float: left; } -.right { float: right; } -.boring { opacity: 0.6; } -.hide-boring .boring { display: none; } -.hidden { display: none !important; } - -h2, h3 { margin-top: 2.5em; } -h4, h5 { margin-top: 2em; } - -.header + .header h3, -.header + .header h4, -.header + .header h5 { - margin-top: 1em; -} - -h1:target::before, -h2:target::before, -h3:target::before, -h4:target::before, -h5:target::before, -h6:target::before { - display: inline-block; - content: "»"; - margin-left: -30px; - width: 30px; -} - -/* This is broken on Safari as of version 14, but is fixed - in Safari Technology Preview 117 which I think will be Safari 14.2. - https://bugs.webkit.org/show_bug.cgi?id=218076 -*/ -:target { - scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); -} - -.page { - outline: 0; - padding: 0 var(--page-padding); - margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ -} -.page-wrapper { - box-sizing: border-box; -} -.js:not(.sidebar-resizing) .page-wrapper { - transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ -} - -.content { - overflow-y: auto; - padding: 0 5px 50px 5px; -} -.content main { - margin-left: auto; - margin-right: auto; - max-width: var(--content-max-width); -} -.content p { line-height: 1.45em; } -.content ol { line-height: 1.45em; } -.content ul { line-height: 1.45em; } -.content a { text-decoration: none; } -.content a:hover { text-decoration: underline; } -.content img, .content video { max-width: 100%; } -.content .header:link, -.content .header:visited { - color: var(--fg); -} -.content .header:link, -.content .header:visited:hover { - text-decoration: none; -} - -table { - margin: 0 auto; - border-collapse: collapse; -} -table td { - padding: 3px 20px; - border: 1px var(--table-border-color) solid; -} -table thead { - background: var(--table-header-bg); -} -table thead td { - font-weight: 700; - border: none; -} -table thead th { - padding: 3px 20px; -} -table thead tr { - border: 1px var(--table-header-bg) solid; -} -/* Alternate background colors for rows */ -table tbody tr:nth-child(2n) { - background: var(--table-alternate-bg); -} - - -blockquote { - margin: 20px 0; - padding: 0 20px; - color: var(--fg); - background-color: var(--quote-bg); - border-top: .1em solid var(--quote-border); - border-bottom: .1em solid var(--quote-border); -} - -kbd { - background-color: var(--table-border-color); - border-radius: 4px; - border: solid 1px var(--theme-popup-border); - box-shadow: inset 0 -1px 0 var(--theme-hover); - display: inline-block; - font-size: var(--code-font-size); - font-family: var(--mono-font); - line-height: 10px; - padding: 4px 5px; - vertical-align: middle; -} - -:not(.footnote-definition) + .footnote-definition, -.footnote-definition + :not(.footnote-definition) { - margin-top: 2em; -} -.footnote-definition { - font-size: 0.9em; - margin: 0.5em 0; -} -.footnote-definition p { - display: inline; -} - -.tooltiptext { - position: absolute; - visibility: hidden; - color: #fff; - background-color: #333; - transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ - left: -8px; /* Half of the width of the icon */ - top: -35px; - font-size: 0.8em; - text-align: center; - border-radius: 6px; - padding: 5px 8px; - margin: 5px; - z-index: 1000; -} -.tooltipped .tooltiptext { - visibility: visible; -} - -.chapter li.part-title { - color: var(--sidebar-fg); - margin: 5px 0px; - font-weight: bold; -} - -.result-no-output { - font-style: italic; -} diff --git a/docs/manual/css/print.css b/docs/manual/css/print.css deleted file mode 100644 index 5e690f755..000000000 --- a/docs/manual/css/print.css +++ /dev/null @@ -1,54 +0,0 @@ - -#sidebar, -#menu-bar, -.nav-chapters, -.mobile-nav-chapters { - display: none; -} - -#page-wrapper.page-wrapper { - transform: none; - margin-left: 0px; - overflow-y: initial; -} - -#content { - max-width: none; - margin: 0; - padding: 0; -} - -.page { - overflow-y: initial; -} - -code { - background-color: #666666; - border-radius: 5px; - - /* Force background to be printed in Chrome */ - -webkit-print-color-adjust: exact; -} - -pre > .buttons { - z-index: 2; -} - -a, a:visited, a:active, a:hover { - color: #4183c4; - text-decoration: none; -} - -h1, h2, h3, h4, h5, h6 { - page-break-inside: avoid; - page-break-after: avoid; -} - -pre, code { - page-break-inside: avoid; - white-space: pre-wrap; -} - -.fa { - display: none !important; -} diff --git a/docs/manual/css/variables.css b/docs/manual/css/variables.css deleted file mode 100644 index 21bf8e55e..000000000 --- a/docs/manual/css/variables.css +++ /dev/null @@ -1,255 +0,0 @@ - -/* Globals */ - -:root { - --sidebar-width: 300px; - --page-padding: 15px; - --content-max-width: 750px; - --menu-bar-height: 50px; - --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; - --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ -} - -/* Themes */ - -.ayu { - --bg: hsl(210, 25%, 8%); - --fg: #c5c5c5; - - --sidebar-bg: #14191f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #5c6773; - --sidebar-active: #ffb454; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #0096cf; - - --inline-code-color: #ffb454; - - --theme-popup-bg: #14191f; - --theme-popup-border: #5c6773; - --theme-hover: #191f26; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --table-border-color: hsl(210, 25%, 13%); - --table-header-bg: hsl(210, 25%, 28%); - --table-alternate-bg: hsl(210, 25%, 11%); - - --searchbar-border-color: #848484; - --searchbar-bg: #424242; - --searchbar-fg: #fff; - --searchbar-shadow-color: #d4c89f; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #252932; - --search-mark-bg: #e3b171; -} - -.coal { - --bg: hsl(200, 7%, 8%); - --fg: #98a3ad; - - --sidebar-bg: #292c2f; - --sidebar-fg: #a1adb8; - --sidebar-non-existant: #505254; - --sidebar-active: #3473ad; - --sidebar-spacer: #393939; - - --scrollbar: var(--sidebar-fg); - - --icons: #43484d; - --icons-hover: #b3c0cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #141617; - --theme-popup-border: #43484d; - --theme-hover: #1f2124; - - --quote-bg: hsl(234, 21%, 18%); - --quote-border: hsl(234, 21%, 23%); - - --table-border-color: hsl(200, 7%, 13%); - --table-header-bg: hsl(200, 7%, 28%); - --table-alternate-bg: hsl(200, 7%, 11%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #b7b7b7; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #98a3ad; - --searchresults-li-bg: #2b2b2f; - --search-mark-bg: #355c7d; -} - -.light { - --bg: hsl(0, 0%, 100%); - --fg: hsl(0, 0%, 0%); - - --sidebar-bg: #fafafa; - --sidebar-fg: hsl(0, 0%, 0%); - --sidebar-non-existant: #aaaaaa; - --sidebar-active: #1f1fff; - --sidebar-spacer: #f4f4f4; - - --scrollbar: #8F8F8F; - - --icons: #747474; - --icons-hover: #000000; - - --links: #20609f; - - --inline-code-color: #301900; - - --theme-popup-bg: #fafafa; - --theme-popup-border: #cccccc; - --theme-hover: #e6e6e6; - - --quote-bg: hsl(197, 37%, 96%); - --quote-border: hsl(197, 37%, 91%); - - --table-border-color: hsl(0, 0%, 95%); - --table-header-bg: hsl(0, 0%, 80%); - --table-alternate-bg: hsl(0, 0%, 97%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #e4f2fe; - --search-mark-bg: #a2cff5; -} - -.navy { - --bg: hsl(226, 23%, 11%); - --fg: #bcbdd0; - - --sidebar-bg: #282d3f; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505274; - --sidebar-active: #2b79a2; - --sidebar-spacer: #2d334f; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #b7b9cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #161923; - --theme-popup-border: #737480; - --theme-hover: #282e40; - - --quote-bg: hsl(226, 15%, 17%); - --quote-border: hsl(226, 15%, 22%); - - --table-border-color: hsl(226, 23%, 16%); - --table-header-bg: hsl(226, 23%, 31%); - --table-alternate-bg: hsl(226, 23%, 14%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #aeaec6; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #5f5f71; - --searchresults-border-color: #5c5c68; - --searchresults-li-bg: #242430; - --search-mark-bg: #a2cff5; -} - -.rust { - --bg: hsl(60, 9%, 87%); - --fg: #262625; - - --sidebar-bg: #3b2e2a; - --sidebar-fg: #c8c9db; - --sidebar-non-existant: #505254; - --sidebar-active: #e69f67; - --sidebar-spacer: #45373a; - - --scrollbar: var(--sidebar-fg); - - --icons: #737480; - --icons-hover: #262625; - - --links: #2b79a2; - - --inline-code-color: #6e6b5e; - - --theme-popup-bg: #e1e1db; - --theme-popup-border: #b38f6b; - --theme-hover: #99908a; - - --quote-bg: hsl(60, 5%, 75%); - --quote-border: hsl(60, 5%, 70%); - - --table-border-color: hsl(60, 9%, 82%); - --table-header-bg: #b3a497; - --table-alternate-bg: hsl(60, 9%, 84%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #fafafa; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #888; - --searchresults-li-bg: #dec2a2; - --search-mark-bg: #e69f67; -} - -@media (prefers-color-scheme: dark) { - .light.no-js { - --bg: hsl(200, 7%, 8%); - --fg: #98a3ad; - - --sidebar-bg: #292c2f; - --sidebar-fg: #a1adb8; - --sidebar-non-existant: #505254; - --sidebar-active: #3473ad; - --sidebar-spacer: #393939; - - --scrollbar: var(--sidebar-fg); - - --icons: #43484d; - --icons-hover: #b3c0cc; - - --links: #2b79a2; - - --inline-code-color: #c5c8c6; - - --theme-popup-bg: #141617; - --theme-popup-border: #43484d; - --theme-hover: #1f2124; - - --quote-bg: hsl(234, 21%, 18%); - --quote-border: hsl(234, 21%, 23%); - - --table-border-color: hsl(200, 7%, 13%); - --table-header-bg: hsl(200, 7%, 28%); - --table-alternate-bg: hsl(200, 7%, 11%); - - --searchbar-border-color: #aaa; - --searchbar-bg: #b7b7b7; - --searchbar-fg: #000; - --searchbar-shadow-color: #aaa; - --searchresults-header-fg: #666; - --searchresults-border-color: #98a3ad; - --searchresults-li-bg: #2b2b2f; - --search-mark-bg: #355c7d; - } -} diff --git a/docs/manual/elasticlunr.min.js b/docs/manual/elasticlunr.min.js deleted file mode 100644 index 94b20dd2e..000000000 --- a/docs/manual/elasticlunr.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * elasticlunr - http://weixsong.github.io - * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 - * - * Copyright (C) 2017 Oliver Nightingale - * Copyright (C) 2017 Wei Song - * MIT Licensed - * @license - */ -!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o - - - - - Encryption - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Unstable API

-

To enable support for in-use encryption (client-side field level encryption and queryable encryption), enable the "in-use-encryption-unstable" feature of the mongodb crate. As the name implies, the API for this feature is unstable, and may change in backwards-incompatible ways in minor releases.

-

Client-Side Field Level Encryption

-

Starting with MongoDB 4.2, client-side field level encryption allows an application to encrypt specific data fields in addition to pre-existing MongoDB encryption features such as Encryption at Rest and TLS/SSL (Transport Encryption).

-

With field level encryption, applications can encrypt fields in documents prior to transmitting data over the wire to the server. Client-side field level encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data.

-

See also the MongoDB documentation on Client Side Field Level Encryption.

-

Dependencies

-

To get started using client-side field level encryption in your project, you will need to install libmongocrypt, which can be fetched from a variety of package repositories. If you install libmongocrypt in a location outside of the system library search path, the MONGOCRYPT_LIB_DIR environment variable will need to be set when compiling your project.

-

Additionally, either crypt_shared or mongocryptd are required in order to use automatic client-side encryption.

-

crypt_shared

-

The Automatic Encryption Shared Library (crypt_shared) provides the same functionality as mongocryptd, but does not require you to spawn another process to perform automatic encryption.

-

By default, the mongodb crate attempts to load crypt_shared from the system and if found uses it automatically. To load crypt_shared from another location, set the "cryptSharedLibPath" field in extra_options:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "cryptSharedLibPath": "/path/to/crypt/shared",
-    })
-    .build();
-
-Ok(())
-}
-}
-

If the mongodb crate cannot load crypt_shared it will attempt to fallback to using mongocryptd by default. Include "cryptSharedRequired": true in the extra_options document to always use crypt_shared and fail if it could not be loaded.

-

For detailed installation instructions see the MongoDB documentation on Automatic Encryption Shared Library.

-

mongocryptd

-

If using crypt_shared is not an option, the mongocryptd binary is required for automatic client-side encryption and is included as a component in the MongoDB Enterprise Server package. For detailed installation instructions see the MongoDB documentation on mongocryptd.

-

mongocryptd performs the following:

-
    -
  • Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, mongocryptd returns an error.
  • -
  • Uses the specified automatic encryption rules to mark fields in read and write operations for encryption.
  • -
  • Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see Read/Write Support with Automatic Field Level Encryption.
  • -
-

A Client configured with auto encryption will automatically spawn the mongocryptd process from the application's PATH. Applications can control the spawning behavior as part of the automatic encryption options:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "mongocryptdSpawnPath": "/path/to/mongocryptd",
-        "mongocryptdSpawnArgs": ["--logpath=/path/to/mongocryptd.log", "--logappend"],
-    })
-    .build();
-
-Ok(())
-}
-}
-

If your application wishes to manage the mongocryptd process manually, it is possible to disable spawning mongocryptd:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "mongocryptdBypassSpawn": true,
-        "mongocryptdURI": "mongodb://localhost:27020",
-    })
-    .build();
-
-Ok(())
-}
-}
-

mongocryptd is only responsible for supporting automatic client-side field level encryption and does not itself perform any encryption or decryption.

-

Automatic Client-Side Field Level Encryption

-

Automatic client-side field level encryption is enabled by using the Client::encrypted_builder constructor method. The following examples show how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key.

-

Note: Automatic client-side field level encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the server supports automatic decryption as well as explicit client-side encryption.

-

Providing Local Automatic Encryption Rules

-

The following example shows how to specify automatic encryption rules via the schema_map option. The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax.

-

Supplying a schema_map provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.

-

JSON Schemas supplied in the schema_map only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // The MongoDB namespace (db.collection) used to store the
-    // encrypted documents in this example.
-    let encrypted_namespace = Namespace::new("test", "coll");
-
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("encryption", "__testKeyVault");
-
-    // The MongoClient used to access the key vault (key_vault_namespace).
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-    // Create a new data key and json schema for the encryptedField.
-    // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_1".to_string()])
-        .run()
-        .await?;
-    let schema = doc! {
-        "properties": {
-            "encryptedField": {
-                "encrypt": {
-                    "keyId": [data_key_id],
-                    "bsonType": "string",
-                    "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
-                }
-            }
-        },
-        "bsonType": "object",
-    };
-
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .schema_map([(encrypted_namespace.to_string(), schema)])
-    .build()
-    .await?;
-    let coll = client
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    // Clear old data.
-    coll.drop(None).await?;
-
-    coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-        .await?;
-    println!("Decrypted document: {:?}", coll.find_one(None, None).await?);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-
-    Ok(())
-}
-

Server-Side Field Level Encryption Enforcement

-

The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the "encrypt" JSON schema keyword.

-

The following example shows how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key and create a collection with the Automatic Encryption JSON Schema Syntax:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::{ClientOptions, CreateCollectionOptions, WriteConcern},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // The MongoDB namespace (db.collection) used to store the
-    // encrypted documents in this example.
-    let encrypted_namespace = Namespace::new("test", "coll");
-
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("encryption", "__testKeyVault");
-
-    // The MongoClient used to access the key vault (key_vault_namespace).
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key and json schema for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_2".to_string()])
-        .run()
-        .await?;
-    let schema = doc! {
-        "properties": {
-            "encryptedField": {
-                "encrypt": {
-                    "keyId": [data_key_id],
-                    "bsonType": "string",
-                    "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
-                }
-            }
-        },
-        "bsonType": "object",
-    };
-    
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .build()
-    .await?;
-    let db = client.database(&encrypted_namespace.db);
-    let coll = db.collection::<Document>(&encrypted_namespace.coll);
-    // Clear old data
-    coll.drop(None).await?;
-    // Create the collection with the encryption JSON Schema.
-    db.create_collection(
-        &encrypted_namespace.coll,
-        CreateCollectionOptions::builder()
-            .write_concern(WriteConcern::MAJORITY)
-            .validator(doc! { "$jsonSchema": schema })
-            .build(),
-    ).await?;
-
-    coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-        .await?;
-    println!("Decrypted document: {:?}", coll.find_one(None, None).await?);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-    // This would return a Write error with the message "Document failed validation".
-    // unencrypted_coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-    //    .await?;
-
-    Ok(())
-}
-

Automatic Queryable Encryption

-

Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0.

-

Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side.

-

You must have MongoDB 6.0 Enterprise to preview the feature.

-

Automatic encryption in Queryable Encryption is configured with an encrypted_fields mapping, as demonstrated by the following example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-extern crate futures;
-static URI: &str = "mongodb://example.com";
-use futures::TryStreamExt;
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-    let key1_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["firstName".to_string()])
-        .run()
-        .await?;
-    let key2_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["lastName".to_string()])
-        .run()
-        .await?;
-
-    let encrypted_fields_map = vec![(
-        "example.encryptedCollection",
-        doc! {
-            "escCollection": "encryptedCollection.esc",
-            "eccCollection": "encryptedCollection.ecc",
-            "ecocCollection": "encryptedCollection.ecoc",
-            "fields": [
-              {
-                "path": "firstName",
-                "bsonType": "string",
-                "keyId": key1_id,
-                "queries": [{"queryType": "equality"}],
-              },
-                {
-                  "path": "lastName",
-                  "bsonType": "string",
-                  "keyId": key2_id,
-                }
-            ]
-        },
-    )];
-
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .encrypted_fields_map(encrypted_fields_map)
-    .build()
-    .await?;
-    let db = client.database("example");
-    let coll = db.collection::<Document>("encryptedCollection");
-    coll.drop(None).await?;
-    db.create_collection("encryptedCollection", None).await?;
-    coll.insert_one(
-        doc! { "_id": 1, "firstName": "Jane", "lastName": "Doe" },
-        None,
-    )
-    .await?;
-    let docs: Vec<_> = coll
-        .find(doc! {"firstName": "Jane"}, None)
-        .await?
-        .try_collect()
-        .await?;
-    println!("{:?}", docs);
-
-    Ok(())
-}
-

Explicit Queryable Encryption

-

Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0.

-

Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side.

-

Explicit encryption in Queryable Encryption is performed using the encrypt and decrypt methods. Automatic encryption (to allow the find_one to automatically decrypt) is configured using an encrypted_fields mapping, as demonstrated by the following example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{KmsProvider, Algorithm},
-    options::{ClientOptions, CreateCollectionOptions},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let client = Client::with_uri_str(URI).await?;
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let indexed_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .run()
-        .await?;
-    let unindexed_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .run()
-        .await?;
-
-    let encrypted_fields = doc! {
-      "escCollection": "enxcol_.default.esc",
-      "eccCollection": "enxcol_.default.ecc",
-      "ecocCollection": "enxcol_.default.ecoc",
-      "fields": [
-        {
-          "keyId": indexed_key_id.clone(),
-          "path": "encryptedIndexed",
-          "bsonType": "string",
-          "queries": {
-            "queryType": "equality"
-          }
-        },
-        {
-          "keyId": unindexed_key_id.clone(),
-          "path": "encryptedUnindexed",
-          "bsonType": "string",
-        }
-      ]
-    };
-
-    // The MongoClient used to read/write application data.
-    let encrypted_client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .bypass_query_analysis(true)
-    .build()
-    .await?;
-    let db = encrypted_client.database("test");
-    db.drop(None).await?;
-
-    // Create the collection with encrypted fields.
-    db.create_collection(
-        "coll",
-        CreateCollectionOptions::builder()
-            .encrypted_fields(encrypted_fields)
-            .build(),
-    )
-    .await?;
-    let coll = db.collection::<Document>("coll");
-
-    // Create and encrypt an indexed and unindexed value.
-    let val = "encrypted indexed value";
-    let unindexed_val = "encrypted unindexed value";
-    let insert_payload_indexed = client_encryption
-        .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed)
-        .contention_factor(1)
-        .run()
-        .await?;
-    let insert_payload_unindexed = client_encryption
-        .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed)
-        .run()
-        .await?;
-
-    // Insert the payloads.
-    coll.insert_one(
-        doc! {
-            "encryptedIndexed": insert_payload_indexed,
-            "encryptedUnindexed": insert_payload_unindexed,
-        },
-        None,
-    )
-    .await?;
-
-    // Encrypt our find payload using QueryType.EQUALITY.
-    // The value of `data_key_id` must be the same as used to encrypt the values
-    // above.
-    let find_payload = client_encryption
-        .encrypt(val, indexed_key_id, Algorithm::Indexed)
-        .query_type("equality")
-        .contention_factor(1)
-        .run()
-        .await?;
-
-    // Find the document we inserted using the encrypted payload.
-    // The returned document is automatically decrypted.
-    let doc = coll
-        .find_one(doc! { "encryptedIndexed": find_payload }, None)
-        .await?;
-    println!("Returned document: {:?}", doc);
-
-    Ok(())
-}
-

Explicit Encryption

-

Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the ClientEncryption struct, for example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Bson, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{Algorithm, KmsProvider},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // The MongoClient used to read/write application data.
-    let client = Client::with_uri_str(URI).await?;
-    let coll = client.database("test").collection::<Document>("coll");
-    // Clear old data
-    coll.drop(None).await?;
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_3".to_string()])
-        .run()
-        .await?;
-
-    // Explicitly encrypt a field:
-    let encrypted_field = client_encryption
-        .encrypt(
-            "123456789",
-            data_key_id,
-            Algorithm::AeadAes256CbcHmacSha512Deterministic,
-        )
-        .run()
-        .await?;
-    coll.insert_one(doc! { "encryptedField": encrypted_field }, None)
-        .await?;
-    let mut doc = coll.find_one(None, None).await?.unwrap();
-    println!("Encrypted document: {:?}", doc);
-
-    // Explicitly decrypt the field:
-    let field = match doc.get("encryptedField") {
-        Some(Bson::Binary(bin)) => bin,
-        _ => panic!("invalid field"),
-    };
-    let decrypted: Bson = client_encryption
-        .decrypt(field.as_raw_binary())
-        .await?
-        .try_into()?;
-    doc.insert("encryptedField", decrypted);
-    println!("Decrypted document: {:?}", doc);
-
-    Ok(())
-}
-

Explicit Encryption with Automatic Decryption

-

Although automatic encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set bypass_auto_encryption to true in the EncryptedClientBuilder:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{Algorithm, KmsProvider},
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // `bypass_auto_encryption(true)` disables automatic encryption but keeps
-    // the automatic _decryption_ behavior. bypass_auto_encryption will
-    // also disable spawning mongocryptd.
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?
-    .bypass_auto_encryption(true)
-    .build()
-    .await?;
-    let coll = client.database("test").collection::<Document>("coll");
-    // Clear old data
-    coll.drop(None).await?;
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_4".to_string()])
-        .run()
-        .await?;
-
-    // Explicitly encrypt a field:
-    let encrypted_field = client_encryption
-        .encrypt(
-            "123456789",
-            data_key_id,
-            Algorithm::AeadAes256CbcHmacSha512Deterministic,
-        )
-        .run()
-        .await?;
-    coll.insert_one(doc! { "encryptedField": encrypted_field }, None)
-        .await?;
-    // Automatically decrypts any encrypted fields.
-    let doc = coll.find_one(None, None).await?.unwrap();
-    println!("Decrypted document: {:?}", doc);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database("test")
-        .collection::<Document>("coll");
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-
-    Ok(())
-}
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/favicon.png b/docs/manual/favicon.png deleted file mode 100644 index a5b1aa16c..000000000 Binary files a/docs/manual/favicon.png and /dev/null differ diff --git a/docs/manual/favicon.svg b/docs/manual/favicon.svg deleted file mode 100644 index 90e0ea58b..000000000 --- a/docs/manual/favicon.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - diff --git a/docs/manual/fonts/OPEN-SANS-LICENSE.txt b/docs/manual/fonts/OPEN-SANS-LICENSE.txt deleted file mode 100644 index d64569567..000000000 --- a/docs/manual/fonts/OPEN-SANS-LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/docs/manual/fonts/SOURCE-CODE-PRO-LICENSE.txt b/docs/manual/fonts/SOURCE-CODE-PRO-LICENSE.txt deleted file mode 100644 index 366206f54..000000000 --- a/docs/manual/fonts/SOURCE-CODE-PRO-LICENSE.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/manual/fonts/fonts.css b/docs/manual/fonts/fonts.css deleted file mode 100644 index 858efa598..000000000 --- a/docs/manual/fonts/fonts.css +++ /dev/null @@ -1,100 +0,0 @@ -/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ -/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ - -/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 300; - src: local('Open Sans Light'), local('OpenSans-Light'), - url('open-sans-v17-all-charsets-300.woff2') format('woff2'); -} - -/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 300; - src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), - url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); -} - -/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 400; - src: local('Open Sans Regular'), local('OpenSans-Regular'), - url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); -} - -/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 400; - src: local('Open Sans Italic'), local('OpenSans-Italic'), - url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); -} - -/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 600; - src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), - url('open-sans-v17-all-charsets-600.woff2') format('woff2'); -} - -/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 600; - src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), - url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); -} - -/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 700; - src: local('Open Sans Bold'), local('OpenSans-Bold'), - url('open-sans-v17-all-charsets-700.woff2') format('woff2'); -} - -/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 700; - src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), - url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); -} - -/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: normal; - font-weight: 800; - src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), - url('open-sans-v17-all-charsets-800.woff2') format('woff2'); -} - -/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Open Sans'; - font-style: italic; - font-weight: 800; - src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), - url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); -} - -/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ -@font-face { - font-family: 'Source Code Pro'; - font-style: normal; - font-weight: 500; - src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); -} diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-300.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-300.woff2 deleted file mode 100644 index 9f51be370..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-300.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-300italic.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-300italic.woff2 deleted file mode 100644 index 2f5454484..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-300italic.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-600.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-600.woff2 deleted file mode 100644 index f503d558d..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-600.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-600italic.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-600italic.woff2 deleted file mode 100644 index c99aabe80..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-600italic.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-700.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-700.woff2 deleted file mode 100644 index 421a1ab25..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-700.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-700italic.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-700italic.woff2 deleted file mode 100644 index 12ce3d20d..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-700italic.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-800.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-800.woff2 deleted file mode 100644 index c94a223b0..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-800.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-800italic.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-800italic.woff2 deleted file mode 100644 index eed7d3c63..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-800italic.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-italic.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-italic.woff2 deleted file mode 100644 index 398b68a08..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-italic.woff2 and /dev/null differ diff --git a/docs/manual/fonts/open-sans-v17-all-charsets-regular.woff2 b/docs/manual/fonts/open-sans-v17-all-charsets-regular.woff2 deleted file mode 100644 index 8383e94c6..000000000 Binary files a/docs/manual/fonts/open-sans-v17-all-charsets-regular.woff2 and /dev/null differ diff --git a/docs/manual/fonts/source-code-pro-v11-all-charsets-500.woff2 b/docs/manual/fonts/source-code-pro-v11-all-charsets-500.woff2 deleted file mode 100644 index 722245682..000000000 Binary files a/docs/manual/fonts/source-code-pro-v11-all-charsets-500.woff2 and /dev/null differ diff --git a/docs/manual/highlight.css b/docs/manual/highlight.css deleted file mode 100644 index ba57b82b2..000000000 --- a/docs/manual/highlight.css +++ /dev/null @@ -1,82 +0,0 @@ -/* - * An increased contrast highlighting scheme loosely based on the - * "Base16 Atelier Dune Light" theme by Bram de Haan - * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) - * Original Base16 color scheme by Chris Kempson - * (https://github.com/chriskempson/base16) - */ - -/* Comment */ -.hljs-comment, -.hljs-quote { - color: #575757; -} - -/* Red */ -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-tag, -.hljs-name, -.hljs-regexp, -.hljs-link, -.hljs-name, -.hljs-selector-id, -.hljs-selector-class { - color: #d70025; -} - -/* Orange */ -.hljs-number, -.hljs-meta, -.hljs-built_in, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #b21e00; -} - -/* Green */ -.hljs-string, -.hljs-symbol, -.hljs-bullet { - color: #008200; -} - -/* Blue */ -.hljs-title, -.hljs-section { - color: #0030f2; -} - -/* Purple */ -.hljs-keyword, -.hljs-selector-tag { - color: #9d00ec; -} - -.hljs { - display: block; - overflow-x: auto; - background: #f6f7f6; - color: #000; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-addition { - color: #22863a; - background-color: #f0fff4; -} - -.hljs-deletion { - color: #b31d28; - background-color: #ffeef0; -} diff --git a/docs/manual/highlight.js b/docs/manual/highlight.js deleted file mode 100644 index 180385b70..000000000 --- a/docs/manual/highlight.js +++ /dev/null @@ -1,6 +0,0 @@ -/* - Highlight.js 10.1.1 (93fd0d73) - License: BSD-3-Clause - Copyright (c) 2006-2020, Ivan Sagalaev -*/ -var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:">>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); diff --git a/docs/manual/index.html b/docs/manual/index.html index a361ea49a..bc157b446 100644 --- a/docs/manual/index.html +++ b/docs/manual/index.html @@ -1,219 +1,14 @@ - - - - - Introduction - MongoDB Rust Driver + + + + Moved + - - - - - + + The MongoDB Rust Driver documentation has moved to the + official MongoDB docs site. Please update your bookmarks. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Introduction

-

Crates.io docs.rs License

-

This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that supports either tokio (default) or async-std, depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag.

-

Warning about timeouts / cancellation

-

In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how tokio::time::timeout works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more details). As such, it is highly recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like tokio::time::timeout with the driver, one option is to spawn tasks and time out on their JoinHandle futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout.

-

e.g.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use std::time::Duration;
-use mongodb::{
-    Client,
-    bson::doc,
-};
-
-async fn foo() -> std::result::Result<(), Box<dyn std::error::Error>> {
-
-let client = Client::with_uri_str("mongodb://example.com").await?;
-let collection = client.database("foo").collection("bar");
-let handle = tokio::task::spawn(async move {
-    collection.insert_one(doc! { "x": 1 }, None).await
-});
-
-tokio::time::timeout(Duration::from_secs(5), handle).await???;
-Ok(())
-}
-}
-

Minimum supported Rust version (MSRV)

-

The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, -it will only happen in a minor or major version release.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/manual/installation_features.html b/docs/manual/installation_features.html deleted file mode 100644 index f1bec36aa..000000000 --- a/docs/manual/installation_features.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Installation and Features - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Installation and Features

-

Importing

-

The driver is available on crates.io. To use the driver in your application, simply add it to your project's Cargo.toml.

-
[dependencies]
-mongodb = "2.1.0"
-
-

Configuring the async runtime

-

The driver supports both of the most popular async runtime crates, namely tokio and async-std. By default, the driver will use tokio, but you can explicitly choose a runtime by specifying one of "tokio-runtime" or "async-std-runtime" feature flags in your Cargo.toml.

-

For example, to instruct the driver to work with async-std, add the following to your Cargo.toml:

-
[dependencies.mongodb]
-version = "2.1.0"
-default-features = false
-features = ["async-std-runtime"]
-
-

Enabling the sync API

-

The driver also provides a blocking sync API. To enable this, add the "sync" or "tokio-sync" feature to your Cargo.toml:

-
[dependencies.mongodb]
-version = "2.3.0"
-features = ["tokio-sync"]
-
-

Using the "sync" feature also requires using default-features = false. -Note: The sync-specific types can be imported from mongodb::sync (e.g. mongodb::sync::Client).

-

All Feature Flags

-
- - - - - - - - - - - - - -
FeatureDescriptionExtra dependenciesDefault
tokio-runtimeEnable support for the tokio async runtimetokio 1.0 with the full featureyes
async-std-runtimeEnable support for the async-std runtimeasync-std 1.0no
syncExpose the synchronous API (mongodb::sync), using an async-std backend. Cannot be used with the tokio-runtime feature flag.async-std 1.0no
tokio-syncExpose the synchronous API (mongodb::sync), using a tokio backend. Cannot be used with the async-std-runtime feature flag.tokio 1.0 with the full featureno
aws-authEnable support for the MONGODB-AWS authentication mechanism.reqwest 0.11no
bson-uuid-0_8Enable support for v0.8 of the uuid crate in the public API of the re-exported bson crate.n/ano
bson-uuid-1Enable support for v1.x of the uuid crate in the public API of the re-exported bson crate.n/ano
bson-chrono-0_4Enable support for v0.4 of the chrono crate in the public API of the re-exported bson crate.n/ano
bson-serde_withEnable support for the serde_with crate in the public API of the re-exported bson crate.serde_with 1.0no
zlib-compressionEnable support for compressing messages with zlibflate2 1.0no
zstd-compressionEnable support for compressing messages with zstd. This flag requires Rust version 1.54.zstd 0.9.0no
snappy-compressionEnable support for compressing messages with snappysnap 1.0.5no
openssl-tlsSwitch TLS connection handling to use 'openssl'.openssl 0.10.38no
-
-
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/mark.min.js b/docs/manual/mark.min.js deleted file mode 100644 index 163623188..000000000 --- a/docs/manual/mark.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*!*************************************************** -* mark.js v8.11.1 -* https://markjs.io/ -* Copyright (c) 2014–2018, Julian Kühnel -* Released under the MIT license https://git.io/vwTVl -*****************************************************/ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c - - - - - Performance - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Performance

-

Client Best Practices

-

The Client handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a Client should be used for best performance.

-

Lifetime

-

A Client should be as long-lived as possible. Establishing a new Client is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because Client is implemented using an internal Arc, it can safely be shared across threads or tasks, and cloneing it to pass to new contexts is extremely cheap.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::Client;
-use std::error::Error;
-// This will be very slow because it's constructing and tearing down a `Client`
-// with every request.
-async fn handle_request_bad() -> Result<(), Box<dyn Error>> {
-    let client = Client::with_uri_str("mongodb://example.com").await?;
-    // Do something with the client
-    Ok(())
-}
-
-// This will be much faster.
-async fn handle_request_good(client: &Client) -> Result<(), Box<dyn Error>> {
-    // Do something with the client
-    Ok(())
-}
-}
-

This is especially noticeable when using a framework that provides connection pooling; because Client does its own pooling internally, attempting to maintain a pool of Clients will (somewhat counter-intuitively) result in worse performance than using a single one.

-

Runtime

-

A Client is implicitly bound to the instance of the tokio or async-std runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the tokio::test or async_std::test helper macros create a new runtime for each test.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate once_cell;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-use tokio::runtime::Runtime;
-use once_cell::sync::Lazy;
-
-static CLIENT: Lazy<Client> = Lazy::new(|| {
-    let rt = Runtime::new().unwrap();
-    rt.block_on(async {
-        Client::with_uri_str("mongodb://example.com").await.unwrap()
-    })
-});
-
-// This will inconsistently fail.
-#[tokio::test]
-async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    CLIENT.list_database_names(None, None).await?;
-    Ok(())
-}
-}
-

To work around this issue, either create a new Client for every async test, or bundle the Runtime along with the client and don't use the test helper macros.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate once_cell;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-use tokio::runtime::Runtime;
-use once_cell::sync::Lazy;
-
-static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| {
-    let rt = Runtime::new().unwrap();
-    let client = rt.block_on(async {
-        Client::with_uri_str("mongodb://example.com").await.unwrap()
-    });
-    (client, rt)
-});
-
-#[test]
-fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    let (client, rt) = &*CLIENT_RUNTIME;
-    rt.block_on(async {
-        client.list_database_names(None, None).await
-    })?;
-    Ok(())
-}
-}
-

or

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-#[tokio::test]
-async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    let client = Client::with_uri_str("mongodb://example.com").await?;
-    CLIENT.list_database_names(None, None).await?;
-    Ok(())
-}
-}
-

Parallelism

-

Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-
-for i in 0..5 {
-    let client_ref = client.clone();
-
-    task::spawn(async move {
-        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/print.html b/docs/manual/print.html deleted file mode 100644 index 3c5083b74..000000000 --- a/docs/manual/print.html +++ /dev/null @@ -1,1366 +0,0 @@ - - - - - - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Introduction

-

Crates.io docs.rs License

-

This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that supports either tokio (default) or async-std, depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag.

-

Warning about timeouts / cancellation

-

In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how tokio::time::timeout works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more details). As such, it is highly recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like tokio::time::timeout with the driver, one option is to spawn tasks and time out on their JoinHandle futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout.

-

e.g.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use std::time::Duration;
-use mongodb::{
-    Client,
-    bson::doc,
-};
-
-async fn foo() -> std::result::Result<(), Box<dyn std::error::Error>> {
-
-let client = Client::with_uri_str("mongodb://example.com").await?;
-let collection = client.database("foo").collection("bar");
-let handle = tokio::task::spawn(async move {
-    collection.insert_one(doc! { "x": 1 }, None).await
-});
-
-tokio::time::timeout(Duration::from_secs(5), handle).await???;
-Ok(())
-}
-}
-

Minimum supported Rust version (MSRV)

-

The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, -it will only happen in a minor or major version release.

-

Installation and Features

-

Importing

-

The driver is available on crates.io. To use the driver in your application, simply add it to your project's Cargo.toml.

-
[dependencies]
-mongodb = "2.1.0"
-
-

Configuring the async runtime

-

The driver supports both of the most popular async runtime crates, namely tokio and async-std. By default, the driver will use tokio, but you can explicitly choose a runtime by specifying one of "tokio-runtime" or "async-std-runtime" feature flags in your Cargo.toml.

-

For example, to instruct the driver to work with async-std, add the following to your Cargo.toml:

-
[dependencies.mongodb]
-version = "2.1.0"
-default-features = false
-features = ["async-std-runtime"]
-
-

Enabling the sync API

-

The driver also provides a blocking sync API. To enable this, add the "sync" or "tokio-sync" feature to your Cargo.toml:

-
[dependencies.mongodb]
-version = "2.3.0"
-features = ["tokio-sync"]
-
-

Using the "sync" feature also requires using default-features = false. -Note: The sync-specific types can be imported from mongodb::sync (e.g. mongodb::sync::Client).

-

All Feature Flags

-
- - - - - - - - - - - - - -
FeatureDescriptionExtra dependenciesDefault
tokio-runtimeEnable support for the tokio async runtimetokio 1.0 with the full featureyes
async-std-runtimeEnable support for the async-std runtimeasync-std 1.0no
syncExpose the synchronous API (mongodb::sync), using an async-std backend. Cannot be used with the tokio-runtime feature flag.async-std 1.0no
tokio-syncExpose the synchronous API (mongodb::sync), using a tokio backend. Cannot be used with the async-std-runtime feature flag.tokio 1.0 with the full featureno
aws-authEnable support for the MONGODB-AWS authentication mechanism.reqwest 0.11no
bson-uuid-0_8Enable support for v0.8 of the uuid crate in the public API of the re-exported bson crate.n/ano
bson-uuid-1Enable support for v1.x of the uuid crate in the public API of the re-exported bson crate.n/ano
bson-chrono-0_4Enable support for v0.4 of the chrono crate in the public API of the re-exported bson crate.n/ano
bson-serde_withEnable support for the serde_with crate in the public API of the re-exported bson crate.serde_with 1.0no
zlib-compressionEnable support for compressing messages with zlibflate2 1.0no
zstd-compressionEnable support for compressing messages with zstd. This flag requires Rust version 1.54.zstd 0.9.0no
snappy-compressionEnable support for compressing messages with snappysnap 1.0.5no
openssl-tlsSwitch TLS connection handling to use 'openssl'.openssl 0.10.38no
-

Connecting to the Database

-

Connection String

-

Connecting to a MongoDB database requires using a connection string, a URI of the form:

-
mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]]
-
-

At its simplest this can just specify the host and port, e.g.

-
mongodb://mongodb0.example.com:27017
-
-

For the full range of options supported by the Rust driver, see the documentation for the ClientOptions::parse method. That method will return a ClientOptions struct, allowing for directly querying or setting any of the options supported by the Rust driver:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::options::ClientOptions;
-async fn run() -> mongodb::error::Result<()> {
-let mut options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
-options.app_name = Some("My App".to_string());
-Ok(())
-}
-}
-

Creating a Client

-

The Client struct is the main entry point for the driver. You can create one from a ClientOptions struct:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{Client, options::ClientOptions};
-async fn run() -> mongodb::error::Result<()> {
-let options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?;
-let client = Client::with_options(options)?;
-Ok(())
-}
-}
-

As a convenience, if you don't need to modify the ClientOptions before creating the Client, you can directly create one from the connection string:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::Client;
-async fn run() -> mongodb::error::Result<()> {
-let client = Client::with_uri_str("mongodb://mongodb0.example.com:27017").await?;
-Ok(())
-}
-}
-

Client uses std::sync::Arc internally, so it can safely be shared across threads or async tasks. For example:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-
-for i in 0..5 {
-    let client_ref = client.clone();
-
-    task::spawn(async move {
-        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
-

Client Performance

-

While cloning a Client is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single Client and persist it for the lifetime of your application. For more information, see the Performance chapter.

-

Reading From the Database

-

Database and Collection Handles

-

Once you have a Client, you can call Client::database to create a handle to a particular database on the server, and Database::collection to create a handle to a particular collection in that database. Database and Collection handles are lightweight - creating them requires no IO, cloneing them is cheap, and they can be safely shared across threads or async tasks. For example:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-let db = client.database("items");
-
-for i in 0..5 {
-    let db_ref = db.clone();
-
-    task::spawn(async move {
-        let collection = db_ref.collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
-

A Collection can be parameterized with a type for the documents in the collection; this includes but is not limited to just Document. The various methods that accept instances of the documents (e.g. Collection::insert_one) require that it implement the Serialize trait from the serde crate. Similarly, the methods that return instances (e.g. Collection::find_one) require that it implement Deserialize.

-

Document implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your Collections with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-extern crate serde;
-use mongodb::{
-    bson::doc,
-    error::Result,
-};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-use mongodb::Client;
-
-let client = Client::with_uri_str("mongodb://example.com").await?;
-use serde::{Deserialize, Serialize};
-
-// Define a type that models our data.
-#[derive(Clone, Debug, Deserialize, Serialize)]
-struct Item {
-    id: u32,
-}
-
-// Parameterize our collection with the model.
-let coll = client.database("items").collection::<Item>("in_stock");
-
-for i in 0..5 {
-    // Perform operations that work with directly our model.
-    coll.insert_one(Item { id: i }, None).await;
-}
-
-Ok(())
-}
-}
-

For more information, see the Serde Integration section.

-

Cursors

-

Results from queries are generally returned via Cursor, a struct which streams the results back from the server as requested. The Cursor type implements the Stream trait from the futures crate, and in order to access its streaming functionality you need to import at least one of the StreamExt or TryStreamExt traits.

-
# In Cargo.toml, add the following dependency.
-futures = "0.3"
-
-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate serde;
-extern crate futures;
-use serde::Deserialize;
-#[derive(Deserialize)]
-struct Book { title: String }
-async fn foo() -> mongodb::error::Result<()> {
-let typed_collection = mongodb::Client::with_uri_str("").await?.database("").collection::<Book>("");
-// This trait is required to use `try_next()` on the cursor
-use futures::stream::TryStreamExt;
-use mongodb::{bson::doc, options::FindOptions};
-
-// Query the books in the collection with a filter and an option.
-let filter = doc! { "author": "George Orwell" };
-let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build();
-let mut cursor = typed_collection.find(filter, find_options).await?;
-
-// Iterate over the results of the cursor.
-while let Some(book) = cursor.try_next().await? {
-    println!("title: {}", book.title);
-}
-Ok(()) }
-}
-

If a Cursor is still open when it goes out of scope, it will automatically be closed via an asynchronous killCursors command executed from its Drop implementation.

-

Performance

-

Client Best Practices

-

The Client handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a Client should be used for best performance.

-

Lifetime

-

A Client should be as long-lived as possible. Establishing a new Client is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because Client is implemented using an internal Arc, it can safely be shared across threads or tasks, and cloneing it to pass to new contexts is extremely cheap.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::Client;
-use std::error::Error;
-// This will be very slow because it's constructing and tearing down a `Client`
-// with every request.
-async fn handle_request_bad() -> Result<(), Box<dyn Error>> {
-    let client = Client::with_uri_str("mongodb://example.com").await?;
-    // Do something with the client
-    Ok(())
-}
-
-// This will be much faster.
-async fn handle_request_good(client: &Client) -> Result<(), Box<dyn Error>> {
-    // Do something with the client
-    Ok(())
-}
-}
-

This is especially noticeable when using a framework that provides connection pooling; because Client does its own pooling internally, attempting to maintain a pool of Clients will (somewhat counter-intuitively) result in worse performance than using a single one.

-

Runtime

-

A Client is implicitly bound to the instance of the tokio or async-std runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the tokio::test or async_std::test helper macros create a new runtime for each test.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate once_cell;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-use tokio::runtime::Runtime;
-use once_cell::sync::Lazy;
-
-static CLIENT: Lazy<Client> = Lazy::new(|| {
-    let rt = Runtime::new().unwrap();
-    rt.block_on(async {
-        Client::with_uri_str("mongodb://example.com").await.unwrap()
-    })
-});
-
-// This will inconsistently fail.
-#[tokio::test]
-async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    CLIENT.list_database_names(None, None).await?;
-    Ok(())
-}
-}
-

To work around this issue, either create a new Client for every async test, or bundle the Runtime along with the client and don't use the test helper macros.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate once_cell;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-use tokio::runtime::Runtime;
-use once_cell::sync::Lazy;
-
-static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| {
-    let rt = Runtime::new().unwrap();
-    let client = rt.block_on(async {
-        Client::with_uri_str("mongodb://example.com").await.unwrap()
-    });
-    (client, rt)
-});
-
-#[test]
-fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    let (client, rt) = &*CLIENT_RUNTIME;
-    rt.block_on(async {
-        client.list_database_names(None, None).await
-    })?;
-    Ok(())
-}
-}
-

or

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::Client;
-use std::error::Error;
-#[tokio::test]
-async fn test_list_dbs() -> Result<(), Box<dyn Error>> {
-    let client = Client::with_uri_str("mongodb://example.com").await?;
-    CLIENT.list_database_names(None, None).await?;
-    Ok(())
-}
-}
-

Parallelism

-

Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-
-for i in 0..5 {
-    let client_ref = client.clone();
-
-    task::spawn(async move {
-        let collection = client_ref.database("items").collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
-

Tracing and Logging

-

The driver utilizes the tracing crate to emit events at points of interest. To enable this, you must turn on the tracing-unstable feature flag.

-

Stability Guarantees

-

This functionality is considered unstable as the tracing crate has not reached 1.0 yet. Future minor versions of the driver may upgrade the tracing dependency -to a new version which is not backwards-compatible with Subscribers that depend on older versions of tracing. -Additionally, future minor releases may make changes such as:

-
    -
  • add or remove tracing events
  • -
  • add or remove values attached to tracing events
  • -
  • change the types and/or names of values attached to tracing events
  • -
  • add or remove driver-defined tracing spans
  • -
  • change the severity level of tracing events
  • -
-

Such changes will be called out in release notes.

-

Event Targets

-

Currently, events are emitted under the following targets:

-
- - - -
TargetDescription
mongodb::commandEvents describing commands sent to the database and their success or failure.
mongodb::server_selectionEvents describing the driver's process of selecting a server in the database deployment to send a command to.
mongodb::connectionEvents describing the behavior of driver connection pools and the connections they contain.
-
-

Consuming Events

-

To consume events in your application, in addition to enabling the tracing-unstable feature flag, you must either register a tracing-compatible subscriber or a log-compatible logger, as detailed in the following sections.

-

Consuming Events with tracing

-

To consume events with tracing, you will need to register a type implementing the tracing::Subscriber trait in your application, as discussed in the tracing docs.

-

Here's a minimal example of a program using the driver which uses a tracing subscriber.

-

First, add the following to Cargo.toml:

-
tracing = "LATEST_VERSION_HERE"
-tracing-subscriber = "LATEST_VERSION_HERE"
-mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] }
-
-

And then in main.rs:

-
extern crate mongodb;
-extern crate tokio;
-extern crate tracing_subscriber;
-use std::env;
-use mongodb::{bson::doc, error::Result, Client};
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // Register a global tracing subscriber which will obey the RUST_LOG environment variable
-    // config.
-    tracing_subscriber::fmt::init();
-
-    // Create a MongoDB client.
-    let mongodb_uri =
-        env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set.");
-    let client = Client::with_uri_str(mongodb_uri).await?;
-
-    // Insert a document.
-    let coll = client.database("test").collection("test_coll");
-    coll.insert_one(doc! { "x" : 1 }, None).await?;
-
-    Ok(())
-}
-

This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related events with severity debug or higher:

-
RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run
-
-

The output will look something like the following:

-
2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017
-2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0
-
-

Consuming Events with log

-

Alternatively, to consume events with log, you will need to add tracing as a dependency of your application, and enable either its log or log-always feature. -Those features are described in detail here.

-

Here's a minimal example of a program using the driver which uses env_logger.

-

In Cargo.toml:

-
tracing = { version = "LATEST_VERSION_HERE", features = ["log"] }
-mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] }
-env_logger = "LATEST_VERSION_HERE"
-
-

And in main.rs:

-
extern crate mongodb;
-extern crate tokio;
-extern crate env_logger;
-use std::env;
-use mongodb::{bson::doc, error::Result, Client};
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // Register a global logger.
-    env_logger::init();
-
-    // Create a MongoDB client.
-    let mongodb_uri =
-        env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set.");
-    let client = Client::with_uri_str(mongodb_uri).await?;
-
-    // Insert a document.
-    let coll = client.database("test").collection("test_coll");
-    coll.insert_one(doc! { "x" : 1 }, None).await?;
-
-    Ok(())
-}
-

This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related messages with severity debug or higher:

-
RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run
-
-

The output will look something like the following:

-
2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017
-2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0
-
-

Web Framework Examples

-

Actix

-

The driver can be used easily with the Actix web framework by storing a Client in Actix application data. A full example application for using MongoDB with Actix can be found here.

-

Rocket

-

The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the rocket_db_pools crate contains instructions for using MongoDB with your Rocket application.

-

Unstable API

-

To enable support for in-use encryption (client-side field level encryption and queryable encryption), enable the "in-use-encryption-unstable" feature of the mongodb crate. As the name implies, the API for this feature is unstable, and may change in backwards-incompatible ways in minor releases.

-

Client-Side Field Level Encryption

-

Starting with MongoDB 4.2, client-side field level encryption allows an application to encrypt specific data fields in addition to pre-existing MongoDB encryption features such as Encryption at Rest and TLS/SSL (Transport Encryption).

-

With field level encryption, applications can encrypt fields in documents prior to transmitting data over the wire to the server. Client-side field level encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data.

-

See also the MongoDB documentation on Client Side Field Level Encryption.

-

Dependencies

-

To get started using client-side field level encryption in your project, you will need to install libmongocrypt, which can be fetched from a variety of package repositories. If you install libmongocrypt in a location outside of the system library search path, the MONGOCRYPT_LIB_DIR environment variable will need to be set when compiling your project.

-

Additionally, either crypt_shared or mongocryptd are required in order to use automatic client-side encryption.

-

crypt_shared

-

The Automatic Encryption Shared Library (crypt_shared) provides the same functionality as mongocryptd, but does not require you to spawn another process to perform automatic encryption.

-

By default, the mongodb crate attempts to load crypt_shared from the system and if found uses it automatically. To load crypt_shared from another location, set the "cryptSharedLibPath" field in extra_options:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "cryptSharedLibPath": "/path/to/crypt/shared",
-    })
-    .build();
-
-Ok(())
-}
-}
-

If the mongodb crate cannot load crypt_shared it will attempt to fallback to using mongocryptd by default. Include "cryptSharedRequired": true in the extra_options document to always use crypt_shared and fail if it could not be loaded.

-

For detailed installation instructions see the MongoDB documentation on Automatic Encryption Shared Library.

-

mongocryptd

-

If using crypt_shared is not an option, the mongocryptd binary is required for automatic client-side encryption and is included as a component in the MongoDB Enterprise Server package. For detailed installation instructions see the MongoDB documentation on mongocryptd.

-

mongocryptd performs the following:

-
    -
  • Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, mongocryptd returns an error.
  • -
  • Uses the specified automatic encryption rules to mark fields in read and write operations for encryption.
  • -
  • Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see Read/Write Support with Automatic Field Level Encryption.
  • -
-

A Client configured with auto encryption will automatically spawn the mongocryptd process from the application's PATH. Applications can control the spawning behavior as part of the automatic encryption options:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "mongocryptdSpawnPath": "/path/to/mongocryptd",
-        "mongocryptdSpawnArgs": ["--logpath=/path/to/mongocryptd.log", "--logappend"],
-    })
-    .build();
-
-Ok(())
-}
-}
-

If your application wishes to manage the mongocryptd process manually, it is possible to disable spawning mongocryptd:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-use mongodb::{bson::doc, Client, error::Result};
-
-async fn func() -> Result<()> {
-let options = todo!();
-let kv_namespace = todo!();
-let kms_providers: Vec<_> = todo!();
-let client = Client::encrypted_builder(options, kv_namespace, kms_providers)?
-    .extra_options(doc! {
-        "mongocryptdBypassSpawn": true,
-        "mongocryptdURI": "mongodb://localhost:27020",
-    })
-    .build();
-
-Ok(())
-}
-}
-

mongocryptd is only responsible for supporting automatic client-side field level encryption and does not itself perform any encryption or decryption.

-

Automatic Client-Side Field Level Encryption

-

Automatic client-side field level encryption is enabled by using the Client::encrypted_builder constructor method. The following examples show how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key.

-

Note: Automatic client-side field level encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the server supports automatic decryption as well as explicit client-side encryption.

-

Providing Local Automatic Encryption Rules

-

The following example shows how to specify automatic encryption rules via the schema_map option. The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax.

-

Supplying a schema_map provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted.

-

JSON Schemas supplied in the schema_map only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error.

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // The MongoDB namespace (db.collection) used to store the
-    // encrypted documents in this example.
-    let encrypted_namespace = Namespace::new("test", "coll");
-
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("encryption", "__testKeyVault");
-
-    // The MongoClient used to access the key vault (key_vault_namespace).
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-    // Create a new data key and json schema for the encryptedField.
-    // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_1".to_string()])
-        .run()
-        .await?;
-    let schema = doc! {
-        "properties": {
-            "encryptedField": {
-                "encrypt": {
-                    "keyId": [data_key_id],
-                    "bsonType": "string",
-                    "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
-                }
-            }
-        },
-        "bsonType": "object",
-    };
-
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .schema_map([(encrypted_namespace.to_string(), schema)])
-    .build()
-    .await?;
-    let coll = client
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    // Clear old data.
-    coll.drop(None).await?;
-
-    coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-        .await?;
-    println!("Decrypted document: {:?}", coll.find_one(None, None).await?);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-
-    Ok(())
-}
-

Server-Side Field Level Encryption Enforcement

-

The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the "encrypt" JSON schema keyword.

-

The following example shows how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key and create a collection with the Automatic Encryption JSON Schema Syntax:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::{ClientOptions, CreateCollectionOptions, WriteConcern},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // The MongoDB namespace (db.collection) used to store the
-    // encrypted documents in this example.
-    let encrypted_namespace = Namespace::new("test", "coll");
-
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("encryption", "__testKeyVault");
-
-    // The MongoClient used to access the key vault (key_vault_namespace).
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key and json schema for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_2".to_string()])
-        .run()
-        .await?;
-    let schema = doc! {
-        "properties": {
-            "encryptedField": {
-                "encrypt": {
-                    "keyId": [data_key_id],
-                    "bsonType": "string",
-                    "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
-                }
-            }
-        },
-        "bsonType": "object",
-    };
-    
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .build()
-    .await?;
-    let db = client.database(&encrypted_namespace.db);
-    let coll = db.collection::<Document>(&encrypted_namespace.coll);
-    // Clear old data
-    coll.drop(None).await?;
-    // Create the collection with the encryption JSON Schema.
-    db.create_collection(
-        &encrypted_namespace.coll,
-        CreateCollectionOptions::builder()
-            .write_concern(WriteConcern::MAJORITY)
-            .validator(doc! { "$jsonSchema": schema })
-            .build(),
-    ).await?;
-
-    coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-        .await?;
-    println!("Decrypted document: {:?}", coll.find_one(None, None).await?);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database(&encrypted_namespace.db)
-        .collection::<Document>(&encrypted_namespace.coll);
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-    // This would return a Write error with the message "Document failed validation".
-    // unencrypted_coll.insert_one(doc! { "encryptedField": "123456789" }, None)
-    //    .await?;
-
-    Ok(())
-}
-

Automatic Queryable Encryption

-

Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0.

-

Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side.

-

You must have MongoDB 6.0 Enterprise to preview the feature.

-

Automatic encryption in Queryable Encryption is configured with an encrypted_fields mapping, as demonstrated by the following example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-extern crate futures;
-static URI: &str = "mongodb://example.com";
-use futures::TryStreamExt;
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::KmsProvider,
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-    let key_vault_client = Client::with_uri_str(URI).await?;
-    let key_vault = key_vault_client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    let client_encryption = ClientEncryption::new(
-        key_vault_client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-    let key1_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["firstName".to_string()])
-        .run()
-        .await?;
-    let key2_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["lastName".to_string()])
-        .run()
-        .await?;
-
-    let encrypted_fields_map = vec![(
-        "example.encryptedCollection",
-        doc! {
-            "escCollection": "encryptedCollection.esc",
-            "eccCollection": "encryptedCollection.ecc",
-            "ecocCollection": "encryptedCollection.ecoc",
-            "fields": [
-              {
-                "path": "firstName",
-                "bsonType": "string",
-                "keyId": key1_id,
-                "queries": [{"queryType": "equality"}],
-              },
-                {
-                  "path": "lastName",
-                  "bsonType": "string",
-                  "keyId": key2_id,
-                }
-            ]
-        },
-    )];
-
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .encrypted_fields_map(encrypted_fields_map)
-    .build()
-    .await?;
-    let db = client.database("example");
-    let coll = db.collection::<Document>("encryptedCollection");
-    coll.drop(None).await?;
-    db.create_collection("encryptedCollection", None).await?;
-    coll.insert_one(
-        doc! { "_id": 1, "firstName": "Jane", "lastName": "Doe" },
-        None,
-    )
-    .await?;
-    let docs: Vec<_> = coll
-        .find(doc! {"firstName": "Jane"}, None)
-        .await?
-        .try_collect()
-        .await?;
-    println!("{:?}", docs);
-
-    Ok(())
-}
-

Explicit Queryable Encryption

-

Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0.

-

Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side.

-

Explicit encryption in Queryable Encryption is performed using the encrypt and decrypt methods. Automatic encryption (to allow the find_one to automatically decrypt) is configured using an encrypted_fields mapping, as demonstrated by the following example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{KmsProvider, Algorithm},
-    options::{ClientOptions, CreateCollectionOptions},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let client = Client::with_uri_str(URI).await?;
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let indexed_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .run()
-        .await?;
-    let unindexed_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .run()
-        .await?;
-
-    let encrypted_fields = doc! {
-      "escCollection": "enxcol_.default.esc",
-      "eccCollection": "enxcol_.default.ecc",
-      "ecocCollection": "enxcol_.default.ecoc",
-      "fields": [
-        {
-          "keyId": indexed_key_id.clone(),
-          "path": "encryptedIndexed",
-          "bsonType": "string",
-          "queries": {
-            "queryType": "equality"
-          }
-        },
-        {
-          "keyId": unindexed_key_id.clone(),
-          "path": "encryptedUnindexed",
-          "bsonType": "string",
-        }
-      ]
-    };
-
-    // The MongoClient used to read/write application data.
-    let encrypted_client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace,
-        kms_providers,
-    )?
-    .bypass_query_analysis(true)
-    .build()
-    .await?;
-    let db = encrypted_client.database("test");
-    db.drop(None).await?;
-
-    // Create the collection with encrypted fields.
-    db.create_collection(
-        "coll",
-        CreateCollectionOptions::builder()
-            .encrypted_fields(encrypted_fields)
-            .build(),
-    )
-    .await?;
-    let coll = db.collection::<Document>("coll");
-
-    // Create and encrypt an indexed and unindexed value.
-    let val = "encrypted indexed value";
-    let unindexed_val = "encrypted unindexed value";
-    let insert_payload_indexed = client_encryption
-        .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed)
-        .contention_factor(1)
-        .run()
-        .await?;
-    let insert_payload_unindexed = client_encryption
-        .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed)
-        .run()
-        .await?;
-
-    // Insert the payloads.
-    coll.insert_one(
-        doc! {
-            "encryptedIndexed": insert_payload_indexed,
-            "encryptedUnindexed": insert_payload_unindexed,
-        },
-        None,
-    )
-    .await?;
-
-    // Encrypt our find payload using QueryType.EQUALITY.
-    // The value of `data_key_id` must be the same as used to encrypt the values
-    // above.
-    let find_payload = client_encryption
-        .encrypt(val, indexed_key_id, Algorithm::Indexed)
-        .query_type("equality")
-        .contention_factor(1)
-        .run()
-        .await?;
-
-    // Find the document we inserted using the encrypted payload.
-    // The returned document is automatically decrypted.
-    let doc = coll
-        .find_one(doc! { "encryptedIndexed": find_payload }, None)
-        .await?;
-    println!("Returned document: {:?}", doc);
-
-    Ok(())
-}
-

Explicit Encryption

-

Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the ClientEncryption struct, for example:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Bson, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{Algorithm, KmsProvider},
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // The MongoClient used to read/write application data.
-    let client = Client::with_uri_str(URI).await?;
-    let coll = client.database("test").collection::<Document>("coll");
-    // Clear old data
-    coll.drop(None).await?;
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_3".to_string()])
-        .run()
-        .await?;
-
-    // Explicitly encrypt a field:
-    let encrypted_field = client_encryption
-        .encrypt(
-            "123456789",
-            data_key_id,
-            Algorithm::AeadAes256CbcHmacSha512Deterministic,
-        )
-        .run()
-        .await?;
-    coll.insert_one(doc! { "encryptedField": encrypted_field }, None)
-        .await?;
-    let mut doc = coll.find_one(None, None).await?.unwrap();
-    println!("Encrypted document: {:?}", doc);
-
-    // Explicitly decrypt the field:
-    let field = match doc.get("encryptedField") {
-        Some(Bson::Binary(bin)) => bin,
-        _ => panic!("invalid field"),
-    };
-    let decrypted: Bson = client_encryption
-        .decrypt(field.as_raw_binary())
-        .await?
-        .try_into()?;
-    doc.insert("encryptedField", decrypted);
-    println!("Decrypted document: {:?}", doc);
-
-    Ok(())
-}
-

Explicit Encryption with Automatic Decryption

-

Although automatic encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set bypass_auto_encryption to true in the EncryptedClientBuilder:

- -
extern crate mongodb;
-extern crate tokio;
-extern crate rand;
-static URI: &str = "mongodb://example.com";
-use mongodb::{
-    bson::{self, doc, Document},
-    client_encryption::{ClientEncryption, MasterKey},
-    error::Result,
-    mongocrypt::ctx::{Algorithm, KmsProvider},
-    options::ClientOptions,
-    Client,
-    Namespace,
-};
-use rand::Rng;
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // This must be the same master key that was used to create
-    // the encryption key.
-    let mut key_bytes = vec![0u8; 96];
-    rand::thread_rng().fill(&mut key_bytes[..]);
-    let local_master_key = bson::Binary {
-        subtype: bson::spec::BinarySubtype::Generic,
-        bytes: key_bytes,
-    };
-    let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)];
-
-    // The MongoDB namespace (db.collection) used to store
-    // the encryption data keys.
-    let key_vault_namespace = Namespace::new("keyvault", "datakeys");
-
-    // `bypass_auto_encryption(true)` disables automatic encryption but keeps
-    // the automatic _decryption_ behavior. bypass_auto_encryption will
-    // also disable spawning mongocryptd.
-    let client = Client::encrypted_builder(
-        ClientOptions::parse(URI).await?,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?
-    .bypass_auto_encryption(true)
-    .build()
-    .await?;
-    let coll = client.database("test").collection::<Document>("coll");
-    // Clear old data
-    coll.drop(None).await?;
-
-    // Set up the key vault (key_vault_namespace) for this example.
-    let key_vault = client
-        .database(&key_vault_namespace.db)
-        .collection::<Document>(&key_vault_namespace.coll);
-    key_vault.drop(None).await?;
-
-    let client_encryption = ClientEncryption::new(
-        // The MongoClient to use for reading/writing to the key vault.
-        // This can be the same MongoClient used by the main application.
-        client,
-        key_vault_namespace.clone(),
-        kms_providers.clone(),
-    )?;
-
-    // Create a new data key for the encryptedField.
-    let data_key_id = client_encryption
-        .create_data_key(MasterKey::Local)
-        .key_alt_names(["encryption_example_4".to_string()])
-        .run()
-        .await?;
-
-    // Explicitly encrypt a field:
-    let encrypted_field = client_encryption
-        .encrypt(
-            "123456789",
-            data_key_id,
-            Algorithm::AeadAes256CbcHmacSha512Deterministic,
-        )
-        .run()
-        .await?;
-    coll.insert_one(doc! { "encryptedField": encrypted_field }, None)
-        .await?;
-    // Automatically decrypts any encrypted fields.
-    let doc = coll.find_one(None, None).await?.unwrap();
-    println!("Decrypted document: {:?}", doc);
-    let unencrypted_coll = Client::with_uri_str(URI)
-        .await?
-        .database("test")
-        .collection::<Document>("coll");
-    println!(
-        "Encrypted document: {:?}",
-        unencrypted_coll.find_one(None, None).await?
-    );
-
-    Ok(())
-}
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/reading.html b/docs/manual/reading.html deleted file mode 100644 index 3e7bdd46e..000000000 --- a/docs/manual/reading.html +++ /dev/null @@ -1,289 +0,0 @@ - - - - - - Reading From the Database - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Reading From the Database

-

Database and Collection Handles

-

Once you have a Client, you can call Client::database to create a handle to a particular database on the server, and Database::collection to create a handle to a particular collection in that database. Database and Collection handles are lightweight - creating them requires no IO, cloneing them is cheap, and they can be safely shared across threads or async tasks. For example:

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-use mongodb::{bson::Document, Client, error::Result};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-let client = Client::with_uri_str("mongodb://example.com").await?;
-let db = client.database("items");
-
-for i in 0..5 {
-    let db_ref = db.clone();
-
-    task::spawn(async move {
-        let collection = db_ref.collection::<Document>(&format!("coll{}", i));
-
-        // Do something with the collection
-    });
-}
-
-Ok(())
-}
-}
-

A Collection can be parameterized with a type for the documents in the collection; this includes but is not limited to just Document. The various methods that accept instances of the documents (e.g. Collection::insert_one) require that it implement the Serialize trait from the serde crate. Similarly, the methods that return instances (e.g. Collection::find_one) require that it implement Deserialize.

-

Document implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your Collections with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant.

-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate tokio;
-extern crate serde;
-use mongodb::{
-    bson::doc,
-    error::Result,
-};
-use tokio::task;
-
-async fn start_workers() -> Result<()> {
-use mongodb::Client;
-
-let client = Client::with_uri_str("mongodb://example.com").await?;
-use serde::{Deserialize, Serialize};
-
-// Define a type that models our data.
-#[derive(Clone, Debug, Deserialize, Serialize)]
-struct Item {
-    id: u32,
-}
-
-// Parameterize our collection with the model.
-let coll = client.database("items").collection::<Item>("in_stock");
-
-for i in 0..5 {
-    // Perform operations that work with directly our model.
-    coll.insert_one(Item { id: i }, None).await;
-}
-
-Ok(())
-}
-}
-

For more information, see the Serde Integration section.

-

Cursors

-

Results from queries are generally returned via Cursor, a struct which streams the results back from the server as requested. The Cursor type implements the Stream trait from the futures crate, and in order to access its streaming functionality you need to import at least one of the StreamExt or TryStreamExt traits.

-
# In Cargo.toml, add the following dependency.
-futures = "0.3"
-
-
#![allow(unused)]
-fn main() {
-extern crate mongodb;
-extern crate serde;
-extern crate futures;
-use serde::Deserialize;
-#[derive(Deserialize)]
-struct Book { title: String }
-async fn foo() -> mongodb::error::Result<()> {
-let typed_collection = mongodb::Client::with_uri_str("").await?.database("").collection::<Book>("");
-// This trait is required to use `try_next()` on the cursor
-use futures::stream::TryStreamExt;
-use mongodb::{bson::doc, options::FindOptions};
-
-// Query the books in the collection with a filter and an option.
-let filter = doc! { "author": "George Orwell" };
-let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build();
-let mut cursor = typed_collection.find(filter, find_options).await?;
-
-// Iterate over the results of the cursor.
-while let Some(book) = cursor.try_next().await? {
-    println!("title: {}", book.title);
-}
-Ok(()) }
-}
-

If a Cursor is still open when it goes out of scope, it will automatically be closed via an asynchronous killCursors command executed from its Drop implementation.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/searcher.js b/docs/manual/searcher.js deleted file mode 100644 index d2b0aeed3..000000000 --- a/docs/manual/searcher.js +++ /dev/null @@ -1,483 +0,0 @@ -"use strict"; -window.search = window.search || {}; -(function search(search) { - // Search functionality - // - // You can use !hasFocus() to prevent keyhandling in your key - // event handlers while the user is typing their search. - - if (!Mark || !elasticlunr) { - return; - } - - //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith - if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search, pos) { - return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; - }; - } - - var search_wrap = document.getElementById('search-wrapper'), - searchbar = document.getElementById('searchbar'), - searchbar_outer = document.getElementById('searchbar-outer'), - searchresults = document.getElementById('searchresults'), - searchresults_outer = document.getElementById('searchresults-outer'), - searchresults_header = document.getElementById('searchresults-header'), - searchicon = document.getElementById('search-toggle'), - content = document.getElementById('content'), - - searchindex = null, - doc_urls = [], - results_options = { - teaser_word_count: 30, - limit_results: 30, - }, - search_options = { - bool: "AND", - expand: true, - fields: { - title: {boost: 1}, - body: {boost: 1}, - breadcrumbs: {boost: 0} - } - }, - mark_exclude = [], - marker = new Mark(content), - current_searchterm = "", - URL_SEARCH_PARAM = 'search', - URL_MARK_PARAM = 'highlight', - teaser_count = 0, - - SEARCH_HOTKEY_KEYCODE = 83, - ESCAPE_KEYCODE = 27, - DOWN_KEYCODE = 40, - UP_KEYCODE = 38, - SELECT_KEYCODE = 13; - - function hasFocus() { - return searchbar === document.activeElement; - } - - function removeChildren(elem) { - while (elem.firstChild) { - elem.removeChild(elem.firstChild); - } - } - - // Helper to parse a url into its building blocks. - function parseURL(url) { - var a = document.createElement('a'); - a.href = url; - return { - source: url, - protocol: a.protocol.replace(':',''), - host: a.hostname, - port: a.port, - params: (function(){ - var ret = {}; - var seg = a.search.replace(/^\?/,'').split('&'); - var len = seg.length, i = 0, s; - for (;i': '>', - '"': '"', - "'": ''' - }; - var repl = function(c) { return MAP[c]; }; - return function(s) { - return s.replace(/[&<>'"]/g, repl); - }; - })(); - - function formatSearchMetric(count, searchterm) { - if (count == 1) { - return count + " search result for '" + searchterm + "':"; - } else if (count == 0) { - return "No search results for '" + searchterm + "'."; - } else { - return count + " search results for '" + searchterm + "':"; - } - } - - function formatSearchResult(result, searchterms) { - var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); - teaser_count++; - - // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor - var url = doc_urls[result.ref].split("#"); - if (url.length == 1) { // no anchor found - url.push(""); - } - - // encodeURIComponent escapes all chars that could allow an XSS except - // for '. Due to that we also manually replace ' with its url-encoded - // representation (%27). - var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); - - return '' + result.doc.breadcrumbs + '' - + '' - + teaser + ''; - } - - function makeTeaser(body, searchterms) { - // The strategy is as follows: - // First, assign a value to each word in the document: - // Words that correspond to search terms (stemmer aware): 40 - // Normal words: 2 - // First word in a sentence: 8 - // Then use a sliding window with a constant number of words and count the - // sum of the values of the words within the window. Then use the window that got the - // maximum sum. If there are multiple maximas, then get the last one. - // Enclose the terms in . - var stemmed_searchterms = searchterms.map(function(w) { - return elasticlunr.stemmer(w.toLowerCase()); - }); - var searchterm_weight = 40; - var weighted = []; // contains elements of ["word", weight, index_in_document] - // split in sentences, then words - var sentences = body.toLowerCase().split('. '); - var index = 0; - var value = 0; - var searchterm_found = false; - for (var sentenceindex in sentences) { - var words = sentences[sentenceindex].split(' '); - value = 8; - for (var wordindex in words) { - var word = words[wordindex]; - if (word.length > 0) { - for (var searchtermindex in stemmed_searchterms) { - if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { - value = searchterm_weight; - searchterm_found = true; - } - }; - weighted.push([word, value, index]); - value = 2; - } - index += word.length; - index += 1; // ' ' or '.' if last word in sentence - }; - index += 1; // because we split at a two-char boundary '. ' - }; - - if (weighted.length == 0) { - return body; - } - - var window_weight = []; - var window_size = Math.min(weighted.length, results_options.teaser_word_count); - - var cur_sum = 0; - for (var wordindex = 0; wordindex < window_size; wordindex++) { - cur_sum += weighted[wordindex][1]; - }; - window_weight.push(cur_sum); - for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { - cur_sum -= weighted[wordindex][1]; - cur_sum += weighted[wordindex + window_size][1]; - window_weight.push(cur_sum); - }; - - if (searchterm_found) { - var max_sum = 0; - var max_sum_window_index = 0; - // backwards - for (var i = window_weight.length - 1; i >= 0; i--) { - if (window_weight[i] > max_sum) { - max_sum = window_weight[i]; - max_sum_window_index = i; - } - }; - } else { - max_sum_window_index = 0; - } - - // add around searchterms - var teaser_split = []; - var index = weighted[max_sum_window_index][2]; - for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { - var word = weighted[i]; - if (index < word[2]) { - // missing text from index to start of `word` - teaser_split.push(body.substring(index, word[2])); - index = word[2]; - } - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - index = word[2] + word[0].length; - teaser_split.push(body.substring(word[2], index)); - if (word[1] == searchterm_weight) { - teaser_split.push("") - } - }; - - return teaser_split.join(''); - } - - function init(config) { - results_options = config.results_options; - search_options = config.search_options; - searchbar_outer = config.searchbar_outer; - doc_urls = config.doc_urls; - searchindex = elasticlunr.Index.load(config.index); - - // Set up events - searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); - searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); - document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); - // If the user uses the browser buttons, do the same as if a reload happened - window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; - // Suppress "submit" events so the page doesn't reload when the user presses Enter - document.addEventListener('submit', function(e) { e.preventDefault(); }, false); - - // If reloaded, do the search or mark again, depending on the current url parameters - doSearchOrMarkFromUrl(); - } - - function unfocusSearchbar() { - // hacky, but just focusing a div only works once - var tmp = document.createElement('input'); - tmp.setAttribute('style', 'position: absolute; opacity: 0;'); - searchicon.appendChild(tmp); - tmp.focus(); - tmp.remove(); - } - - // On reload or browser history backwards/forwards events, parse the url and do search or mark - function doSearchOrMarkFromUrl() { - // Check current URL for search request - var url = parseURL(window.location.href); - if (url.params.hasOwnProperty(URL_SEARCH_PARAM) - && url.params[URL_SEARCH_PARAM] != "") { - showSearch(true); - searchbar.value = decodeURIComponent( - (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); - searchbarKeyUpHandler(); // -> doSearch() - } else { - showSearch(false); - } - - if (url.params.hasOwnProperty(URL_MARK_PARAM)) { - var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); - marker.mark(words, { - exclude: mark_exclude - }); - - var markers = document.querySelectorAll("mark"); - function hide() { - for (var i = 0; i < markers.length; i++) { - markers[i].classList.add("fade-out"); - window.setTimeout(function(e) { marker.unmark(); }, 300); - } - } - for (var i = 0; i < markers.length; i++) { - markers[i].addEventListener('click', hide); - } - } - } - - // Eventhandler for keyevents on `document` - function globalKeyHandler(e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; } - - if (e.keyCode === ESCAPE_KEYCODE) { - e.preventDefault(); - searchbar.classList.remove("active"); - setSearchUrlParameters("", - (searchbar.value.trim() !== "") ? "push" : "replace"); - if (hasFocus()) { - unfocusSearchbar(); - } - showSearch(false); - marker.unmark(); - } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { - e.preventDefault(); - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { - e.preventDefault(); - unfocusSearchbar(); - searchresults.firstElementChild.classList.add("focus"); - } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE - || e.keyCode === UP_KEYCODE - || e.keyCode === SELECT_KEYCODE)) { - // not `:focus` because browser does annoying scrolling - var focused = searchresults.querySelector("li.focus"); - if (!focused) return; - e.preventDefault(); - if (e.keyCode === DOWN_KEYCODE) { - var next = focused.nextElementSibling; - if (next) { - focused.classList.remove("focus"); - next.classList.add("focus"); - } - } else if (e.keyCode === UP_KEYCODE) { - focused.classList.remove("focus"); - var prev = focused.previousElementSibling; - if (prev) { - prev.classList.add("focus"); - } else { - searchbar.select(); - } - } else { // SELECT_KEYCODE - window.location.assign(focused.querySelector('a')); - } - } - } - - function showSearch(yes) { - if (yes) { - search_wrap.classList.remove('hidden'); - searchicon.setAttribute('aria-expanded', 'true'); - } else { - search_wrap.classList.add('hidden'); - searchicon.setAttribute('aria-expanded', 'false'); - var results = searchresults.children; - for (var i = 0; i < results.length; i++) { - results[i].classList.remove("focus"); - } - } - } - - function showResults(yes) { - if (yes) { - searchresults_outer.classList.remove('hidden'); - } else { - searchresults_outer.classList.add('hidden'); - } - } - - // Eventhandler for search icon - function searchIconClickHandler() { - if (search_wrap.classList.contains('hidden')) { - showSearch(true); - window.scrollTo(0, 0); - searchbar.select(); - } else { - showSearch(false); - } - } - - // Eventhandler for keyevents while the searchbar is focused - function searchbarKeyUpHandler() { - var searchterm = searchbar.value.trim(); - if (searchterm != "") { - searchbar.classList.add("active"); - doSearch(searchterm); - } else { - searchbar.classList.remove("active"); - showResults(false); - removeChildren(searchresults); - } - - setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); - - // Remove marks - marker.unmark(); - } - - // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . - // `action` can be one of "push", "replace", "push_if_new_search_else_replace" - // and replaces or pushes a new browser history item. - // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. - function setSearchUrlParameters(searchterm, action) { - var url = parseURL(window.location.href); - var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); - if (searchterm != "" || action == "push_if_new_search_else_replace") { - url.params[URL_SEARCH_PARAM] = searchterm; - delete url.params[URL_MARK_PARAM]; - url.hash = ""; - } else { - delete url.params[URL_MARK_PARAM]; - delete url.params[URL_SEARCH_PARAM]; - } - // A new search will also add a new history item, so the user can go back - // to the page prior to searching. A updated search term will only replace - // the url. - if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { - history.pushState({}, document.title, renderURL(url)); - } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { - history.replaceState({}, document.title, renderURL(url)); - } - } - - function doSearch(searchterm) { - - // Don't search the same twice - if (current_searchterm == searchterm) { return; } - else { current_searchterm = searchterm; } - - if (searchindex == null) { return; } - - // Do the actual search - var results = searchindex.search(searchterm, search_options); - var resultcount = Math.min(results.length, results_options.limit_results); - - // Display search metrics - searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); - - // Clear and insert results - var searchterms = searchterm.split(' '); - removeChildren(searchresults); - for(var i = 0; i < resultcount ; i++){ - var resultElem = document.createElement('li'); - resultElem.innerHTML = formatSearchResult(results[i], searchterms); - searchresults.appendChild(resultElem); - } - - // Display results - showResults(true); - } - - fetch(path_to_root + 'searchindex.json') - .then(response => response.json()) - .then(json => init(json)) - .catch(error => { // Try to load searchindex.js if fetch failed - var script = document.createElement('script'); - script.src = path_to_root + 'searchindex.js'; - script.onload = () => init(window.search); - document.head.appendChild(script); - }); - - // Exported functions - search.hasFocus = hasFocus; -})(window.search); diff --git a/docs/manual/searchindex.js b/docs/manual/searchindex.js deleted file mode 100644 index 525fd001c..000000000 --- a/docs/manual/searchindex.js +++ /dev/null @@ -1 +0,0 @@ -Object.assign(window.search, {"doc_urls":["index.html#introduction","index.html#warning-about-timeouts--cancellation","index.html#minimum-supported-rust-version-msrv","installation_features.html#installation-and-features","installation_features.html#importing","installation_features.html#configuring-the-async-runtime","installation_features.html#enabling-the-sync-api","installation_features.html#all-feature-flags","connecting.html#connecting-to-the-database","connecting.html#connection-string","connecting.html#creating-a-client","connecting.html#client-performance","reading.html#reading-from-the-database","reading.html#database-and-collection-handles","reading.html#cursors","performance.html#performance","performance.html#client-best-practices","performance.html#lifetime","performance.html#runtime","performance.html#parallelism","tracing.html#tracing-and-logging","tracing.html#stability-guarantees","tracing.html#event-targets","tracing.html#consuming-events","tracing.html#consuming-events-with-tracing","tracing.html#consuming-events-with-log","web_framework_examples.html#web-framework-examples","web_framework_examples.html#actix","web_framework_examples.html#rocket","encryption.html#unstable-api","encryption.html#client-side-field-level-encryption","encryption.html#dependencies","encryption.html#crypt_shared","encryption.html#mongocryptd","encryption.html#automatic-client-side-field-level-encryption","encryption.html#providing-local-automatic-encryption-rules","encryption.html#server-side-field-level-encryption-enforcement","encryption.html#automatic-queryable-encryption","encryption.html#explicit-queryable-encryption","encryption.html#explicit-encryption","encryption.html#explicit-encryption-with-automatic-decryption"],"index":{"documentStore":{"docInfo":{"0":{"body":44,"breadcrumbs":2,"title":1},"1":{"body":106,"breadcrumbs":4,"title":3},"10":{"body":88,"breadcrumbs":4,"title":2},"11":{"body":24,"breadcrumbs":4,"title":2},"12":{"body":0,"breadcrumbs":4,"title":2},"13":{"body":172,"breadcrumbs":5,"title":3},"14":{"body":110,"breadcrumbs":3,"title":1},"15":{"body":0,"breadcrumbs":2,"title":1},"16":{"body":27,"breadcrumbs":4,"title":3},"17":{"body":92,"breadcrumbs":2,"title":1},"18":{"body":151,"breadcrumbs":2,"title":1},"19":{"body":50,"breadcrumbs":2,"title":1},"2":{"body":11,"breadcrumbs":6,"title":5},"20":{"body":14,"breadcrumbs":4,"title":2},"21":{"body":65,"breadcrumbs":4,"title":2},"22":{"body":36,"breadcrumbs":4,"title":2},"23":{"body":19,"breadcrumbs":4,"title":2},"24":{"body":146,"breadcrumbs":5,"title":3},"25":{"body":144,"breadcrumbs":5,"title":3},"26":{"body":0,"breadcrumbs":6,"title":3},"27":{"body":19,"breadcrumbs":4,"title":1},"28":{"body":19,"breadcrumbs":4,"title":1},"29":{"body":29,"breadcrumbs":3,"title":2},"3":{"body":0,"breadcrumbs":4,"title":2},"30":{"body":64,"breadcrumbs":6,"title":5},"31":{"body":40,"breadcrumbs":2,"title":1},"32":{"body":90,"breadcrumbs":2,"title":1},"33":{"body":177,"breadcrumbs":2,"title":1},"34":{"body":54,"breadcrumbs":7,"title":6},"35":{"body":233,"breadcrumbs":6,"title":5},"36":{"body":236,"breadcrumbs":7,"title":6},"37":{"body":194,"breadcrumbs":4,"title":3},"38":{"body":300,"breadcrumbs":4,"title":3},"39":{"body":179,"breadcrumbs":3,"title":2},"4":{"body":13,"breadcrumbs":3,"title":1},"40":{"body":194,"breadcrumbs":5,"title":4},"5":{"body":47,"breadcrumbs":5,"title":3},"6":{"body":34,"breadcrumbs":5,"title":3},"7":{"body":175,"breadcrumbs":4,"title":2},"8":{"body":0,"breadcrumbs":4,"title":2},"9":{"body":53,"breadcrumbs":4,"title":2}},"docs":{"0":{"body":"Crates.io docs.rs License This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that supports either tokio (default) or async-std , depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag.","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how tokio::time::timeout works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more details). As such, it is highly recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like tokio::time::timeout with the driver, one option is to spawn tasks and time out on their JoinHandle futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout. e.g. # extern crate mongodb;\n# extern crate tokio;\n# use std::time::Duration;\n# use mongodb::{\n# Client,\n# bson::doc,\n# };\n#\n# async fn foo() -> std::result::Result<(), Box> {\n#\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nlet collection = client.database(\"foo\").collection(\"bar\");\nlet handle = tokio::task::spawn(async move { collection.insert_one(doc! { \"x\": 1 }, None).await\n}); tokio::time::timeout(Duration::from_secs(5), handle).await???;\n# Ok(())\n# }","breadcrumbs":"Introduction » Warning about timeouts / cancellation","id":"1","title":"Warning about timeouts / cancellation"},"10":{"body":"The Client struct is the main entry point for the driver. You can create one from a ClientOptions struct: # extern crate mongodb;\n# use mongodb::{Client, options::ClientOptions};\n# async fn run() -> mongodb::error::Result<()> {\n# let options = ClientOptions::parse(\"mongodb://mongodb0.example.com:27017\").await?;\nlet client = Client::with_options(options)?;\n# Ok(())\n# } As a convenience, if you don't need to modify the ClientOptions before creating the Client, you can directly create one from the connection string: # extern crate mongodb;\n# use mongodb::Client;\n# async fn run() -> mongodb::error::Result<()> {\nlet client = Client::with_uri_str(\"mongodb://mongodb0.example.com:27017\").await?;\n# Ok(())\n# } Client uses std::sync::Arc internally, so it can safely be shared across threads or async tasks. For example: # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\nlet client = Client::with_uri_str(\"mongodb://example.com\").await?; for i in 0..5 { let client_ref = client.clone(); task::spawn(async move { let collection = client_ref.database(\"items\").collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# }","breadcrumbs":"Connecting to the Database » Creating a Client","id":"10","title":"Creating a Client"},"11":{"body":"While cloning a Client is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single Client and persist it for the lifetime of your application. For more information, see the Performance chapter.","breadcrumbs":"Connecting to the Database » Client Performance","id":"11","title":"Client Performance"},"12":{"body":"","breadcrumbs":"Reading From the Database » Reading From the Database","id":"12","title":"Reading From the Database"},"13":{"body":"Once you have a Client, you can call Client::database to create a handle to a particular database on the server, and Database::collection to create a handle to a particular collection in that database. Database and Collection handles are lightweight - creating them requires no IO, cloneing them is cheap, and they can be safely shared across threads or async tasks. For example: # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nlet db = client.database(\"items\"); for i in 0..5 { let db_ref = db.clone(); task::spawn(async move { let collection = db_ref.collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# } A Collection can be parameterized with a type for the documents in the collection; this includes but is not limited to just Document. The various methods that accept instances of the documents (e.g. Collection::insert_one ) require that it implement the Serialize trait from the serde crate. Similarly, the methods that return instances (e.g. Collection::find_one ) require that it implement Deserialize. Document implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your Collections with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant. # extern crate mongodb;\n# extern crate tokio;\n# extern crate serde;\n# use mongodb::{\n# bson::doc,\n# error::Result,\n# };\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\n# use mongodb::Client;\n#\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nuse serde::{Deserialize, Serialize}; // Define a type that models our data.\n#[derive(Clone, Debug, Deserialize, Serialize)]\nstruct Item { id: u32,\n} // Parameterize our collection with the model.\nlet coll = client.database(\"items\").collection::(\"in_stock\"); for i in 0..5 { // Perform operations that work with directly our model. coll.insert_one(Item { id: i }, None).await;\n}\n#\n# Ok(())\n# } For more information, see the Serde Integration section.","breadcrumbs":"Reading From the Database » Database and Collection Handles","id":"13","title":"Database and Collection Handles"},"14":{"body":"Results from queries are generally returned via Cursor , a struct which streams the results back from the server as requested. The Cursor type implements the Stream trait from the futures crate, and in order to access its streaming functionality you need to import at least one of the StreamExt or TryStreamExt traits. # In Cargo.toml, add the following dependency.\nfutures = \"0.3\" # extern crate mongodb;\n# extern crate serde;\n# extern crate futures;\n# use serde::Deserialize;\n# #[derive(Deserialize)]\n# struct Book { title: String }\n# async fn foo() -> mongodb::error::Result<()> {\n# let typed_collection = mongodb::Client::with_uri_str(\"\").await?.database(\"\").collection::(\"\");\n// This trait is required to use `try_next()` on the cursor\nuse futures::stream::TryStreamExt;\nuse mongodb::{bson::doc, options::FindOptions}; // Query the books in the collection with a filter and an option.\nlet filter = doc! { \"author\": \"George Orwell\" };\nlet find_options = FindOptions::builder().sort(doc! { \"title\": 1 }).build();\nlet mut cursor = typed_collection.find(filter, find_options).await?; // Iterate over the results of the cursor.\nwhile let Some(book) = cursor.try_next().await? { println!(\"title: {}\", book.title);\n}\n# Ok(()) } If a Cursor is still open when it goes out of scope, it will automatically be closed via an asynchronous killCursors command executed from its Drop implementation.","breadcrumbs":"Reading From the Database » Cursors","id":"14","title":"Cursors"},"15":{"body":"","breadcrumbs":"Performance » Performance","id":"15","title":"Performance"},"16":{"body":"The Client handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a Client should be used for best performance.","breadcrumbs":"Performance » Client Best Practices","id":"16","title":"Client Best Practices"},"17":{"body":"A Client should be as long-lived as possible. Establishing a new Client is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because Client is implemented using an internal Arc , it can safely be shared across threads or tasks, and cloneing it to pass to new contexts is extremely cheap. # extern crate mongodb;\n# use mongodb::Client;\n# use std::error::Error;\n// This will be very slow because it's constructing and tearing down a `Client`\n// with every request.\nasync fn handle_request_bad() -> Result<(), Box> { let client = Client::with_uri_str(\"mongodb://example.com\").await?; // Do something with the client Ok(())\n} // This will be much faster.\nasync fn handle_request_good(client: &Client) -> Result<(), Box> { // Do something with the client Ok(())\n} This is especially noticeable when using a framework that provides connection pooling; because Client does its own pooling internally, attempting to maintain a pool of Clients will (somewhat counter-intuitively) result in worse performance than using a single one.","breadcrumbs":"Performance » Lifetime","id":"17","title":"Lifetime"},"18":{"body":"A Client is implicitly bound to the instance of the tokio or async-std runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the tokio::test or async_std::test helper macros create a new runtime for each test. # extern crate mongodb;\n# extern crate once_cell;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\nuse tokio::runtime::Runtime;\nuse once_cell::sync::Lazy; static CLIENT: Lazy = Lazy::new(|| { let rt = Runtime::new().unwrap(); rt.block_on(async { Client::with_uri_str(\"mongodb://example.com\").await.unwrap() })\n}); // This will inconsistently fail.\n#[tokio::test]\nasync fn test_list_dbs() -> Result<(), Box> { CLIENT.list_database_names(None, None).await?; Ok(())\n} To work around this issue, either create a new Client for every async test, or bundle the Runtime along with the client and don't use the test helper macros. # extern crate mongodb;\n# extern crate once_cell;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\nuse tokio::runtime::Runtime;\nuse once_cell::sync::Lazy; static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| { let rt = Runtime::new().unwrap(); let client = rt.block_on(async { Client::with_uri_str(\"mongodb://example.com\").await.unwrap() }); (client, rt)\n}); #[test]\nfn test_list_dbs() -> Result<(), Box> { let (client, rt) = &*CLIENT_RUNTIME; rt.block_on(async { client.list_database_names(None, None).await })?; Ok(())\n} or # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\n#[tokio::test]\nasync fn test_list_dbs() -> Result<(), Box> { let client = Client::with_uri_str(\"mongodb://example.com\").await?; CLIENT.list_database_names(None, None).await?; Ok(())\n}","breadcrumbs":"Performance » Runtime","id":"18","title":"Runtime"},"19":{"body":"Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations. # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\nlet client = Client::with_uri_str(\"mongodb://example.com\").await?; for i in 0..5 { let client_ref = client.clone(); task::spawn(async move { let collection = client_ref.database(\"items\").collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# }","breadcrumbs":"Performance » Parallelism","id":"19","title":"Parallelism"},"2":{"body":"The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, it will only happen in a minor or major version release.","breadcrumbs":"Introduction » Minimum supported Rust version (MSRV)","id":"2","title":"Minimum supported Rust version (MSRV)"},"20":{"body":"The driver utilizes the tracing crate to emit events at points of interest. To enable this, you must turn on the tracing-unstable feature flag.","breadcrumbs":"Tracing and Logging » Tracing and Logging","id":"20","title":"Tracing and Logging"},"21":{"body":"This functionality is considered unstable as the tracing crate has not reached 1.0 yet. Future minor versions of the driver may upgrade the tracing dependency to a new version which is not backwards-compatible with Subscribers that depend on older versions of tracing. Additionally, future minor releases may make changes such as: add or remove tracing events add or remove values attached to tracing events change the types and/or names of values attached to tracing events add or remove driver-defined tracing spans change the severity level of tracing events Such changes will be called out in release notes.","breadcrumbs":"Tracing and Logging » Stability Guarantees","id":"21","title":"Stability Guarantees"},"22":{"body":"Currently, events are emitted under the following targets: Target Description mongodb::command Events describing commands sent to the database and their success or failure. mongodb::server_selection Events describing the driver's process of selecting a server in the database deployment to send a command to. mongodb::connection Events describing the behavior of driver connection pools and the connections they contain.","breadcrumbs":"Tracing and Logging » Event Targets","id":"22","title":"Event Targets"},"23":{"body":"To consume events in your application, in addition to enabling the tracing-unstable feature flag, you must either register a tracing-compatible subscriber or a log-compatible logger, as detailed in the following sections.","breadcrumbs":"Tracing and Logging » Consuming Events","id":"23","title":"Consuming Events"},"24":{"body":"To consume events with tracing, you will need to register a type implementing the tracing::Subscriber trait in your application, as discussed in the tracing docs . Here's a minimal example of a program using the driver which uses a tracing subscriber. First, add the following to Cargo.toml: tracing = \"LATEST_VERSION_HERE\"\ntracing-subscriber = \"LATEST_VERSION_HERE\"\nmongodb = { version = \"LATEST_VERSION_HERE\", features = [\"tracing-unstable\"] } And then in main.rs: # extern crate mongodb;\n# extern crate tokio;\n# extern crate tracing_subscriber;\n# use std::env;\nuse mongodb::{bson::doc, error::Result, Client}; #[tokio::main]\nasync fn main() -> Result<()> { // Register a global tracing subscriber which will obey the RUST_LOG environment variable // config. tracing_subscriber::fmt::init(); // Create a MongoDB client. let mongodb_uri = env::var(\"MONGODB_URI\").expect(\"The MONGODB_URI environment variable was not set.\"); let client = Client::with_uri_str(mongodb_uri).await?; // Insert a document. let coll = client.database(\"test\").collection(\"test_coll\"); coll.insert_one(doc! { \"x\" : 1 }, None).await?; Ok(())\n} This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related events with severity debug or higher: RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run The output will look something like the following: 2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId=\"63dd5e706af9908fc834fd94\" command=\"{\\\"insert\\\":\\\"test_coll\\\",\\\"ordered\\\":true,\\\"$db\\\":\\\"test\\\",\\\"lsid\\\":{\\\"id\\\":{\\\"$binary\\\":{\\\"base64\\\":\\\"y/v7PiLaRwOhT0RBFRDtNw==\\\",\\\"subType\\\":\\\"04\\\"}}},\\\"documents\\\":[{\\\"_id\\\":{\\\"$oid\\\":\\\"63dd5e706af9908fc834fd95\\\"},\\\"x\\\":1}]}\" databaseName=\"test\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017\n2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId=\"63dd5e706af9908fc834fd94\" reply=\"{\\\"n\\\":1,\\\"ok\\\":1.0}\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017 durationMS=0","breadcrumbs":"Tracing and Logging » Consuming Events with tracing","id":"24","title":"Consuming Events with tracing"},"25":{"body":"Alternatively, to consume events with log, you will need to add tracing as a dependency of your application, and enable either its log or log-always feature. Those features are described in detail here . Here's a minimal example of a program using the driver which uses env_logger . In Cargo.toml: tracing = { version = \"LATEST_VERSION_HERE\", features = [\"log\"] }\nmongodb = { version = \"LATEST_VERSION_HERE\", features = [\"tracing-unstable\"] }\nenv_logger = \"LATEST_VERSION_HERE\" And in main.rs: # extern crate mongodb;\n# extern crate tokio;\n# extern crate env_logger;\nuse std::env;\nuse mongodb::{bson::doc, error::Result, Client}; #[tokio::main]\nasync fn main() -> Result<()> { // Register a global logger. env_logger::init(); // Create a MongoDB client. let mongodb_uri = env::var(\"MONGODB_URI\").expect(\"The MONGODB_URI environment variable was not set.\"); let client = Client::with_uri_str(mongodb_uri).await?; // Insert a document. let coll = client.database(\"test\").collection(\"test_coll\"); coll.insert_one(doc! { \"x\" : 1 }, None).await?; Ok(())\n} This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related messages with severity debug or higher: RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run The output will look something like the following: 2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId=\"63dd5e706af9908fc834fd94\" command=\"{\\\"insert\\\":\\\"test_coll\\\",\\\"ordered\\\":true,\\\"$db\\\":\\\"test\\\",\\\"lsid\\\":{\\\"id\\\":{\\\"$binary\\\":{\\\"base64\\\":\\\"y/v7PiLaRwOhT0RBFRDtNw==\\\",\\\"subType\\\":\\\"04\\\"}}},\\\"documents\\\":[{\\\"_id\\\":{\\\"$oid\\\":\\\"63dd5e706af9908fc834fd95\\\"},\\\"x\\\":1}]}\" databaseName=\"test\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017\n2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId=\"63dd5e706af9908fc834fd94\" reply=\"{\\\"n\\\":1,\\\"ok\\\":1.0}\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017 durationMS=0","breadcrumbs":"Tracing and Logging » Consuming Events with log","id":"25","title":"Consuming Events with log"},"26":{"body":"","breadcrumbs":"Web Framework Examples » Web Framework Examples","id":"26","title":"Web Framework Examples"},"27":{"body":"The driver can be used easily with the Actix web framework by storing a Client in Actix application data. A full example application for using MongoDB with Actix can be found here .","breadcrumbs":"Web Framework Examples » Actix","id":"27","title":"Actix"},"28":{"body":"The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the rocket_db_pools crate contains instructions for using MongoDB with your Rocket application.","breadcrumbs":"Web Framework Examples » Rocket","id":"28","title":"Rocket"},"29":{"body":"To enable support for in-use encryption ( client-side field level encryption and queryable encryption ), enable the \"in-use-encryption-unstable\" feature of the mongodb crate. As the name implies, the API for this feature is unstable, and may change in backwards-incompatible ways in minor releases.","breadcrumbs":"Encryption » Unstable API","id":"29","title":"Unstable API"},"3":{"body":"","breadcrumbs":"Installation and Features » Installation and Features","id":"3","title":"Installation and Features"},"30":{"body":"Starting with MongoDB 4.2, client-side field level encryption allows an application to encrypt specific data fields in addition to pre-existing MongoDB encryption features such as Encryption at Rest and TLS/SSL (Transport Encryption) . With field level encryption, applications can encrypt fields in documents prior to transmitting data over the wire to the server. Client-side field level encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data. See also the MongoDB documentation on Client Side Field Level Encryption .","breadcrumbs":"Encryption » Client-Side Field Level Encryption","id":"30","title":"Client-Side Field Level Encryption"},"31":{"body":"To get started using client-side field level encryption in your project, you will need to install libmongocrypt , which can be fetched from a variety of package repositories . If you install libmongocrypt in a location outside of the system library search path, the MONGOCRYPT_LIB_DIR environment variable will need to be set when compiling your project. Additionally, either crypt_shared or mongocryptd are required in order to use automatic client-side encryption.","breadcrumbs":"Encryption » Dependencies","id":"31","title":"Dependencies"},"32":{"body":"The Automatic Encryption Shared Library (crypt_shared) provides the same functionality as mongocryptd, but does not require you to spawn another process to perform automatic encryption. By default, the mongodb crate attempts to load crypt_shared from the system and if found uses it automatically. To load crypt_shared from another location, set the \"cryptSharedLibPath\" field in extra_options: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"cryptSharedLibPath\": \"/path/to/crypt/shared\", }) .build();\n#\n# Ok(())\n# } If the mongodb crate cannot load crypt_shared it will attempt to fallback to using mongocryptd by default. Include \"cryptSharedRequired\": true in the extra_options document to always use crypt_shared and fail if it could not be loaded. For detailed installation instructions see the MongoDB documentation on Automatic Encryption Shared Library .","breadcrumbs":"Encryption » crypt_shared","id":"32","title":"crypt_shared"},"33":{"body":"If using crypt_shared is not an option, the mongocryptd binary is required for automatic client-side encryption and is included as a component in the MongoDB Enterprise Server package . For detailed installation instructions see the MongoDB documentation on mongocryptd . mongocryptd performs the following: Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, mongocryptd returns an error. Uses the specified automatic encryption rules to mark fields in read and write operations for encryption. Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see Read/Write Support with Automatic Field Level Encryption . A Client configured with auto encryption will automatically spawn the mongocryptd process from the application's PATH. Applications can control the spawning behavior as part of the automatic encryption options: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"mongocryptdSpawnPath\": \"/path/to/mongocryptd\", \"mongocryptdSpawnArgs\": [\"--logpath=/path/to/mongocryptd.log\", \"--logappend\"], }) .build();\n#\n# Ok(())\n# } If your application wishes to manage the mongocryptd process manually, it is possible to disable spawning mongocryptd: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"mongocryptdBypassSpawn\": true, \"mongocryptdURI\": \"mongodb://localhost:27020\", }) .build();\n#\n# Ok(())\n# } mongocryptd is only responsible for supporting automatic client-side field level encryption and does not itself perform any encryption or decryption.","breadcrumbs":"Encryption » mongocryptd","id":"33","title":"mongocryptd"},"34":{"body":"Automatic client-side field level encryption is enabled by using the Client::encrypted_builder constructor method. The following examples show how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key. Note : Automatic client-side field level encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the server supports automatic decryption as well as explicit client-side encryption.","breadcrumbs":"Encryption » Automatic Client-Side Field Level Encryption","id":"34","title":"Automatic Client-Side Field Level Encryption"},"35":{"body":"The following example shows how to specify automatic encryption rules via the schema_map option. The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax . Supplying a schema_map provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted. JSON Schemas supplied in the schema_map only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error. # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // The MongoDB namespace (db.collection) used to store the // encrypted documents in this example. let encrypted_namespace = Namespace::new(\"test\", \"coll\"); // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"encryption\", \"__testKeyVault\"); // The MongoClient used to access the key vault (key_vault_namespace). let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key and json schema for the encryptedField. // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_1\".to_string()]) .run() .await?; let schema = doc! { \"properties\": { \"encryptedField\": { \"encrypt\": { \"keyId\": [data_key_id], \"bsonType\": \"string\", \"algorithm\": \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\", } } }, \"bsonType\": \"object\", }; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .schema_map([(encrypted_namespace.to_string(), schema)]) .build() .await?; let coll = client .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); // Clear old data. coll.drop(None).await?; coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) .await?; println!(\"Decrypted document: {:?}\", coll.find_one(None, None).await?); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); Ok(())\n}","breadcrumbs":"Encryption » Providing Local Automatic Encryption Rules","id":"35","title":"Providing Local Automatic Encryption Rules"},"36":{"body":"The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the \"encrypt\" JSON schema keyword. The following example shows how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key and create a collection with the Automatic Encryption JSON Schema Syntax : # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::{ClientOptions, CreateCollectionOptions, WriteConcern}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // The MongoDB namespace (db.collection) used to store the // encrypted documents in this example. let encrypted_namespace = Namespace::new(\"test\", \"coll\"); // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"encryption\", \"__testKeyVault\"); // The MongoClient used to access the key vault (key_vault_namespace). let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key and json schema for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_2\".to_string()]) .run() .await?; let schema = doc! { \"properties\": { \"encryptedField\": { \"encrypt\": { \"keyId\": [data_key_id], \"bsonType\": \"string\", \"algorithm\": \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\", } } }, \"bsonType\": \"object\", }; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .build() .await?; let db = client.database(&encrypted_namespace.db); let coll = db.collection::(&encrypted_namespace.coll); // Clear old data coll.drop(None).await?; // Create the collection with the encryption JSON Schema. db.create_collection( &encrypted_namespace.coll, CreateCollectionOptions::builder() .write_concern(WriteConcern::MAJORITY) .validator(doc! { \"$jsonSchema\": schema }) .build(), ).await?; coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) .await?; println!(\"Decrypted document: {:?}\", coll.find_one(None, None).await?); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); // This would return a Write error with the message \"Document failed validation\". // unencrypted_coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) // .await?; Ok(())\n}","breadcrumbs":"Encryption » Server-Side Field Level Encryption Enforcement","id":"36","title":"Server-Side Field Level Encryption Enforcement"},"37":{"body":"Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0. Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. You must have MongoDB 6.0 Enterprise to preview the feature. Automatic encryption in Queryable Encryption is configured with an encrypted_fields mapping, as demonstrated by the following example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# extern crate futures;\n# static URI: &str = \"mongodb://example.com\";\nuse futures::TryStreamExt;\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; let key1_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"firstName\".to_string()]) .run() .await?; let key2_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"lastName\".to_string()]) .run() .await?; let encrypted_fields_map = vec![( \"example.encryptedCollection\", doc! { \"escCollection\": \"encryptedCollection.esc\", \"eccCollection\": \"encryptedCollection.ecc\", \"ecocCollection\": \"encryptedCollection.ecoc\", \"fields\": [ { \"path\": \"firstName\", \"bsonType\": \"string\", \"keyId\": key1_id, \"queries\": [{\"queryType\": \"equality\"}], }, { \"path\": \"lastName\", \"bsonType\": \"string\", \"keyId\": key2_id, } ] }, )]; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .encrypted_fields_map(encrypted_fields_map) .build() .await?; let db = client.database(\"example\"); let coll = db.collection::(\"encryptedCollection\"); coll.drop(None).await?; db.create_collection(\"encryptedCollection\", None).await?; coll.insert_one( doc! { \"_id\": 1, \"firstName\": \"Jane\", \"lastName\": \"Doe\" }, None, ) .await?; let docs: Vec<_> = coll .find(doc! {\"firstName\": \"Jane\"}, None) .await? .try_collect() .await?; println!(\"{:?}\", docs); Ok(())\n}","breadcrumbs":"Encryption » Automatic Queryable Encryption","id":"37","title":"Automatic Queryable Encryption"},"38":{"body":"Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0. Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. Explicit encryption in Queryable Encryption is performed using the encrypt and decrypt methods. Automatic encryption (to allow the find_one to automatically decrypt) is configured using an encrypted_fields mapping, as demonstrated by the following example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{KmsProvider, Algorithm}, options::{ClientOptions, CreateCollectionOptions}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // Set up the key vault (key_vault_namespace) for this example. let client = Client::with_uri_str(URI).await?; let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let indexed_key_id = client_encryption .create_data_key(MasterKey::Local) .run() .await?; let unindexed_key_id = client_encryption .create_data_key(MasterKey::Local) .run() .await?; let encrypted_fields = doc! { \"escCollection\": \"enxcol_.default.esc\", \"eccCollection\": \"enxcol_.default.ecc\", \"ecocCollection\": \"enxcol_.default.ecoc\", \"fields\": [ { \"keyId\": indexed_key_id.clone(), \"path\": \"encryptedIndexed\", \"bsonType\": \"string\", \"queries\": { \"queryType\": \"equality\" } }, { \"keyId\": unindexed_key_id.clone(), \"path\": \"encryptedUnindexed\", \"bsonType\": \"string\", } ] }; // The MongoClient used to read/write application data. let encrypted_client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .bypass_query_analysis(true) .build() .await?; let db = encrypted_client.database(\"test\"); db.drop(None).await?; // Create the collection with encrypted fields. db.create_collection( \"coll\", CreateCollectionOptions::builder() .encrypted_fields(encrypted_fields) .build(), ) .await?; let coll = db.collection::(\"coll\"); // Create and encrypt an indexed and unindexed value. let val = \"encrypted indexed value\"; let unindexed_val = \"encrypted unindexed value\"; let insert_payload_indexed = client_encryption .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed) .contention_factor(1) .run() .await?; let insert_payload_unindexed = client_encryption .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed) .run() .await?; // Insert the payloads. coll.insert_one( doc! { \"encryptedIndexed\": insert_payload_indexed, \"encryptedUnindexed\": insert_payload_unindexed, }, None, ) .await?; // Encrypt our find payload using QueryType.EQUALITY. // The value of `data_key_id` must be the same as used to encrypt the values // above. let find_payload = client_encryption .encrypt(val, indexed_key_id, Algorithm::Indexed) .query_type(\"equality\") .contention_factor(1) .run() .await?; // Find the document we inserted using the encrypted payload. // The returned document is automatically decrypted. let doc = coll .find_one(doc! { \"encryptedIndexed\": find_payload }, None) .await?; println!(\"Returned document: {:?}\", doc); Ok(())\n}","breadcrumbs":"Encryption » Explicit Queryable Encryption","id":"38","title":"Explicit Queryable Encryption"},"39":{"body":"Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the ClientEncryption struct, for example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Bson, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{Algorithm, KmsProvider}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // The MongoClient used to read/write application data. let client = Client::with_uri_str(URI).await?; let coll = client.database(\"test\").collection::(\"coll\"); // Clear old data coll.drop(None).await?; // Set up the key vault (key_vault_namespace) for this example. let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_3\".to_string()]) .run() .await?; // Explicitly encrypt a field: let encrypted_field = client_encryption .encrypt( \"123456789\", data_key_id, Algorithm::AeadAes256CbcHmacSha512Deterministic, ) .run() .await?; coll.insert_one(doc! { \"encryptedField\": encrypted_field }, None) .await?; let mut doc = coll.find_one(None, None).await?.unwrap(); println!(\"Encrypted document: {:?}\", doc); // Explicitly decrypt the field: let field = match doc.get(\"encryptedField\") { Some(Bson::Binary(bin)) => bin, _ => panic!(\"invalid field\"), }; let decrypted: Bson = client_encryption .decrypt(field.as_raw_binary()) .await? .try_into()?; doc.insert(\"encryptedField\", decrypted); println!(\"Decrypted document: {:?}\", doc); Ok(())\n}","breadcrumbs":"Encryption » Explicit Encryption","id":"39","title":"Explicit Encryption"},"4":{"body":"The driver is available on crates.io . To use the driver in your application, simply add it to your project's Cargo.toml. [dependencies]\nmongodb = \"2.1.0\"","breadcrumbs":"Installation and Features » Importing","id":"4","title":"Importing"},"40":{"body":"Although automatic encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set bypass_auto_encryption to true in the EncryptedClientBuilder: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{Algorithm, KmsProvider}, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // `bypass_auto_encryption(true)` disables automatic encryption but keeps // the automatic _decryption_ behavior. bypass_auto_encryption will // also disable spawning mongocryptd. let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace.clone(), kms_providers.clone(), )? .bypass_auto_encryption(true) .build() .await?; let coll = client.database(\"test\").collection::(\"coll\"); // Clear old data coll.drop(None).await?; // Set up the key vault (key_vault_namespace) for this example. let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_4\".to_string()]) .run() .await?; // Explicitly encrypt a field: let encrypted_field = client_encryption .encrypt( \"123456789\", data_key_id, Algorithm::AeadAes256CbcHmacSha512Deterministic, ) .run() .await?; coll.insert_one(doc! { \"encryptedField\": encrypted_field }, None) .await?; // Automatically decrypts any encrypted fields. let doc = coll.find_one(None, None).await?.unwrap(); println!(\"Decrypted document: {:?}\", doc); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(\"test\") .collection::(\"coll\"); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); Ok(())\n}","breadcrumbs":"Encryption » Explicit Encryption with Automatic Decryption","id":"40","title":"Explicit Encryption with Automatic Decryption"},"5":{"body":"The driver supports both of the most popular async runtime crates, namely tokio and async-std . By default, the driver will use tokio , but you can explicitly choose a runtime by specifying one of \"tokio-runtime\" or \"async-std-runtime\" feature flags in your Cargo.toml. For example, to instruct the driver to work with async-std , add the following to your Cargo.toml: [dependencies.mongodb]\nversion = \"2.1.0\"\ndefault-features = false\nfeatures = [\"async-std-runtime\"]","breadcrumbs":"Installation and Features » Configuring the async runtime","id":"5","title":"Configuring the async runtime"},"6":{"body":"The driver also provides a blocking sync API. To enable this, add the \"sync\" or \"tokio-sync\" feature to your Cargo.toml: [dependencies.mongodb]\nversion = \"2.3.0\"\nfeatures = [\"tokio-sync\"] Using the \"sync\" feature also requires using default-features = false. Note: The sync-specific types can be imported from mongodb::sync (e.g. mongodb::sync::Client).","breadcrumbs":"Installation and Features » Enabling the sync API","id":"6","title":"Enabling the sync API"},"7":{"body":"Feature Description Extra dependencies Default tokio-runtime Enable support for the tokio async runtime tokio 1.0 with the full feature yes async-std-runtime Enable support for the async-std runtime async-std 1.0 no sync Expose the synchronous API (mongodb::sync), using an async-std backend. Cannot be used with the tokio-runtime feature flag. async-std 1.0 no tokio-sync Expose the synchronous API (mongodb::sync), using a tokio backend. Cannot be used with the async-std-runtime feature flag. tokio 1.0 with the full feature no aws-auth Enable support for the MONGODB-AWS authentication mechanism. reqwest 0.11 no bson-uuid-0_8 Enable support for v0.8 of the uuid crate in the public API of the re-exported bson crate. n/a no bson-uuid-1 Enable support for v1.x of the uuid crate in the public API of the re-exported bson crate. n/a no bson-chrono-0_4 Enable support for v0.4 of the chrono crate in the public API of the re-exported bson crate. n/a no bson-serde_with Enable support for the serde_with crate in the public API of the re-exported bson crate. serde_with 1.0 no zlib-compression Enable support for compressing messages with zlib flate2 1.0 no zstd-compression Enable support for compressing messages with zstd . This flag requires Rust version 1.54. zstd 0.9.0 no snappy-compression Enable support for compressing messages with snappy snap 1.0.5 no openssl-tls Switch TLS connection handling to use 'openssl' . openssl 0.10.38 no","breadcrumbs":"Installation and Features » All Feature Flags","id":"7","title":"All Feature Flags"},"8":{"body":"","breadcrumbs":"Connecting to the Database » Connecting to the Database","id":"8","title":"Connecting to the Database"},"9":{"body":"Connecting to a MongoDB database requires using a connection string , a URI of the form: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] At its simplest this can just specify the host and port, e.g. mongodb://mongodb0.example.com:27017 For the full range of options supported by the Rust driver, see the documentation for the ClientOptions::parse method. That method will return a ClientOptions struct, allowing for directly querying or setting any of the options supported by the Rust driver: # extern crate mongodb;\n# use mongodb::options::ClientOptions;\n# async fn run() -> mongodb::error::Result<()> {\nlet mut options = ClientOptions::parse(\"mongodb://mongodb0.example.com:27017\").await?;\noptions.app_name = Some(\"My App\".to_string());\n# Ok(())\n# }","breadcrumbs":"Connecting to the Database » Connection String","id":"9","title":"Connection String"}},"length":41,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{".":{".":{"5":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{".":{"3":{"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"14":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"3":{"df":0,"docs":{},"t":{"1":{"9":{":":{"2":{"0":{":":{"1":{"6":{".":{"0":{"9":{"1":{"8":{"2":{"2":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"7":{"0":{"0":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"_":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{".":{"5":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.0},"7":{"tf":2.449489742783178}}},"5":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"7":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0}}},"2":{".":{"1":{".":{"0":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{".":{"0":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"2":{"3":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"2":{"df":4,"docs":{"30":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{".":{"0":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"3":{"7":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"_":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"39":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":2.0}}}}}},"d":{"d":{"df":7,"docs":{"14":{"tf":1.0},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"23":{"tf":1.0},"30":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"21":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"_":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"2":{"5":{"6":{"_":{"c":{"b":{"c":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"_":{"5":{"1":{"2":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":0,"docs":{},"e":{"a":{"d":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"2":{"5":{"6":{"c":{"b":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"5":{"1":{"2":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":4,"docs":{"1":{"tf":1.0},"30":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"0":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178}}},"p":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":16,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"11":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0}}},"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":21,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":2.449489742783178},"7":{"tf":2.6457513110645907},"9":{"tf":1.0}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"14":{"tf":1.0},"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"17":{"tf":1.0},"18":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":1,"docs":{"33":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"14":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":2.0},"33":{"tf":2.8284271247461903},"34":{"tf":2.23606797749979},"35":{"tf":2.23606797749979},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"40":{"tf":2.8284271247461903}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"38":{"tf":3.0},"39":{"tf":2.0},"40":{"tf":2.23606797749979}}}}},"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"21":{"tf":1.0},"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":1,"docs":{"39":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":1,"docs":{"14":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"<":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"0":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"7":{"tf":2.8284271247461903}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":4,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":8,"docs":{"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.0},"21":{"tf":1.0}}}},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":6,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"16":{"tf":1.0},"21":{"tf":2.0},"29":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{">":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":24,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.8284271247461903},"11":{"tf":1.7320508075688772},"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"17":{"tf":3.1622776601683795},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":2.6457513110645907},"34":{"tf":2.23606797749979},"35":{"tf":2.23606797749979},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":2.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":3,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":9,"docs":{"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"13":{"tf":3.0},"14":{"tf":1.0},"19":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"14":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":2.0},"25":{"tf":2.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":2,"docs":{"34":{"tf":1.0},"39":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":2.449489742783178}}}}}}}},"n":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"10":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"0":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":26,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.449489742783178},"14":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"5":{"tf":1.0},"7":{"tf":2.8284271247461903},"9":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"0":{"tf":1.0},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"10":{"tf":2.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"31":{"tf":1.0},"32":{"tf":2.449489742783178},"33":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"14":{"tf":2.6457513110645907}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":7,"docs":{"12":{"tf":1.0},"13":{"tf":2.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"27":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":2.0},"36":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"b":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":4,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":3,"docs":{"13":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{".":{"a":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"w":{"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":5,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":2.0}}}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"32":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"21":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":7,"docs":{"0":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"22":{"tf":1.7320508075688772},"25":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"33":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"24":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"c":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":8,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178},"39":{"tf":2.23606797749979},"40":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"13":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"22":{"tf":1.0}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":17,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.7320508075688772},"10":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"35":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"6":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"6":{"tf":1.0},"9":{"tf":1.0}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"c":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"0":{"tf":1.0},"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"v":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}},"df":12,"docs":{"29":{"tf":2.0},"30":{"tf":3.3166247903554},"31":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"33":{"tf":3.3166247903554},"34":{"tf":2.449489742783178},"35":{"tf":3.4641016151377544},"36":{"tf":3.4641016151377544},"37":{"tf":3.0},"38":{"tf":4.47213595499958},"39":{"tf":2.6457513110645907},"40":{"tf":3.1622776601683795}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"s":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":2.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"v":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}}}},"x":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"s":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":2.0},"22":{"tf":2.23606797749979},"23":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"18":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"40":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":2.0}}}},"s":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":18,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.23606797749979},"14":{"tf":1.7320508075688772},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"r":{"a":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"18":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":3,"docs":{"35":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":13,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":2.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.0},"30":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":2.0},"7":{"tf":2.449489742783178}}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":12,"docs":{"29":{"tf":1.0},"30":{"tf":2.6457513110645907},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.7320508075688772},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"n":{"d":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"38":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":2.0}}},"t":{"df":0,"docs":{},"e":{"2":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":12,"docs":{"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"27":{"tf":1.0},"32":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":4,"docs":{"17":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"7":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"14":{"tf":1.0},"21":{"tf":1.0},"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":2.449489742783178},"14":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.0},"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":2.0},"16":{"tf":1.0},"7":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"a":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"11":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"d":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"17":{"tf":1.0},"24":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":1,"docs":{"29":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"14":{"tf":1.0},"4":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.7320508075688772}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.0}}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"28":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"n":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"'":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}},"r":{"df":1,"docs":{"14":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}},"j":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.0}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"40":{"tf":1.0}}}},"y":{"1":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"2":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"[":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"1":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":7,"docs":{"34":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"38":{"tf":2.6457513110645907},"39":{"tf":2.6457513110645907},"40":{"tf":2.6457513110645907}},"i":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":8,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"v":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"z":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"<":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":12,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"32":{"tf":2.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"35":{"tf":1.0}}},"t":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":3,"docs":{"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":2.23606797749979}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"23":{"tf":1.0},"25":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":9,"docs":{"10":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"17":{"tf":1.0}}}}},"df":0,"docs":{}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"19":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"0":{"tf":1.0},"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"39":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"25":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":2.0}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"{":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":5,"docs":{"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":3.0},"39":{"tf":1.0},"40":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"2":{"7":{"0":{"2":{"0":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"22":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.0},"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"6":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"{":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"d":{"df":0,"docs":{},"o":{"c":{"df":5,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"u":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"=":{"'":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}},"df":26,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979},"39":{"tf":2.0},"4":{"tf":1.0},"40":{"tf":2.23606797749979},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"35":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":8,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"9":{"tf":1.0}}}}},"n":{"/":{"a":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":5,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":10,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"21":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":9,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"34":{"tf":1.0},"6":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"24":{"tf":1.0}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"l":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"_":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"14":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"33":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"35":{"tf":1.0},"9":{"tf":1.7320508075688772}},"s":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"14":{"tf":1.0},"30":{"tf":1.0}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"!":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}},"t":{"df":1,"docs":{"33":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":1,"docs":{"30":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"31":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"38":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"22":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"!":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"22":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"4":{"tf":1.0}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":6,"docs":{"17":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"35":{"tf":1.4142135623730951},"39":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"29":{"tf":1.0},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"(":{"&":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0}}}}}}},"df":3,"docs":{"12":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"7":{"tf":2.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":1,"docs":{"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"21":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"=":{"\"":{"df":0,"docs":{},"{":{"\\":{"\"":{"df":0,"docs":{},"n":{"\\":{"\"":{":":{"1":{",":{"\\":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"\\":{"\"":{":":{"1":{".":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"17":{"tf":1.0}},"i":{"d":{"=":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"r":{"df":11,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"16":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"30":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"t":{".":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":2.23606797749979}}}},"n":{"df":10,"docs":{"10":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"18":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"7":{"tf":2.449489742783178}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"=":{"'":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"=":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"df":6,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"2":{"tf":1.0},"28":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":6,"docs":{"32":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"[":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"35":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.8284271247461903},"36":{"tf":2.8284271247461903}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.0},"23":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":7,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"9":{"tf":1.0}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":2,"docs":{"22":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{},"{":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"6":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"13":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"=":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"=":{"2":{"7":{"0":{"1":{"7":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"t":{"df":9,"docs":{"0":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.0},"36":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":10,"docs":{"0":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"n":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"7":{"tf":1.0}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}},"w":{"df":0,"docs":{},"n":{"df":5,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":3,"docs":{"30":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"i":{"c":{"df":7,"docs":{"18":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"0":{"tf":1.0},"18":{"tf":1.0},"5":{"tf":2.0},"7":{"tf":2.449489742783178}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"27":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":7,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":3,"docs":{"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"h":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":14,"docs":{"0":{"tf":1.7320508075688772},"2":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":3.1622776601683795},"9":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"0":{"tf":1.0},"6":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"18":{"tf":2.23606797749979}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"25":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.0}}}}}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}},"l":{"df":1,"docs":{"7":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"o":{"df":2,"docs":{"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"s":{"(":{"5":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":17,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}},"y":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"6":{"3":{"d":{"d":{"5":{"df":0,"docs":{},"e":{"7":{"0":{"6":{"a":{"df":0,"docs":{},"f":{"9":{"9":{"0":{"8":{"df":0,"docs":{},"f":{"c":{"8":{"3":{"4":{"df":0,"docs":{},"f":{"d":{"9":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.7320508075688772},"21":{"tf":2.8284271247461903},"23":{"tf":1.4142135623730951},"24":{"tf":2.8284271247461903},"25":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"24":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"e":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"13":{"tf":2.0},"14":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"3":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}},"l":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":6,"docs":{"20":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}}}},"p":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":30,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.7320508075688772},"10":{"tf":2.23606797749979},"11":{"tf":1.0},"13":{"tf":2.6457513110645907},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.23606797749979},"18":{"tf":3.3166247903554},"19":{"tf":1.7320508075688772},"24":{"tf":2.23606797749979},"25":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.6457513110645907},"36":{"tf":2.8284271247461903},"37":{"tf":1.7320508075688772},"38":{"tf":3.4641016151377544},"39":{"tf":2.8284271247461903},"4":{"tf":1.0},"40":{"tf":2.449489742783178},"5":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":2.23606797749979},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"v":{"0":{".":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"df":0,"docs":{},"x":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":3,"docs":{"21":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":2.23606797749979}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"c":{"!":{"[":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"0":{"df":0,"docs":{},"u":{"8":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"_":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"2":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}}}}}}},"i":{"a":{"df":4,"docs":{"0":{"tf":1.0},"14":{"tf":1.4142135623730951},"28":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":2,"docs":{"19":{"tf":1.0},"29":{"tf":1.0}}}},"df":0,"docs":{},"e":{"b":{"df":3,"docs":{"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"19":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}}}}},"x":{"df":3,"docs":{"1":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"7":{"tf":1.0}}}},"z":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}},"breadcrumbs":{"root":{"0":{".":{".":{"5":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{".":{"3":{"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"14":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"3":{"df":0,"docs":{},"t":{"1":{"9":{":":{"2":{"0":{":":{"1":{"6":{".":{"0":{"9":{"1":{"8":{"2":{"2":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"7":{"0":{"0":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"_":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{".":{"5":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.0},"7":{"tf":2.449489742783178}}},"5":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"7":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0}}},"2":{".":{"1":{".":{"0":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{".":{"0":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"2":{"3":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"2":{"df":4,"docs":{"30":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{".":{"0":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"3":{"7":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"_":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"39":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":2.23606797749979}}}}}},"d":{"d":{"df":7,"docs":{"14":{"tf":1.0},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"23":{"tf":1.0},"30":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"21":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"_":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"2":{"5":{"6":{"_":{"c":{"b":{"c":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"_":{"5":{"1":{"2":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":0,"docs":{},"e":{"a":{"d":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"2":{"5":{"6":{"c":{"b":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"5":{"1":{"2":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":4,"docs":{"1":{"tf":1.0},"30":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"0":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"6":{"tf":1.7320508075688772},"7":{"tf":2.449489742783178}}},"p":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":16,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"11":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0}}},"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":21,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":2.6457513110645907},"7":{"tf":2.6457513110645907},"9":{"tf":1.0}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"14":{"tf":1.0},"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"17":{"tf":1.0},"18":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":1,"docs":{"33":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"14":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":2.0},"33":{"tf":2.8284271247461903},"34":{"tf":2.449489742783178},"35":{"tf":2.449489742783178},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"40":{"tf":3.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"38":{"tf":3.0},"39":{"tf":2.0},"40":{"tf":2.23606797749979}}}}},"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"21":{"tf":1.0},"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.7320508075688772},"19":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":1,"docs":{"39":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":1,"docs":{"14":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"<":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"0":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"7":{"tf":2.8284271247461903}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":4,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":8,"docs":{"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.0},"21":{"tf":1.0}}}},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":6,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"16":{"tf":1.0},"21":{"tf":2.0},"29":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{">":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":24,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":3.0},"11":{"tf":2.0},"13":{"tf":2.0},"16":{"tf":2.0},"17":{"tf":3.1622776601683795},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":2.6457513110645907},"34":{"tf":2.449489742783178},"35":{"tf":2.23606797749979},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":2.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":3,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":9,"docs":{"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"13":{"tf":3.1622776601683795},"14":{"tf":1.0},"19":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"14":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":2.0},"25":{"tf":2.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":2,"docs":{"34":{"tf":1.0},"39":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":2.449489742783178}}}}}}}},"n":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.4142135623730951}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.7320508075688772},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"0":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":26,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.449489742783178},"14":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"5":{"tf":1.0},"7":{"tf":2.8284271247461903},"9":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"0":{"tf":1.0},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"10":{"tf":2.23606797749979},"11":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"31":{"tf":1.0},"32":{"tf":2.6457513110645907},"33":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"14":{"tf":2.8284271247461903}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":10,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.7320508075688772},"13":{"tf":2.449489742783178},"14":{"tf":1.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"27":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":2.0},"36":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"b":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":4,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":3,"docs":{"13":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{".":{"a":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"w":{"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":5,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":2.23606797749979}}}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"32":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"21":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":7,"docs":{"0":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"31":{"tf":1.4142135623730951},"4":{"tf":1.0},"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"22":{"tf":1.7320508075688772},"25":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"33":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"24":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"c":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":8,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178},"39":{"tf":2.23606797749979},"40":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"13":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"22":{"tf":1.0}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":17,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.7320508075688772},"10":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"35":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"6":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"6":{"tf":1.0},"9":{"tf":1.0}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"c":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"0":{"tf":1.0},"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"6":{"tf":1.7320508075688772},"7":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"v":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}},"df":12,"docs":{"29":{"tf":2.23606797749979},"30":{"tf":3.605551275463989},"31":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":3.4641016151377544},"34":{"tf":2.8284271247461903},"35":{"tf":3.7416573867739413},"36":{"tf":3.7416573867739413},"37":{"tf":3.3166247903554},"38":{"tf":4.69041575982343},"39":{"tf":3.0},"40":{"tf":3.4641016151377544}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"s":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":2.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"v":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}}}},"x":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"s":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":2.0},"22":{"tf":2.449489742783178},"23":{"tf":1.7320508075688772},"24":{"tf":2.0},"25":{"tf":1.7320508075688772}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":16,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"18":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":2.0}}}},"s":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":18,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.23606797749979},"14":{"tf":1.7320508075688772},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"r":{"a":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"18":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":3,"docs":{"35":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":14,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":2.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":2.0},"6":{"tf":2.23606797749979},"7":{"tf":2.8284271247461903}}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":12,"docs":{"29":{"tf":1.0},"30":{"tf":2.8284271247461903},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"n":{"d":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"38":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":2.23606797749979}}},"t":{"df":0,"docs":{},"e":{"2":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":12,"docs":{"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"27":{"tf":1.0},"32":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":4,"docs":{"17":{"tf":1.0},"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"7":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"14":{"tf":1.0},"21":{"tf":1.0},"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":2.449489742783178},"14":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":2.23606797749979},"16":{"tf":1.0},"7":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"a":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"11":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"d":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"17":{"tf":1.0},"24":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":1,"docs":{"29":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"14":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.7320508075688772}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":8,"docs":{"3":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"28":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"n":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"2":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"'":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}},"r":{"df":1,"docs":{"14":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}},"j":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.0}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"40":{"tf":1.0}}}},"y":{"1":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"2":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"[":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"1":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":7,"docs":{"34":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"38":{"tf":2.6457513110645907},"39":{"tf":2.6457513110645907},"40":{"tf":2.6457513110645907}},"i":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":8,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"v":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"z":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"<":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":12,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.449489742783178},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"32":{"tf":2.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"35":{"tf":1.4142135623730951}}},"t":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.0},"25":{"tf":2.6457513110645907}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"23":{"tf":1.0},"25":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":9,"docs":{"10":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"17":{"tf":1.0}}}}},"df":0,"docs":{}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"19":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"0":{"tf":1.0},"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"39":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"25":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":2.0}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"{":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":5,"docs":{"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":3.1622776601683795},"39":{"tf":1.0},"40":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"2":{"7":{"0":{"2":{"0":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"22":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.0},"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"6":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"{":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"d":{"df":0,"docs":{},"o":{"c":{"df":5,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"u":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"=":{"'":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}},"df":26,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979},"39":{"tf":2.0},"4":{"tf":1.0},"40":{"tf":2.23606797749979},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"35":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.7320508075688772}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":8,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"9":{"tf":1.0}}}}},"n":{"/":{"a":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":5,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":10,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"21":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":9,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"34":{"tf":1.0},"6":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"24":{"tf":1.0}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"l":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"_":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"14":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"33":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"35":{"tf":1.0},"9":{"tf":1.7320508075688772}},"s":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"14":{"tf":1.0},"30":{"tf":1.0}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"!":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}},"t":{"df":1,"docs":{"33":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":1,"docs":{"30":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"31":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.0},"19":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"38":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"22":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"!":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"22":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"4":{"tf":1.0}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":6,"docs":{"17":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"35":{"tf":1.7320508075688772},"39":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"29":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"(":{"&":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0}}}}}}},"df":5,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.0},"14":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"7":{"tf":2.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":1,"docs":{"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"21":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"=":{"\"":{"df":0,"docs":{},"{":{"\\":{"\"":{"df":0,"docs":{},"n":{"\\":{"\"":{":":{"1":{",":{"\\":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"\\":{"\"":{":":{"1":{".":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"17":{"tf":1.0}},"i":{"d":{"=":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"r":{"df":11,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"16":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"30":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":2.0}}}}}},"df":0,"docs":{}},"t":{".":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":2.449489742783178}}}},"n":{"df":10,"docs":{"10":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"18":{"tf":2.6457513110645907},"5":{"tf":2.6457513110645907},"7":{"tf":2.449489742783178}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"=":{"'":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"=":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"df":6,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"28":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":6,"docs":{"32":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"[":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"35":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.8284271247461903},"36":{"tf":2.8284271247461903}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.0},"23":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":7,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"9":{"tf":1.0}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":2,"docs":{"22":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{},"{":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"6":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"13":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"=":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"=":{"2":{"7":{"0":{"1":{"7":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"t":{"df":9,"docs":{"0":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.0},"36":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":10,"docs":{"0":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"n":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"7":{"tf":1.0}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}},"w":{"df":0,"docs":{},"n":{"df":5,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":3,"docs":{"30":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"i":{"c":{"df":7,"docs":{"18":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"0":{"tf":1.0},"18":{"tf":1.0},"5":{"tf":2.0},"7":{"tf":2.449489742783178}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"27":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":7,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":3,"docs":{"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"h":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":14,"docs":{"0":{"tf":1.7320508075688772},"2":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":3.1622776601683795},"9":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"0":{"tf":1.0},"6":{"tf":2.8284271247461903},"7":{"tf":1.4142135623730951}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"18":{"tf":2.23606797749979}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"25":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}},"l":{"df":1,"docs":{"7":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"o":{"df":2,"docs":{"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"s":{"(":{"5":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":17,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}},"y":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"6":{"3":{"d":{"d":{"5":{"df":0,"docs":{},"e":{"7":{"0":{"6":{"a":{"df":0,"docs":{},"f":{"9":{"9":{"0":{"8":{"df":0,"docs":{},"f":{"c":{"8":{"3":{"4":{"df":0,"docs":{},"f":{"d":{"9":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":6,"docs":{"20":{"tf":2.23606797749979},"21":{"tf":3.0},"22":{"tf":1.0},"23":{"tf":1.7320508075688772},"24":{"tf":3.1622776601683795},"25":{"tf":2.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"24":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"e":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"13":{"tf":2.0},"14":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"3":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}},"l":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":6,"docs":{"20":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":2.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}}}},"p":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":30,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.7320508075688772},"10":{"tf":2.23606797749979},"11":{"tf":1.0},"13":{"tf":2.6457513110645907},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.23606797749979},"18":{"tf":3.3166247903554},"19":{"tf":1.7320508075688772},"24":{"tf":2.23606797749979},"25":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.6457513110645907},"36":{"tf":2.8284271247461903},"37":{"tf":1.7320508075688772},"38":{"tf":3.4641016151377544},"39":{"tf":2.8284271247461903},"4":{"tf":1.0},"40":{"tf":2.449489742783178},"5":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":2.23606797749979},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"v":{"0":{".":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"df":0,"docs":{},"x":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":3,"docs":{"21":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":2.23606797749979}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"c":{"!":{"[":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"0":{"df":0,"docs":{},"u":{"8":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"_":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"2":{"tf":1.7320508075688772},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}}}}}}},"i":{"a":{"df":4,"docs":{"0":{"tf":1.0},"14":{"tf":1.4142135623730951},"28":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"y":{"df":2,"docs":{"19":{"tf":1.0},"29":{"tf":1.0}}}},"df":0,"docs":{},"e":{"b":{"df":3,"docs":{"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"19":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}}}}},"x":{"df":3,"docs":{"1":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"7":{"tf":1.0}}}},"z":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}},"title":{"root":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"16":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":8,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"f":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"3":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"26":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"3":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":2,"docs":{"20":{"tf":1.0},"25":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"15":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"35":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"18":{"tf":1.0},"5":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"20":{"tf":1.0},"24":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"b":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}); \ No newline at end of file diff --git a/docs/manual/searchindex.json b/docs/manual/searchindex.json deleted file mode 100644 index 443951a34..000000000 --- a/docs/manual/searchindex.json +++ /dev/null @@ -1 +0,0 @@ -{"doc_urls":["index.html#introduction","index.html#warning-about-timeouts--cancellation","index.html#minimum-supported-rust-version-msrv","installation_features.html#installation-and-features","installation_features.html#importing","installation_features.html#configuring-the-async-runtime","installation_features.html#enabling-the-sync-api","installation_features.html#all-feature-flags","connecting.html#connecting-to-the-database","connecting.html#connection-string","connecting.html#creating-a-client","connecting.html#client-performance","reading.html#reading-from-the-database","reading.html#database-and-collection-handles","reading.html#cursors","performance.html#performance","performance.html#client-best-practices","performance.html#lifetime","performance.html#runtime","performance.html#parallelism","tracing.html#tracing-and-logging","tracing.html#stability-guarantees","tracing.html#event-targets","tracing.html#consuming-events","tracing.html#consuming-events-with-tracing","tracing.html#consuming-events-with-log","web_framework_examples.html#web-framework-examples","web_framework_examples.html#actix","web_framework_examples.html#rocket","encryption.html#unstable-api","encryption.html#client-side-field-level-encryption","encryption.html#dependencies","encryption.html#crypt_shared","encryption.html#mongocryptd","encryption.html#automatic-client-side-field-level-encryption","encryption.html#providing-local-automatic-encryption-rules","encryption.html#server-side-field-level-encryption-enforcement","encryption.html#automatic-queryable-encryption","encryption.html#explicit-queryable-encryption","encryption.html#explicit-encryption","encryption.html#explicit-encryption-with-automatic-decryption"],"index":{"documentStore":{"docInfo":{"0":{"body":44,"breadcrumbs":2,"title":1},"1":{"body":106,"breadcrumbs":4,"title":3},"10":{"body":88,"breadcrumbs":4,"title":2},"11":{"body":24,"breadcrumbs":4,"title":2},"12":{"body":0,"breadcrumbs":4,"title":2},"13":{"body":172,"breadcrumbs":5,"title":3},"14":{"body":110,"breadcrumbs":3,"title":1},"15":{"body":0,"breadcrumbs":2,"title":1},"16":{"body":27,"breadcrumbs":4,"title":3},"17":{"body":92,"breadcrumbs":2,"title":1},"18":{"body":151,"breadcrumbs":2,"title":1},"19":{"body":50,"breadcrumbs":2,"title":1},"2":{"body":11,"breadcrumbs":6,"title":5},"20":{"body":14,"breadcrumbs":4,"title":2},"21":{"body":65,"breadcrumbs":4,"title":2},"22":{"body":36,"breadcrumbs":4,"title":2},"23":{"body":19,"breadcrumbs":4,"title":2},"24":{"body":146,"breadcrumbs":5,"title":3},"25":{"body":144,"breadcrumbs":5,"title":3},"26":{"body":0,"breadcrumbs":6,"title":3},"27":{"body":19,"breadcrumbs":4,"title":1},"28":{"body":19,"breadcrumbs":4,"title":1},"29":{"body":29,"breadcrumbs":3,"title":2},"3":{"body":0,"breadcrumbs":4,"title":2},"30":{"body":64,"breadcrumbs":6,"title":5},"31":{"body":40,"breadcrumbs":2,"title":1},"32":{"body":90,"breadcrumbs":2,"title":1},"33":{"body":177,"breadcrumbs":2,"title":1},"34":{"body":54,"breadcrumbs":7,"title":6},"35":{"body":233,"breadcrumbs":6,"title":5},"36":{"body":236,"breadcrumbs":7,"title":6},"37":{"body":194,"breadcrumbs":4,"title":3},"38":{"body":300,"breadcrumbs":4,"title":3},"39":{"body":179,"breadcrumbs":3,"title":2},"4":{"body":13,"breadcrumbs":3,"title":1},"40":{"body":194,"breadcrumbs":5,"title":4},"5":{"body":47,"breadcrumbs":5,"title":3},"6":{"body":34,"breadcrumbs":5,"title":3},"7":{"body":175,"breadcrumbs":4,"title":2},"8":{"body":0,"breadcrumbs":4,"title":2},"9":{"body":53,"breadcrumbs":4,"title":2}},"docs":{"0":{"body":"Crates.io docs.rs License This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the bson crate for BSON support. The driver contains a fully async API that supports either tokio (default) or async-std , depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag.","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how tokio::time::timeout works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more details). As such, it is highly recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like tokio::time::timeout with the driver, one option is to spawn tasks and time out on their JoinHandle futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout. e.g. # extern crate mongodb;\n# extern crate tokio;\n# use std::time::Duration;\n# use mongodb::{\n# Client,\n# bson::doc,\n# };\n#\n# async fn foo() -> std::result::Result<(), Box> {\n#\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nlet collection = client.database(\"foo\").collection(\"bar\");\nlet handle = tokio::task::spawn(async move { collection.insert_one(doc! { \"x\": 1 }, None).await\n}); tokio::time::timeout(Duration::from_secs(5), handle).await???;\n# Ok(())\n# }","breadcrumbs":"Introduction » Warning about timeouts / cancellation","id":"1","title":"Warning about timeouts / cancellation"},"10":{"body":"The Client struct is the main entry point for the driver. You can create one from a ClientOptions struct: # extern crate mongodb;\n# use mongodb::{Client, options::ClientOptions};\n# async fn run() -> mongodb::error::Result<()> {\n# let options = ClientOptions::parse(\"mongodb://mongodb0.example.com:27017\").await?;\nlet client = Client::with_options(options)?;\n# Ok(())\n# } As a convenience, if you don't need to modify the ClientOptions before creating the Client, you can directly create one from the connection string: # extern crate mongodb;\n# use mongodb::Client;\n# async fn run() -> mongodb::error::Result<()> {\nlet client = Client::with_uri_str(\"mongodb://mongodb0.example.com:27017\").await?;\n# Ok(())\n# } Client uses std::sync::Arc internally, so it can safely be shared across threads or async tasks. For example: # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\nlet client = Client::with_uri_str(\"mongodb://example.com\").await?; for i in 0..5 { let client_ref = client.clone(); task::spawn(async move { let collection = client_ref.database(\"items\").collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# }","breadcrumbs":"Connecting to the Database » Creating a Client","id":"10","title":"Creating a Client"},"11":{"body":"While cloning a Client is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single Client and persist it for the lifetime of your application. For more information, see the Performance chapter.","breadcrumbs":"Connecting to the Database » Client Performance","id":"11","title":"Client Performance"},"12":{"body":"","breadcrumbs":"Reading From the Database » Reading From the Database","id":"12","title":"Reading From the Database"},"13":{"body":"Once you have a Client, you can call Client::database to create a handle to a particular database on the server, and Database::collection to create a handle to a particular collection in that database. Database and Collection handles are lightweight - creating them requires no IO, cloneing them is cheap, and they can be safely shared across threads or async tasks. For example: # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nlet db = client.database(\"items\"); for i in 0..5 { let db_ref = db.clone(); task::spawn(async move { let collection = db_ref.collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# } A Collection can be parameterized with a type for the documents in the collection; this includes but is not limited to just Document. The various methods that accept instances of the documents (e.g. Collection::insert_one ) require that it implement the Serialize trait from the serde crate. Similarly, the methods that return instances (e.g. Collection::find_one ) require that it implement Deserialize. Document implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your Collections with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant. # extern crate mongodb;\n# extern crate tokio;\n# extern crate serde;\n# use mongodb::{\n# bson::doc,\n# error::Result,\n# };\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\n# use mongodb::Client;\n#\n# let client = Client::with_uri_str(\"mongodb://example.com\").await?;\nuse serde::{Deserialize, Serialize}; // Define a type that models our data.\n#[derive(Clone, Debug, Deserialize, Serialize)]\nstruct Item { id: u32,\n} // Parameterize our collection with the model.\nlet coll = client.database(\"items\").collection::(\"in_stock\"); for i in 0..5 { // Perform operations that work with directly our model. coll.insert_one(Item { id: i }, None).await;\n}\n#\n# Ok(())\n# } For more information, see the Serde Integration section.","breadcrumbs":"Reading From the Database » Database and Collection Handles","id":"13","title":"Database and Collection Handles"},"14":{"body":"Results from queries are generally returned via Cursor , a struct which streams the results back from the server as requested. The Cursor type implements the Stream trait from the futures crate, and in order to access its streaming functionality you need to import at least one of the StreamExt or TryStreamExt traits. # In Cargo.toml, add the following dependency.\nfutures = \"0.3\" # extern crate mongodb;\n# extern crate serde;\n# extern crate futures;\n# use serde::Deserialize;\n# #[derive(Deserialize)]\n# struct Book { title: String }\n# async fn foo() -> mongodb::error::Result<()> {\n# let typed_collection = mongodb::Client::with_uri_str(\"\").await?.database(\"\").collection::(\"\");\n// This trait is required to use `try_next()` on the cursor\nuse futures::stream::TryStreamExt;\nuse mongodb::{bson::doc, options::FindOptions}; // Query the books in the collection with a filter and an option.\nlet filter = doc! { \"author\": \"George Orwell\" };\nlet find_options = FindOptions::builder().sort(doc! { \"title\": 1 }).build();\nlet mut cursor = typed_collection.find(filter, find_options).await?; // Iterate over the results of the cursor.\nwhile let Some(book) = cursor.try_next().await? { println!(\"title: {}\", book.title);\n}\n# Ok(()) } If a Cursor is still open when it goes out of scope, it will automatically be closed via an asynchronous killCursors command executed from its Drop implementation.","breadcrumbs":"Reading From the Database » Cursors","id":"14","title":"Cursors"},"15":{"body":"","breadcrumbs":"Performance » Performance","id":"15","title":"Performance"},"16":{"body":"The Client handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a Client should be used for best performance.","breadcrumbs":"Performance » Client Best Practices","id":"16","title":"Client Best Practices"},"17":{"body":"A Client should be as long-lived as possible. Establishing a new Client is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because Client is implemented using an internal Arc , it can safely be shared across threads or tasks, and cloneing it to pass to new contexts is extremely cheap. # extern crate mongodb;\n# use mongodb::Client;\n# use std::error::Error;\n// This will be very slow because it's constructing and tearing down a `Client`\n// with every request.\nasync fn handle_request_bad() -> Result<(), Box> { let client = Client::with_uri_str(\"mongodb://example.com\").await?; // Do something with the client Ok(())\n} // This will be much faster.\nasync fn handle_request_good(client: &Client) -> Result<(), Box> { // Do something with the client Ok(())\n} This is especially noticeable when using a framework that provides connection pooling; because Client does its own pooling internally, attempting to maintain a pool of Clients will (somewhat counter-intuitively) result in worse performance than using a single one.","breadcrumbs":"Performance » Lifetime","id":"17","title":"Lifetime"},"18":{"body":"A Client is implicitly bound to the instance of the tokio or async-std runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the tokio::test or async_std::test helper macros create a new runtime for each test. # extern crate mongodb;\n# extern crate once_cell;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\nuse tokio::runtime::Runtime;\nuse once_cell::sync::Lazy; static CLIENT: Lazy = Lazy::new(|| { let rt = Runtime::new().unwrap(); rt.block_on(async { Client::with_uri_str(\"mongodb://example.com\").await.unwrap() })\n}); // This will inconsistently fail.\n#[tokio::test]\nasync fn test_list_dbs() -> Result<(), Box> { CLIENT.list_database_names(None, None).await?; Ok(())\n} To work around this issue, either create a new Client for every async test, or bundle the Runtime along with the client and don't use the test helper macros. # extern crate mongodb;\n# extern crate once_cell;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\nuse tokio::runtime::Runtime;\nuse once_cell::sync::Lazy; static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| { let rt = Runtime::new().unwrap(); let client = rt.block_on(async { Client::with_uri_str(\"mongodb://example.com\").await.unwrap() }); (client, rt)\n}); #[test]\nfn test_list_dbs() -> Result<(), Box> { let (client, rt) = &*CLIENT_RUNTIME; rt.block_on(async { client.list_database_names(None, None).await })?; Ok(())\n} or # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::Client;\n# use std::error::Error;\n#[tokio::test]\nasync fn test_list_dbs() -> Result<(), Box> { let client = Client::with_uri_str(\"mongodb://example.com\").await?; CLIENT.list_database_names(None, None).await?; Ok(())\n}","breadcrumbs":"Performance » Runtime","id":"18","title":"Runtime"},"19":{"body":"Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations. # extern crate mongodb;\n# extern crate tokio;\n# use mongodb::{bson::Document, Client, error::Result};\n# use tokio::task;\n#\n# async fn start_workers() -> Result<()> {\nlet client = Client::with_uri_str(\"mongodb://example.com\").await?; for i in 0..5 { let client_ref = client.clone(); task::spawn(async move { let collection = client_ref.database(\"items\").collection::(&format!(\"coll{}\", i)); // Do something with the collection });\n}\n#\n# Ok(())\n# }","breadcrumbs":"Performance » Parallelism","id":"19","title":"Parallelism"},"2":{"body":"The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, it will only happen in a minor or major version release.","breadcrumbs":"Introduction » Minimum supported Rust version (MSRV)","id":"2","title":"Minimum supported Rust version (MSRV)"},"20":{"body":"The driver utilizes the tracing crate to emit events at points of interest. To enable this, you must turn on the tracing-unstable feature flag.","breadcrumbs":"Tracing and Logging » Tracing and Logging","id":"20","title":"Tracing and Logging"},"21":{"body":"This functionality is considered unstable as the tracing crate has not reached 1.0 yet. Future minor versions of the driver may upgrade the tracing dependency to a new version which is not backwards-compatible with Subscribers that depend on older versions of tracing. Additionally, future minor releases may make changes such as: add or remove tracing events add or remove values attached to tracing events change the types and/or names of values attached to tracing events add or remove driver-defined tracing spans change the severity level of tracing events Such changes will be called out in release notes.","breadcrumbs":"Tracing and Logging » Stability Guarantees","id":"21","title":"Stability Guarantees"},"22":{"body":"Currently, events are emitted under the following targets: Target Description mongodb::command Events describing commands sent to the database and their success or failure. mongodb::server_selection Events describing the driver's process of selecting a server in the database deployment to send a command to. mongodb::connection Events describing the behavior of driver connection pools and the connections they contain.","breadcrumbs":"Tracing and Logging » Event Targets","id":"22","title":"Event Targets"},"23":{"body":"To consume events in your application, in addition to enabling the tracing-unstable feature flag, you must either register a tracing-compatible subscriber or a log-compatible logger, as detailed in the following sections.","breadcrumbs":"Tracing and Logging » Consuming Events","id":"23","title":"Consuming Events"},"24":{"body":"To consume events with tracing, you will need to register a type implementing the tracing::Subscriber trait in your application, as discussed in the tracing docs . Here's a minimal example of a program using the driver which uses a tracing subscriber. First, add the following to Cargo.toml: tracing = \"LATEST_VERSION_HERE\"\ntracing-subscriber = \"LATEST_VERSION_HERE\"\nmongodb = { version = \"LATEST_VERSION_HERE\", features = [\"tracing-unstable\"] } And then in main.rs: # extern crate mongodb;\n# extern crate tokio;\n# extern crate tracing_subscriber;\n# use std::env;\nuse mongodb::{bson::doc, error::Result, Client}; #[tokio::main]\nasync fn main() -> Result<()> { // Register a global tracing subscriber which will obey the RUST_LOG environment variable // config. tracing_subscriber::fmt::init(); // Create a MongoDB client. let mongodb_uri = env::var(\"MONGODB_URI\").expect(\"The MONGODB_URI environment variable was not set.\"); let client = Client::with_uri_str(mongodb_uri).await?; // Insert a document. let coll = client.database(\"test\").collection(\"test_coll\"); coll.insert_one(doc! { \"x\" : 1 }, None).await?; Ok(())\n} This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related events with severity debug or higher: RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run The output will look something like the following: 2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId=\"63dd5e706af9908fc834fd94\" command=\"{\\\"insert\\\":\\\"test_coll\\\",\\\"ordered\\\":true,\\\"$db\\\":\\\"test\\\",\\\"lsid\\\":{\\\"id\\\":{\\\"$binary\\\":{\\\"base64\\\":\\\"y/v7PiLaRwOhT0RBFRDtNw==\\\",\\\"subType\\\":\\\"04\\\"}}},\\\"documents\\\":[{\\\"_id\\\":{\\\"$oid\\\":\\\"63dd5e706af9908fc834fd95\\\"},\\\"x\\\":1}]}\" databaseName=\"test\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017\n2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId=\"63dd5e706af9908fc834fd94\" reply=\"{\\\"n\\\":1,\\\"ok\\\":1.0}\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017 durationMS=0","breadcrumbs":"Tracing and Logging » Consuming Events with tracing","id":"24","title":"Consuming Events with tracing"},"25":{"body":"Alternatively, to consume events with log, you will need to add tracing as a dependency of your application, and enable either its log or log-always feature. Those features are described in detail here . Here's a minimal example of a program using the driver which uses env_logger . In Cargo.toml: tracing = { version = \"LATEST_VERSION_HERE\", features = [\"log\"] }\nmongodb = { version = \"LATEST_VERSION_HERE\", features = [\"tracing-unstable\"] }\nenv_logger = \"LATEST_VERSION_HERE\" And in main.rs: # extern crate mongodb;\n# extern crate tokio;\n# extern crate env_logger;\nuse std::env;\nuse mongodb::{bson::doc, error::Result, Client}; #[tokio::main]\nasync fn main() -> Result<()> { // Register a global logger. env_logger::init(); // Create a MongoDB client. let mongodb_uri = env::var(\"MONGODB_URI\").expect(\"The MONGODB_URI environment variable was not set.\"); let client = Client::with_uri_str(mongodb_uri).await?; // Insert a document. let coll = client.database(\"test\").collection(\"test_coll\"); coll.insert_one(doc! { \"x\" : 1 }, None).await?; Ok(())\n} This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related messages with severity debug or higher: RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run The output will look something like the following: 2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId=\"63dd5e706af9908fc834fd94\" command=\"{\\\"insert\\\":\\\"test_coll\\\",\\\"ordered\\\":true,\\\"$db\\\":\\\"test\\\",\\\"lsid\\\":{\\\"id\\\":{\\\"$binary\\\":{\\\"base64\\\":\\\"y/v7PiLaRwOhT0RBFRDtNw==\\\",\\\"subType\\\":\\\"04\\\"}}},\\\"documents\\\":[{\\\"_id\\\":{\\\"$oid\\\":\\\"63dd5e706af9908fc834fd95\\\"},\\\"x\\\":1}]}\" databaseName=\"test\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017\n2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId=\"63dd5e706af9908fc834fd94\" reply=\"{\\\"n\\\":1,\\\"ok\\\":1.0}\" commandName=\"insert\" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost=\"localhost\" serverPort=27017 durationMS=0","breadcrumbs":"Tracing and Logging » Consuming Events with log","id":"25","title":"Consuming Events with log"},"26":{"body":"","breadcrumbs":"Web Framework Examples » Web Framework Examples","id":"26","title":"Web Framework Examples"},"27":{"body":"The driver can be used easily with the Actix web framework by storing a Client in Actix application data. A full example application for using MongoDB with Actix can be found here .","breadcrumbs":"Web Framework Examples » Actix","id":"27","title":"Actix"},"28":{"body":"The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the rocket_db_pools crate contains instructions for using MongoDB with your Rocket application.","breadcrumbs":"Web Framework Examples » Rocket","id":"28","title":"Rocket"},"29":{"body":"To enable support for in-use encryption ( client-side field level encryption and queryable encryption ), enable the \"in-use-encryption-unstable\" feature of the mongodb crate. As the name implies, the API for this feature is unstable, and may change in backwards-incompatible ways in minor releases.","breadcrumbs":"Encryption » Unstable API","id":"29","title":"Unstable API"},"3":{"body":"","breadcrumbs":"Installation and Features » Installation and Features","id":"3","title":"Installation and Features"},"30":{"body":"Starting with MongoDB 4.2, client-side field level encryption allows an application to encrypt specific data fields in addition to pre-existing MongoDB encryption features such as Encryption at Rest and TLS/SSL (Transport Encryption) . With field level encryption, applications can encrypt fields in documents prior to transmitting data over the wire to the server. Client-side field level encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data. See also the MongoDB documentation on Client Side Field Level Encryption .","breadcrumbs":"Encryption » Client-Side Field Level Encryption","id":"30","title":"Client-Side Field Level Encryption"},"31":{"body":"To get started using client-side field level encryption in your project, you will need to install libmongocrypt , which can be fetched from a variety of package repositories . If you install libmongocrypt in a location outside of the system library search path, the MONGOCRYPT_LIB_DIR environment variable will need to be set when compiling your project. Additionally, either crypt_shared or mongocryptd are required in order to use automatic client-side encryption.","breadcrumbs":"Encryption » Dependencies","id":"31","title":"Dependencies"},"32":{"body":"The Automatic Encryption Shared Library (crypt_shared) provides the same functionality as mongocryptd, but does not require you to spawn another process to perform automatic encryption. By default, the mongodb crate attempts to load crypt_shared from the system and if found uses it automatically. To load crypt_shared from another location, set the \"cryptSharedLibPath\" field in extra_options: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"cryptSharedLibPath\": \"/path/to/crypt/shared\", }) .build();\n#\n# Ok(())\n# } If the mongodb crate cannot load crypt_shared it will attempt to fallback to using mongocryptd by default. Include \"cryptSharedRequired\": true in the extra_options document to always use crypt_shared and fail if it could not be loaded. For detailed installation instructions see the MongoDB documentation on Automatic Encryption Shared Library .","breadcrumbs":"Encryption » crypt_shared","id":"32","title":"crypt_shared"},"33":{"body":"If using crypt_shared is not an option, the mongocryptd binary is required for automatic client-side encryption and is included as a component in the MongoDB Enterprise Server package . For detailed installation instructions see the MongoDB documentation on mongocryptd . mongocryptd performs the following: Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, mongocryptd returns an error. Uses the specified automatic encryption rules to mark fields in read and write operations for encryption. Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see Read/Write Support with Automatic Field Level Encryption . A Client configured with auto encryption will automatically spawn the mongocryptd process from the application's PATH. Applications can control the spawning behavior as part of the automatic encryption options: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"mongocryptdSpawnPath\": \"/path/to/mongocryptd\", \"mongocryptdSpawnArgs\": [\"--logpath=/path/to/mongocryptd.log\", \"--logappend\"], }) .build();\n#\n# Ok(())\n# } If your application wishes to manage the mongocryptd process manually, it is possible to disable spawning mongocryptd: # extern crate mongodb;\n# use mongodb::{bson::doc, Client, error::Result};\n#\n# async fn func() -> Result<()> {\n# let options = todo!();\n# let kv_namespace = todo!();\n# let kms_providers: Vec<_> = todo!();\nlet client = Client::encrypted_builder(options, kv_namespace, kms_providers)? .extra_options(doc! { \"mongocryptdBypassSpawn\": true, \"mongocryptdURI\": \"mongodb://localhost:27020\", }) .build();\n#\n# Ok(())\n# } mongocryptd is only responsible for supporting automatic client-side field level encryption and does not itself perform any encryption or decryption.","breadcrumbs":"Encryption » mongocryptd","id":"33","title":"mongocryptd"},"34":{"body":"Automatic client-side field level encryption is enabled by using the Client::encrypted_builder constructor method. The following examples show how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key. Note : Automatic client-side field level encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the server supports automatic decryption as well as explicit client-side encryption.","breadcrumbs":"Encryption » Automatic Client-Side Field Level Encryption","id":"34","title":"Automatic Client-Side Field Level Encryption"},"35":{"body":"The following example shows how to specify automatic encryption rules via the schema_map option. The automatic encryption rules are expressed using a strict subset of the JSON Schema syntax . Supplying a schema_map provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted. JSON Schemas supplied in the schema_map only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error. # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // The MongoDB namespace (db.collection) used to store the // encrypted documents in this example. let encrypted_namespace = Namespace::new(\"test\", \"coll\"); // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"encryption\", \"__testKeyVault\"); // The MongoClient used to access the key vault (key_vault_namespace). let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key and json schema for the encryptedField. // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_1\".to_string()]) .run() .await?; let schema = doc! { \"properties\": { \"encryptedField\": { \"encrypt\": { \"keyId\": [data_key_id], \"bsonType\": \"string\", \"algorithm\": \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\", } } }, \"bsonType\": \"object\", }; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .schema_map([(encrypted_namespace.to_string(), schema)]) .build() .await?; let coll = client .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); // Clear old data. coll.drop(None).await?; coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) .await?; println!(\"Decrypted document: {:?}\", coll.find_one(None, None).await?); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); Ok(())\n}","breadcrumbs":"Encryption » Providing Local Automatic Encryption Rules","id":"35","title":"Providing Local Automatic Encryption Rules"},"36":{"body":"The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the \"encrypt\" JSON schema keyword. The following example shows how to setup automatic client-side field level encryption using ClientEncryption to create a new encryption data key and create a collection with the Automatic Encryption JSON Schema Syntax : # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::{ClientOptions, CreateCollectionOptions, WriteConcern}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // The MongoDB namespace (db.collection) used to store the // encrypted documents in this example. let encrypted_namespace = Namespace::new(\"test\", \"coll\"); // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"encryption\", \"__testKeyVault\"); // The MongoClient used to access the key vault (key_vault_namespace). let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key and json schema for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_2\".to_string()]) .run() .await?; let schema = doc! { \"properties\": { \"encryptedField\": { \"encrypt\": { \"keyId\": [data_key_id], \"bsonType\": \"string\", \"algorithm\": \"AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic\", } } }, \"bsonType\": \"object\", }; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .build() .await?; let db = client.database(&encrypted_namespace.db); let coll = db.collection::(&encrypted_namespace.coll); // Clear old data coll.drop(None).await?; // Create the collection with the encryption JSON Schema. db.create_collection( &encrypted_namespace.coll, CreateCollectionOptions::builder() .write_concern(WriteConcern::MAJORITY) .validator(doc! { \"$jsonSchema\": schema }) .build(), ).await?; coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) .await?; println!(\"Decrypted document: {:?}\", coll.find_one(None, None).await?); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(&encrypted_namespace.db) .collection::(&encrypted_namespace.coll); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); // This would return a Write error with the message \"Document failed validation\". // unencrypted_coll.insert_one(doc! { \"encryptedField\": \"123456789\" }, None) // .await?; Ok(())\n}","breadcrumbs":"Encryption » Server-Side Field Level Encryption Enforcement","id":"36","title":"Server-Side Field Level Encryption Enforcement"},"37":{"body":"Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0. Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. You must have MongoDB 6.0 Enterprise to preview the feature. Automatic encryption in Queryable Encryption is configured with an encrypted_fields mapping, as demonstrated by the following example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# extern crate futures;\n# static URI: &str = \"mongodb://example.com\";\nuse futures::TryStreamExt;\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::KmsProvider, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); let key_vault_client = Client::with_uri_str(URI).await?; let key_vault = key_vault_client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( key_vault_client, key_vault_namespace.clone(), kms_providers.clone(), )?; let key1_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"firstName\".to_string()]) .run() .await?; let key2_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"lastName\".to_string()]) .run() .await?; let encrypted_fields_map = vec![( \"example.encryptedCollection\", doc! { \"escCollection\": \"encryptedCollection.esc\", \"eccCollection\": \"encryptedCollection.ecc\", \"ecocCollection\": \"encryptedCollection.ecoc\", \"fields\": [ { \"path\": \"firstName\", \"bsonType\": \"string\", \"keyId\": key1_id, \"queries\": [{\"queryType\": \"equality\"}], }, { \"path\": \"lastName\", \"bsonType\": \"string\", \"keyId\": key2_id, } ] }, )]; let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .encrypted_fields_map(encrypted_fields_map) .build() .await?; let db = client.database(\"example\"); let coll = db.collection::(\"encryptedCollection\"); coll.drop(None).await?; db.create_collection(\"encryptedCollection\", None).await?; coll.insert_one( doc! { \"_id\": 1, \"firstName\": \"Jane\", \"lastName\": \"Doe\" }, None, ) .await?; let docs: Vec<_> = coll .find(doc! {\"firstName\": \"Jane\"}, None) .await? .try_collect() .await?; println!(\"{:?}\", docs); Ok(())\n}","breadcrumbs":"Encryption » Automatic Queryable Encryption","id":"37","title":"Automatic Queryable Encryption"},"38":{"body":"Verison 2.4.0 of the mongodb crate brings support for Queryable Encryption with MongoDB >=6.0. Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. Explicit encryption in Queryable Encryption is performed using the encrypt and decrypt methods. Automatic encryption (to allow the find_one to automatically decrypt) is configured using an encrypted_fields mapping, as demonstrated by the following example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{KmsProvider, Algorithm}, options::{ClientOptions, CreateCollectionOptions}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // Set up the key vault (key_vault_namespace) for this example. let client = Client::with_uri_str(URI).await?; let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let indexed_key_id = client_encryption .create_data_key(MasterKey::Local) .run() .await?; let unindexed_key_id = client_encryption .create_data_key(MasterKey::Local) .run() .await?; let encrypted_fields = doc! { \"escCollection\": \"enxcol_.default.esc\", \"eccCollection\": \"enxcol_.default.ecc\", \"ecocCollection\": \"enxcol_.default.ecoc\", \"fields\": [ { \"keyId\": indexed_key_id.clone(), \"path\": \"encryptedIndexed\", \"bsonType\": \"string\", \"queries\": { \"queryType\": \"equality\" } }, { \"keyId\": unindexed_key_id.clone(), \"path\": \"encryptedUnindexed\", \"bsonType\": \"string\", } ] }; // The MongoClient used to read/write application data. let encrypted_client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace, kms_providers, )? .bypass_query_analysis(true) .build() .await?; let db = encrypted_client.database(\"test\"); db.drop(None).await?; // Create the collection with encrypted fields. db.create_collection( \"coll\", CreateCollectionOptions::builder() .encrypted_fields(encrypted_fields) .build(), ) .await?; let coll = db.collection::(\"coll\"); // Create and encrypt an indexed and unindexed value. let val = \"encrypted indexed value\"; let unindexed_val = \"encrypted unindexed value\"; let insert_payload_indexed = client_encryption .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed) .contention_factor(1) .run() .await?; let insert_payload_unindexed = client_encryption .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed) .run() .await?; // Insert the payloads. coll.insert_one( doc! { \"encryptedIndexed\": insert_payload_indexed, \"encryptedUnindexed\": insert_payload_unindexed, }, None, ) .await?; // Encrypt our find payload using QueryType.EQUALITY. // The value of `data_key_id` must be the same as used to encrypt the values // above. let find_payload = client_encryption .encrypt(val, indexed_key_id, Algorithm::Indexed) .query_type(\"equality\") .contention_factor(1) .run() .await?; // Find the document we inserted using the encrypted payload. // The returned document is automatically decrypted. let doc = coll .find_one(doc! { \"encryptedIndexed\": find_payload }, None) .await?; println!(\"Returned document: {:?}\", doc); Ok(())\n}","breadcrumbs":"Encryption » Explicit Queryable Encryption","id":"38","title":"Explicit Queryable Encryption"},"39":{"body":"Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the ClientEncryption struct, for example: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Bson, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{Algorithm, KmsProvider}, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // The MongoClient used to read/write application data. let client = Client::with_uri_str(URI).await?; let coll = client.database(\"test\").collection::(\"coll\"); // Clear old data coll.drop(None).await?; // Set up the key vault (key_vault_namespace) for this example. let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_3\".to_string()]) .run() .await?; // Explicitly encrypt a field: let encrypted_field = client_encryption .encrypt( \"123456789\", data_key_id, Algorithm::AeadAes256CbcHmacSha512Deterministic, ) .run() .await?; coll.insert_one(doc! { \"encryptedField\": encrypted_field }, None) .await?; let mut doc = coll.find_one(None, None).await?.unwrap(); println!(\"Encrypted document: {:?}\", doc); // Explicitly decrypt the field: let field = match doc.get(\"encryptedField\") { Some(Bson::Binary(bin)) => bin, _ => panic!(\"invalid field\"), }; let decrypted: Bson = client_encryption .decrypt(field.as_raw_binary()) .await? .try_into()?; doc.insert(\"encryptedField\", decrypted); println!(\"Decrypted document: {:?}\", doc); Ok(())\n}","breadcrumbs":"Encryption » Explicit Encryption","id":"39","title":"Explicit Encryption"},"4":{"body":"The driver is available on crates.io . To use the driver in your application, simply add it to your project's Cargo.toml. [dependencies]\nmongodb = \"2.1.0\"","breadcrumbs":"Installation and Features » Importing","id":"4","title":"Importing"},"40":{"body":"Although automatic encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set bypass_auto_encryption to true in the EncryptedClientBuilder: # extern crate mongodb;\n# extern crate tokio;\n# extern crate rand;\n# static URI: &str = \"mongodb://example.com\";\nuse mongodb::{ bson::{self, doc, Document}, client_encryption::{ClientEncryption, MasterKey}, error::Result, mongocrypt::ctx::{Algorithm, KmsProvider}, options::ClientOptions, Client, Namespace,\n};\nuse rand::Rng; #[tokio::main]\nasync fn main() -> Result<()> { // This must be the same master key that was used to create // the encryption key. let mut key_bytes = vec![0u8; 96]; rand::thread_rng().fill(&mut key_bytes[..]); let local_master_key = bson::Binary { subtype: bson::spec::BinarySubtype::Generic, bytes: key_bytes, }; let kms_providers = vec![(KmsProvider::Local, doc! { \"key\": local_master_key }, None)]; // The MongoDB namespace (db.collection) used to store // the encryption data keys. let key_vault_namespace = Namespace::new(\"keyvault\", \"datakeys\"); // `bypass_auto_encryption(true)` disables automatic encryption but keeps // the automatic _decryption_ behavior. bypass_auto_encryption will // also disable spawning mongocryptd. let client = Client::encrypted_builder( ClientOptions::parse(URI).await?, key_vault_namespace.clone(), kms_providers.clone(), )? .bypass_auto_encryption(true) .build() .await?; let coll = client.database(\"test\").collection::(\"coll\"); // Clear old data coll.drop(None).await?; // Set up the key vault (key_vault_namespace) for this example. let key_vault = client .database(&key_vault_namespace.db) .collection::(&key_vault_namespace.coll); key_vault.drop(None).await?; let client_encryption = ClientEncryption::new( // The MongoClient to use for reading/writing to the key vault. // This can be the same MongoClient used by the main application. client, key_vault_namespace.clone(), kms_providers.clone(), )?; // Create a new data key for the encryptedField. let data_key_id = client_encryption .create_data_key(MasterKey::Local) .key_alt_names([\"encryption_example_4\".to_string()]) .run() .await?; // Explicitly encrypt a field: let encrypted_field = client_encryption .encrypt( \"123456789\", data_key_id, Algorithm::AeadAes256CbcHmacSha512Deterministic, ) .run() .await?; coll.insert_one(doc! { \"encryptedField\": encrypted_field }, None) .await?; // Automatically decrypts any encrypted fields. let doc = coll.find_one(None, None).await?.unwrap(); println!(\"Decrypted document: {:?}\", doc); let unencrypted_coll = Client::with_uri_str(URI) .await? .database(\"test\") .collection::(\"coll\"); println!( \"Encrypted document: {:?}\", unencrypted_coll.find_one(None, None).await? ); Ok(())\n}","breadcrumbs":"Encryption » Explicit Encryption with Automatic Decryption","id":"40","title":"Explicit Encryption with Automatic Decryption"},"5":{"body":"The driver supports both of the most popular async runtime crates, namely tokio and async-std . By default, the driver will use tokio , but you can explicitly choose a runtime by specifying one of \"tokio-runtime\" or \"async-std-runtime\" feature flags in your Cargo.toml. For example, to instruct the driver to work with async-std , add the following to your Cargo.toml: [dependencies.mongodb]\nversion = \"2.1.0\"\ndefault-features = false\nfeatures = [\"async-std-runtime\"]","breadcrumbs":"Installation and Features » Configuring the async runtime","id":"5","title":"Configuring the async runtime"},"6":{"body":"The driver also provides a blocking sync API. To enable this, add the \"sync\" or \"tokio-sync\" feature to your Cargo.toml: [dependencies.mongodb]\nversion = \"2.3.0\"\nfeatures = [\"tokio-sync\"] Using the \"sync\" feature also requires using default-features = false. Note: The sync-specific types can be imported from mongodb::sync (e.g. mongodb::sync::Client).","breadcrumbs":"Installation and Features » Enabling the sync API","id":"6","title":"Enabling the sync API"},"7":{"body":"Feature Description Extra dependencies Default tokio-runtime Enable support for the tokio async runtime tokio 1.0 with the full feature yes async-std-runtime Enable support for the async-std runtime async-std 1.0 no sync Expose the synchronous API (mongodb::sync), using an async-std backend. Cannot be used with the tokio-runtime feature flag. async-std 1.0 no tokio-sync Expose the synchronous API (mongodb::sync), using a tokio backend. Cannot be used with the async-std-runtime feature flag. tokio 1.0 with the full feature no aws-auth Enable support for the MONGODB-AWS authentication mechanism. reqwest 0.11 no bson-uuid-0_8 Enable support for v0.8 of the uuid crate in the public API of the re-exported bson crate. n/a no bson-uuid-1 Enable support for v1.x of the uuid crate in the public API of the re-exported bson crate. n/a no bson-chrono-0_4 Enable support for v0.4 of the chrono crate in the public API of the re-exported bson crate. n/a no bson-serde_with Enable support for the serde_with crate in the public API of the re-exported bson crate. serde_with 1.0 no zlib-compression Enable support for compressing messages with zlib flate2 1.0 no zstd-compression Enable support for compressing messages with zstd . This flag requires Rust version 1.54. zstd 0.9.0 no snappy-compression Enable support for compressing messages with snappy snap 1.0.5 no openssl-tls Switch TLS connection handling to use 'openssl' . openssl 0.10.38 no","breadcrumbs":"Installation and Features » All Feature Flags","id":"7","title":"All Feature Flags"},"8":{"body":"","breadcrumbs":"Connecting to the Database » Connecting to the Database","id":"8","title":"Connecting to the Database"},"9":{"body":"Connecting to a MongoDB database requires using a connection string , a URI of the form: mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] At its simplest this can just specify the host and port, e.g. mongodb://mongodb0.example.com:27017 For the full range of options supported by the Rust driver, see the documentation for the ClientOptions::parse method. That method will return a ClientOptions struct, allowing for directly querying or setting any of the options supported by the Rust driver: # extern crate mongodb;\n# use mongodb::options::ClientOptions;\n# async fn run() -> mongodb::error::Result<()> {\nlet mut options = ClientOptions::parse(\"mongodb://mongodb0.example.com:27017\").await?;\noptions.app_name = Some(\"My App\".to_string());\n# Ok(())\n# }","breadcrumbs":"Connecting to the Database » Connection String","id":"9","title":"Connection String"}},"length":41,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{".":{".":{"5":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{".":{"3":{"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"14":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"3":{"df":0,"docs":{},"t":{"1":{"9":{":":{"2":{"0":{":":{"1":{"6":{".":{"0":{"9":{"1":{"8":{"2":{"2":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"7":{"0":{"0":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"_":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{".":{"5":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.0},"7":{"tf":2.449489742783178}}},"5":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"7":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0}}},"2":{".":{"1":{".":{"0":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{".":{"0":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"2":{"3":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"2":{"df":4,"docs":{"30":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{".":{"0":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"3":{"7":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"_":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"39":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":2.0}}}}}},"d":{"d":{"df":7,"docs":{"14":{"tf":1.0},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"23":{"tf":1.0},"30":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"21":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"_":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"2":{"5":{"6":{"_":{"c":{"b":{"c":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"_":{"5":{"1":{"2":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":0,"docs":{},"e":{"a":{"d":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"2":{"5":{"6":{"c":{"b":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"5":{"1":{"2":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":4,"docs":{"1":{"tf":1.0},"30":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"0":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"6":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178}}},"p":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":16,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"11":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0}}},"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":21,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":2.449489742783178},"7":{"tf":2.6457513110645907},"9":{"tf":1.0}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"14":{"tf":1.0},"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"17":{"tf":1.0},"18":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":1,"docs":{"33":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"14":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":2.0},"33":{"tf":2.8284271247461903},"34":{"tf":2.23606797749979},"35":{"tf":2.23606797749979},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"40":{"tf":2.8284271247461903}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"38":{"tf":3.0},"39":{"tf":2.0},"40":{"tf":2.23606797749979}}}}},"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"21":{"tf":1.0},"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":1,"docs":{"39":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":1,"docs":{"14":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"<":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"0":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"7":{"tf":2.8284271247461903}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":4,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":8,"docs":{"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.0},"21":{"tf":1.0}}}},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":6,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"16":{"tf":1.0},"21":{"tf":2.0},"29":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{">":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":24,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.8284271247461903},"11":{"tf":1.7320508075688772},"13":{"tf":2.0},"16":{"tf":1.7320508075688772},"17":{"tf":3.1622776601683795},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":2.6457513110645907},"34":{"tf":2.23606797749979},"35":{"tf":2.23606797749979},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":2.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":3,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":9,"docs":{"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"13":{"tf":3.0},"14":{"tf":1.0},"19":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"14":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":2.0},"25":{"tf":2.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":2,"docs":{"34":{"tf":1.0},"39":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":2.449489742783178}}}}}}}},"n":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"10":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"0":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":26,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.449489742783178},"14":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"5":{"tf":1.0},"7":{"tf":2.8284271247461903},"9":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"0":{"tf":1.0},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"10":{"tf":2.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"31":{"tf":1.0},"32":{"tf":2.449489742783178},"33":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"14":{"tf":2.6457513110645907}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":7,"docs":{"12":{"tf":1.0},"13":{"tf":2.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"27":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":2.0},"36":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"b":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":4,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":3,"docs":{"13":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{".":{"a":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"w":{"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":5,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":2.0}}}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"32":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"21":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":7,"docs":{"0":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"31":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"22":{"tf":1.7320508075688772},"25":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"33":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"24":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"c":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":8,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178},"39":{"tf":2.23606797749979},"40":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"13":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"22":{"tf":1.0}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":17,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.7320508075688772},"10":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"35":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"6":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"6":{"tf":1.0},"9":{"tf":1.0}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"c":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"0":{"tf":1.0},"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"v":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}},"df":12,"docs":{"29":{"tf":2.0},"30":{"tf":3.3166247903554},"31":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"33":{"tf":3.3166247903554},"34":{"tf":2.449489742783178},"35":{"tf":3.4641016151377544},"36":{"tf":3.4641016151377544},"37":{"tf":3.0},"38":{"tf":4.47213595499958},"39":{"tf":2.6457513110645907},"40":{"tf":3.1622776601683795}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"s":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":2.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"v":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}}}},"x":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"s":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":2.0},"22":{"tf":2.23606797749979},"23":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":15,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"18":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"40":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":2.0}}}},"s":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":18,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.23606797749979},"14":{"tf":1.7320508075688772},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"r":{"a":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"18":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":3,"docs":{"35":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":13,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":2.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.0},"30":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":2.0},"7":{"tf":2.449489742783178}}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":12,"docs":{"29":{"tf":1.0},"30":{"tf":2.6457513110645907},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.7320508075688772},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"n":{"d":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"38":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":2.0}}},"t":{"df":0,"docs":{},"e":{"2":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":12,"docs":{"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"27":{"tf":1.0},"32":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":4,"docs":{"17":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"7":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"14":{"tf":1.0},"21":{"tf":1.0},"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":2.449489742783178},"14":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.0},"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":2.0},"16":{"tf":1.0},"7":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"a":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"11":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"d":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"17":{"tf":1.0},"24":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":1,"docs":{"29":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"14":{"tf":1.0},"4":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.7320508075688772}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"3":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.0}}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"28":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"n":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"'":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}},"r":{"df":1,"docs":{"14":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}},"j":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.0}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"40":{"tf":1.0}}}},"y":{"1":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"2":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"[":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"1":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":7,"docs":{"34":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"38":{"tf":2.6457513110645907},"39":{"tf":2.6457513110645907},"40":{"tf":2.6457513110645907}},"i":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":8,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"v":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"z":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"<":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":12,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"32":{"tf":2.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"35":{"tf":1.0}}},"t":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":3,"docs":{"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":2.23606797749979}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"23":{"tf":1.0},"25":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":9,"docs":{"10":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"17":{"tf":1.0}}}}},"df":0,"docs":{}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"19":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"0":{"tf":1.0},"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"39":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"25":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":2.0}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"{":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":5,"docs":{"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":3.0},"39":{"tf":1.0},"40":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"2":{"7":{"0":{"2":{"0":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"22":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.0},"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"6":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"{":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"d":{"df":0,"docs":{},"o":{"c":{"df":5,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"u":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"=":{"'":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}},"df":26,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979},"39":{"tf":2.0},"4":{"tf":1.0},"40":{"tf":2.23606797749979},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"35":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":8,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"9":{"tf":1.0}}}}},"n":{"/":{"a":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":5,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":10,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"21":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":9,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"34":{"tf":1.0},"6":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"24":{"tf":1.0}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"l":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"_":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"14":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"33":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"35":{"tf":1.0},"9":{"tf":1.7320508075688772}},"s":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"14":{"tf":1.0},"30":{"tf":1.0}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"!":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}},"t":{"df":1,"docs":{"33":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":1,"docs":{"30":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"31":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"38":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"22":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"!":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"22":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"4":{"tf":1.0}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":6,"docs":{"17":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"35":{"tf":1.4142135623730951},"39":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"29":{"tf":1.0},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"(":{"&":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0}}}}}}},"df":3,"docs":{"12":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"7":{"tf":2.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":1,"docs":{"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"21":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"=":{"\"":{"df":0,"docs":{},"{":{"\\":{"\"":{"df":0,"docs":{},"n":{"\\":{"\"":{":":{"1":{",":{"\\":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"\\":{"\"":{":":{"1":{".":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"17":{"tf":1.0}},"i":{"d":{"=":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"r":{"df":11,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"16":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"30":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"t":{".":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":2.23606797749979}}}},"n":{"df":10,"docs":{"10":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"18":{"tf":2.449489742783178},"5":{"tf":2.449489742783178},"7":{"tf":2.449489742783178}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"=":{"'":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"=":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"df":6,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"2":{"tf":1.0},"28":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":6,"docs":{"32":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"[":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"35":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.8284271247461903},"36":{"tf":2.8284271247461903}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.0},"23":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":7,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"9":{"tf":1.0}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":2,"docs":{"22":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{},"{":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"6":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"13":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"=":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"=":{"2":{"7":{"0":{"1":{"7":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"t":{"df":9,"docs":{"0":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.0},"36":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":10,"docs":{"0":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.0},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"n":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"7":{"tf":1.0}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}},"w":{"df":0,"docs":{},"n":{"df":5,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":3,"docs":{"30":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"i":{"c":{"df":7,"docs":{"18":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"0":{"tf":1.0},"18":{"tf":1.0},"5":{"tf":2.0},"7":{"tf":2.449489742783178}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"27":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":7,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":3,"docs":{"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"h":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":14,"docs":{"0":{"tf":1.7320508075688772},"2":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":3.1622776601683795},"9":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"0":{"tf":1.0},"6":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"18":{"tf":2.23606797749979}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"25":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.0}}}}}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}},"l":{"df":1,"docs":{"7":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"o":{"df":2,"docs":{"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"s":{"(":{"5":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":17,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}},"y":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"6":{"3":{"d":{"d":{"5":{"df":0,"docs":{},"e":{"7":{"0":{"6":{"a":{"df":0,"docs":{},"f":{"9":{"9":{"0":{"8":{"df":0,"docs":{},"f":{"c":{"8":{"3":{"4":{"df":0,"docs":{},"f":{"d":{"9":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"20":{"tf":1.7320508075688772},"21":{"tf":2.8284271247461903},"23":{"tf":1.4142135623730951},"24":{"tf":2.8284271247461903},"25":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"24":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"e":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"13":{"tf":2.0},"14":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"3":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}},"l":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":6,"docs":{"20":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}}}},"p":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":30,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.7320508075688772},"10":{"tf":2.23606797749979},"11":{"tf":1.0},"13":{"tf":2.6457513110645907},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.23606797749979},"18":{"tf":3.3166247903554},"19":{"tf":1.7320508075688772},"24":{"tf":2.23606797749979},"25":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.6457513110645907},"36":{"tf":2.8284271247461903},"37":{"tf":1.7320508075688772},"38":{"tf":3.4641016151377544},"39":{"tf":2.8284271247461903},"4":{"tf":1.0},"40":{"tf":2.449489742783178},"5":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":2.23606797749979},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"v":{"0":{".":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"df":0,"docs":{},"x":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":3,"docs":{"21":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":2.23606797749979}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"c":{"!":{"[":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"0":{"df":0,"docs":{},"u":{"8":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"_":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"2":{"tf":1.4142135623730951},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}}}}}}},"i":{"a":{"df":4,"docs":{"0":{"tf":1.0},"14":{"tf":1.4142135623730951},"28":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"y":{"df":2,"docs":{"19":{"tf":1.0},"29":{"tf":1.0}}}},"df":0,"docs":{},"e":{"b":{"df":3,"docs":{"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"19":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}}}}},"x":{"df":3,"docs":{"1":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"7":{"tf":1.0}}}},"z":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}},"breadcrumbs":{"root":{"0":{".":{".":{"5":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{".":{"3":{"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"14":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"3":{"df":0,"docs":{},"t":{"1":{"9":{":":{"2":{"0":{":":{"1":{"6":{".":{"0":{"9":{"1":{"8":{"2":{"2":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"7":{"0":{"0":{"df":0,"docs":{},"z":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"_":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{".":{"5":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":2,"docs":{"21":{"tf":1.0},"7":{"tf":2.449489742783178}}},"5":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"7":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"3":{"4":{"5":{"6":{"7":{"8":{"9":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0}}},"2":{".":{"1":{".":{"0":{"df":2,"docs":{"4":{"tf":1.0},"5":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{".":{"0":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"2":{"3":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"2":{"df":4,"docs":{"30":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{".":{"0":{"df":2,"docs":{"37":{"tf":1.4142135623730951},"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"3":{"7":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"6":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"_":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"39":{"tf":1.0}},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":2.23606797749979}}}}}},"d":{"d":{"df":7,"docs":{"14":{"tf":1.0},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"23":{"tf":1.0},"30":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"21":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"_":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"2":{"5":{"6":{"_":{"c":{"b":{"c":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"_":{"5":{"1":{"2":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{":":{":":{"a":{"df":0,"docs":{},"e":{"a":{"d":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"2":{"5":{"6":{"c":{"b":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"5":{"1":{"2":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":4,"docs":{"1":{"tf":1.0},"30":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"25":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}}},"p":{"df":0,"docs":{},"i":{"df":4,"docs":{"0":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"6":{"tf":1.7320508075688772},"7":{"tf":2.449489742783178}}},"p":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"9":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":16,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"11":{"tf":1.0},"17":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"30":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0}}},"df":2,"docs":{"33":{"tf":1.0},"35":{"tf":1.0}}}}}},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"d":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":21,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":2.6457513110645907},"7":{"tf":2.6457513110645907},"9":{"tf":1.0}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"14":{"tf":1.0},"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"17":{"tf":1.0},"18":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":1,"docs":{"33":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"t":{"df":10,"docs":{"14":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":2.0},"33":{"tf":2.8284271247461903},"34":{"tf":2.449489742783178},"35":{"tf":2.449489742783178},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"40":{"tf":3.0}}}},"df":0,"docs":{}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":0,"docs":{}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"38":{"tf":3.0},"39":{"tf":2.0},"40":{"tf":2.23606797749979}}}}},"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"21":{"tf":1.0},"29":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"10":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"16":{"tf":1.7320508075688772},"19":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":1,"docs":{"39":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":1,"docs":{"14":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"13":{"tf":1.0},"5":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"18":{"tf":1.0}}},"df":0,"docs":{}}},"x":{"<":{"d":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"{":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":3,"docs":{"0":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"7":{"tf":2.8284271247461903}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":4,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}}}}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":8,"docs":{"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"13":{"tf":1.0},"21":{"tf":1.0}}}},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":6,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"11":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"16":{"tf":1.0},"21":{"tf":2.0},"29":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"5":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{">":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},":":{":":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":6,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"10":{"tf":1.0},"19":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":24,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":3.0},"11":{"tf":2.0},"13":{"tf":2.0},"16":{"tf":2.0},"17":{"tf":3.1622776601683795},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":2.6457513110645907},"34":{"tf":2.449489742783178},"35":{"tf":2.23606797749979},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":2.449489742783178},"39":{"tf":2.0},"40":{"tf":2.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"34":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"9":{"tf":1.0}},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"9":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":3,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"34":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":9,"docs":{"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"13":{"tf":3.1622776601683795},"14":{"tf":1.0},"19":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"14":{"tf":1.0},"22":{"tf":1.4142135623730951},"24":{"tf":2.0},"25":{"tf":2.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":2,"docs":{"34":{"tf":1.0},"39":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"21":{"tf":1.0},"23":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"31":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":2.449489742783178}}}}}}}},"n":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"24":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.4142135623730951}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"7":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.7320508075688772},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"0":{"tf":1.0},"22":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"1":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"1":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":26,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.449489742783178},"14":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"5":{"tf":1.0},"7":{"tf":2.8284271247461903},"9":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"0":{"tf":1.0},"4":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"10":{"tf":2.23606797749979},"11":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"38":{"tf":2.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{"_":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"(":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"31":{"tf":1.0},"32":{"tf":2.6457513110645907},"33":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"2":{"tf":1.0},"22":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"(":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"14":{"tf":2.8284271247461903}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"b":{"a":{"df":0,"docs":{},"s":{"df":10,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.7320508075688772},"13":{"tf":2.449489742783178},"14":{"tf":1.0},"16":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"33":{"tf":1.0},"8":{"tf":1.7320508075688772},"9":{"tf":1.4142135623730951}},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":2,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"d":{"b":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"=":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":11,"docs":{"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"27":{"tf":1.0},"30":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":2.0},"36":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"b":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"&":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{">":{"(":{"&":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"!":{"(":{"\"":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":4,"docs":{"13":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":3,"docs":{"13":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{".":{"a":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"w":{"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":5,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":2.23606797749979}}}}}}},"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"0":{"tf":1.0},"32":{"tf":1.4142135623730951},"5":{"tf":1.4142135623730951},"6":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"21":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":7,"docs":{"0":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.4142135623730951},"25":{"tf":1.0},"31":{"tf":1.4142135623730951},"4":{"tf":1.0},"7":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"df":2,"docs":{"5":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"0":{"tf":1.0},"22":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":2,"docs":{"22":{"tf":1.7320508075688772},"25":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"1":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"33":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"16":{"tf":1.0}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"24":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"c":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":8,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178},"39":{"tf":2.23606797749979},"40":{"tf":2.0}},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":14,"docs":{"13":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":1.0},"28":{"tf":1.0},"30":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"38":{"tf":2.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}}}}},"df":2,"docs":{"1":{"tf":1.0},"13":{"tf":1.0}},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":2,"docs":{"1":{"tf":1.7320508075688772},"22":{"tf":1.0}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":17,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.7320508075688772},"10":{"tf":1.0},"16":{"tf":1.0},"19":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"35":{"tf":1.0},"4":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"6":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"=":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":1.4142135623730951},"6":{"tf":1.0},"9":{"tf":1.0}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"18":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"27":{"tf":1.0}}}}}}},"c":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"0":{"tf":1.0},"20":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"6":{"tf":1.7320508075688772},"7":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"(":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"v":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}},"df":12,"docs":{"29":{"tf":2.23606797749979},"30":{"tf":3.605551275463989},"31":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":3.4641016151377544},"34":{"tf":2.8284271247461903},"35":{"tf":3.7416573867739413},"36":{"tf":3.7416573867739413},"37":{"tf":3.3166247903554},"38":{"tf":4.69041575982343},"39":{"tf":3.0},"40":{"tf":3.4641016151377544}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"38":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"s":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"36":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"40":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":2.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"33":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"v":{":":{":":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"r":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"\"":{")":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":1,"docs":{"25":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}}}}},"x":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"_":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"e":{"c":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":13,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}},"s":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"1":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":2.0},"22":{"tf":2.449489742783178},"23":{"tf":1.7320508075688772},"24":{"tf":2.0},"25":{"tf":1.7320508075688772}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":16,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"18":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":2.0}}}},"s":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"35":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":18,"docs":{"1":{"tf":1.4142135623730951},"10":{"tf":2.0},"13":{"tf":2.23606797749979},"14":{"tf":1.7320508075688772},"17":{"tf":1.0},"18":{"tf":2.8284271247461903},"19":{"tf":1.4142135623730951},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":2.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"9":{"tf":1.0}}}}},"r":{"a":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"32":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":3,"docs":{"18":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"s":{"df":3,"docs":{"35":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":14,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":2.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":2.0},"6":{"tf":2.23606797749979},"7":{"tf":2.8284271247461903}}}}}},"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":12,"docs":{"29":{"tf":1.0},"30":{"tf":2.8284271247461903},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"38":{"tf":2.0},"39":{"tf":2.0},"40":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}},"n":{"d":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":1,"docs":{"38":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"(":{")":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"14":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":5,"docs":{"0":{"tf":1.4142135623730951},"20":{"tf":1.0},"23":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":2.23606797749979}}},"t":{"df":0,"docs":{},"e":{"2":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":12,"docs":{"14":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"27":{"tf":1.0},"32":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":4,"docs":{"17":{"tf":1.0},"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":3,"docs":{"27":{"tf":1.0},"7":{"tf":1.4142135623730951},"9":{"tf":1.0}},"i":{"df":1,"docs":{"0":{"tf":1.0}}}}},"n":{"c":{"df":2,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"14":{"tf":1.0},"21":{"tf":1.0},"32":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"1":{"tf":2.449489742783178},"14":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":4,"docs":{"1":{"tf":1.0},"13":{"tf":2.23606797749979},"16":{"tf":1.0},"7":{"tf":1.0}},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"a":{"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":2,"docs":{"25":{"tf":1.0},"27":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"1":{"tf":1.0},"11":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"d":{"df":1,"docs":{"13":{"tf":1.4142135623730951}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"17":{"tf":1.0},"24":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}},"df":1,"docs":{"29":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"14":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"18":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.7320508075688772}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":8,"docs":{"3":{"tf":1.7320508075688772},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.0},"4":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"28":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"n":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"2":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"13":{"tf":1.0}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"18":{"tf":1.0}}}}},"t":{"'":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.0}}},"r":{"df":1,"docs":{"14":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}},"j":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.0}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"40":{"tf":1.0}}}},"y":{"1":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"2":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"(":{"[":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"1":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"3":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"\"":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772}}}}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":7,"docs":{"34":{"tf":1.0},"35":{"tf":2.449489742783178},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"38":{"tf":2.6457513110645907},"39":{"tf":2.6457513110645907},"40":{"tf":2.6457513110645907}},"i":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":8,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"v":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"32":{"tf":1.4142135623730951},"33":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"z":{"df":0,"docs":{},"y":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"<":{"(":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":12,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.449489742783178},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"31":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"0":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"0":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951}}}}}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"11":{"tf":1.0},"13":{"tf":1.0}}}}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"17":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"32":{"tf":2.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":1,"docs":{"35":{"tf":1.4142135623730951}}},"t":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":6,"docs":{"20":{"tf":1.7320508075688772},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.4142135623730951},"24":{"tf":1.0},"25":{"tf":2.6457513110645907}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"23":{"tf":1.0},"25":{"tf":1.0}}}}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"17":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"t":{"df":1,"docs":{"13":{"tf":1.0}}}}},"m":{"a":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":9,"docs":{"10":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"16":{"tf":1.0},"17":{"tf":1.0}}}}},"df":0,"docs":{}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"2":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"16":{"tf":1.0},"19":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"0":{"tf":1.0},"16":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"39":{"tf":1.0}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"1":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"25":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":4,"docs":{"13":{"tf":1.4142135623730951},"34":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":2.0}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{":":{":":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{":":{":":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"{":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"31":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"d":{"b":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":5,"docs":{"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":3.1622776601683795},"39":{"tf":1.0},"40":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"d":{"b":{":":{"/":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{":":{"2":{"7":{"0":{"2":{"0":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{"0":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{":":{"2":{"7":{"0":{"1":{"7":{"df":1,"docs":{"9":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"\"":{"\"":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"(":{"\"":{"\"":{")":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"<":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"22":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"10":{"tf":1.4142135623730951},"14":{"tf":1.0},"9":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"6":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"{":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"d":{"df":0,"docs":{},"o":{"c":{"df":5,"docs":{"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951}},"u":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"10":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"=":{"'":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}},"df":26,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.7320508075688772},"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"36":{"tf":2.23606797749979},"37":{"tf":2.23606797749979},"38":{"tf":2.23606797749979},"39":{"tf":2.0},"4":{"tf":1.0},"40":{"tf":2.23606797749979},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"16":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.4142135623730951},"35":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":4,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.7320508075688772}}}}},"u":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":8,"docs":{"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"9":{"tf":1.0}}}}},"n":{"/":{"a":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"29":{"tf":1.0},"5":{"tf":1.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":6,"docs":{"35":{"tf":1.7320508075688772},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":4,"docs":{"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":5,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"w":{"df":10,"docs":{"11":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"21":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{")":{".":{"a":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"?":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":9,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":3,"docs":{"21":{"tf":1.0},"34":{"tf":1.0},"6":{"tf":1.0}}},"i":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"24":{"tf":1.0}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"df":18,"docs":{"1":{"tf":1.0},"10":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"17":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}},"l":{"d":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"c":{"df":2,"docs":{"13":{"tf":1.0},"17":{"tf":1.0}},"e":{"_":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.4142135623730951}},"l":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"1":{"tf":1.0},"10":{"tf":1.4142135623730951},"11":{"tf":1.0},"14":{"tf":1.0},"17":{"tf":1.0},"5":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"14":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"33":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"14":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":2.0},"35":{"tf":1.0},"9":{"tf":1.7320508075688772}},"s":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"9":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},":":{":":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"10":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"{":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"36":{"tf":1.0},"38":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"31":{"tf":1.0}}}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"1":{"tf":1.0},"14":{"tf":1.0},"21":{"tf":1.0}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"14":{"tf":1.0},"30":{"tf":1.0}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"!":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"39":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"z":{"df":1,"docs":{"19":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}},"t":{"df":1,"docs":{"33":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":1,"docs":{"30":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"17":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"31":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"38":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"18":{"tf":1.0},"19":{"tf":1.4142135623730951},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"38":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"10":{"tf":1.0},"20":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"16":{"tf":1.0},"17":{"tf":1.7320508075688772},"22":{"tf":1.0}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.0},"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"!":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"22":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"4":{"tf":1.0}}},"df":1,"docs":{"31":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":6,"docs":{"17":{"tf":1.0},"28":{"tf":1.0},"32":{"tf":1.0},"35":{"tf":1.7320508075688772},"39":{"tf":1.0},"6":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"29":{"tf":1.0},"37":{"tf":2.449489742783178},"38":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"r":{"a":{"df":0,"docs":{},"n":{"d":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"(":{")":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"(":{"&":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"2":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0}}}}}}},"df":5,"docs":{"12":{"tf":1.7320508075688772},"13":{"tf":1.0},"14":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"7":{"tf":2.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"df":1,"docs":{"17":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"2":{"tf":1.0},"21":{"tf":1.4142135623730951},"29":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"35":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"21":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"=":{"\"":{"df":0,"docs":{},"{":{"\\":{"\"":{"df":0,"docs":{},"n":{"\\":{"\"":{":":{"1":{",":{"\\":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"\\":{"\"":{":":{"1":{".":{"0":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"14":{"tf":1.0},"17":{"tf":1.0}},"i":{"d":{"=":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"r":{"df":11,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0},"16":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"33":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"30":{"tf":1.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":16,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.7320508075688772},"17":{"tf":1.7320508075688772},"18":{"tf":1.7320508075688772},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":7,"docs":{"1":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":1.0},"9":{"tf":1.0}}}}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"28":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"28":{"tf":2.0}}}}}},"df":0,"docs":{}},"t":{".":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"18":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":2,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":2.449489742783178}}}},"n":{"df":10,"docs":{"10":{"tf":1.4142135623730951},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"18":{"tf":2.6457513110645907},"5":{"tf":2.6457513110645907},"7":{"tf":2.449489742783178}},"e":{":":{":":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"(":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"=":{"'":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"d":{"b":{":":{":":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"=":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.0}}}}}},"df":6,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"28":{"tf":1.0},"7":{"tf":1.0},"9":{"tf":1.4142135623730951}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":6,"docs":{"32":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.7320508075688772},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"16":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"(":{"[":{"(":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"35":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":2.8284271247461903},"36":{"tf":2.8284271247461903}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"14":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"31":{"tf":1.0}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"13":{"tf":1.0},"23":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"35":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":7,"docs":{"1":{"tf":1.0},"11":{"tf":1.0},"13":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"9":{"tf":1.0}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":2,"docs":{"22":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"13":{"tf":1.7320508075688772},"14":{"tf":1.0}},"e":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"df":0,"docs":{},"{":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"d":{"=":{"1":{"6":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":11,"docs":{"13":{"tf":1.0},"14":{"tf":1.0},"16":{"tf":1.0},"22":{"tf":1.0},"30":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"=":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"=":{"2":{"7":{"0":{"1":{"7":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"t":{"df":9,"docs":{"0":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"9":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"34":{"tf":1.0},"36":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"21":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"32":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":10,"docs":{"0":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"9":{"tf":1.0}}}}},"i":{"df":1,"docs":{"4":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"19":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}}},"n":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"7":{"tf":1.0}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"(":{"\"":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":1,"docs":{"9":{"tf":1.0}}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"14":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"39":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":6,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"21":{"tf":1.0}}},"w":{"df":0,"docs":{},"n":{"df":5,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.7320508075688772},"40":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":3,"docs":{"30":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}},"i":{"df":4,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"5":{"tf":1.0},"9":{"tf":1.0}}}}}},"df":0,"docs":{}}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}}},"df":4,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.0}}},"i":{"c":{"df":7,"docs":{"18":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"1":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{":":{":":{"a":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"10":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"0":{"tf":1.0},"18":{"tf":1.0},"5":{"tf":2.0},"7":{"tf":2.449489742783178}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"1":{"tf":1.0},"14":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":6,"docs":{"27":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"r":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":7,"docs":{"10":{"tf":1.0},"14":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"9":{"tf":1.7320508075688772}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"39":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":3,"docs":{"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"35":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"h":{"df":4,"docs":{"1":{"tf":1.0},"19":{"tf":1.0},"21":{"tf":1.4142135623730951},"30":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"35":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":14,"docs":{"0":{"tf":1.7320508075688772},"2":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.4142135623730951},"40":{"tf":1.0},"5":{"tf":1.0},"7":{"tf":3.1622776601683795},"9":{"tf":1.4142135623730951}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"0":{"tf":1.0},"6":{"tf":2.8284271247461903},"7":{"tf":1.4142135623730951}},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"x":{"df":3,"docs":{"33":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"31":{"tf":1.0},"32":{"tf":1.0}}}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":2.0}}}}}},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"19":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":5,"docs":{"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"17":{"tf":1.0}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"d":{"b":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":1,"docs":{"18":{"tf":2.23606797749979}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"25":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.0},"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":1,"docs":{"1":{"tf":1.4142135623730951}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":2.23606797749979}}}}}}},"t":{"df":0,"docs":{},"l":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}},"l":{"df":1,"docs":{"7":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"30":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"o":{"df":2,"docs":{"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"18":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"(":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":3,"docs":{"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"18":{"tf":1.7320508075688772}}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"(":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"s":{"(":{"5":{"df":1,"docs":{"1":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"1":{"tf":1.4142135623730951}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":17,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"10":{"tf":1.0},"13":{"tf":1.4142135623730951},"18":{"tf":2.0},"19":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.7320508075688772},"6":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"16":{"tf":1.0}},"y":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"6":{"3":{"d":{"d":{"5":{"df":0,"docs":{},"e":{"7":{"0":{"6":{"a":{"df":0,"docs":{},"f":{"9":{"9":{"0":{"8":{"df":0,"docs":{},"f":{"c":{"8":{"3":{"4":{"df":0,"docs":{},"f":{"d":{"9":{"4":{"df":2,"docs":{"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":6,"docs":{"20":{"tf":2.23606797749979},"21":{"tf":3.0},"22":{"tf":1.0},"23":{"tf":1.7320508075688772},"24":{"tf":3.1622776601683795},"25":{"tf":2.0}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"24":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"m":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"24":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"e":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.0},"40":{"tf":1.0}}}},"y":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"39":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"(":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"14":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":5,"docs":{"13":{"tf":2.0},"14":{"tf":1.0},"21":{"tf":1.0},"24":{"tf":1.0},"6":{"tf":1.0}}}}}},"u":{"3":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"30":{"tf":1.0}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"35":{"tf":1.0},"36":{"tf":1.0}},"e":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}},"l":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"38":{"tf":1.4142135623730951}},"e":{"d":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"38":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"38":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":1,"docs":{"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"1":{"tf":1.0},"18":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":6,"docs":{"20":{"tf":1.0},"21":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":2.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"33":{"tf":1.0}}}}}}}}}},"p":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"9":{"tf":1.0}}}},"s":{"df":30,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.7320508075688772},"10":{"tf":2.23606797749979},"11":{"tf":1.0},"13":{"tf":2.6457513110645907},"14":{"tf":2.0},"16":{"tf":1.0},"17":{"tf":2.23606797749979},"18":{"tf":3.3166247903554},"19":{"tf":1.7320508075688772},"24":{"tf":2.23606797749979},"25":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"32":{"tf":2.0},"33":{"tf":2.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.6457513110645907},"36":{"tf":2.8284271247461903},"37":{"tf":1.7320508075688772},"38":{"tf":3.4641016151377544},"39":{"tf":2.8284271247461903},"4":{"tf":1.0},"40":{"tf":2.449489742783178},"5":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":2.23606797749979},"9":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"40":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"7":{"tf":2.0}}},"df":0,"docs":{}}}},"v":{"0":{".":{"4":{"df":1,"docs":{"7":{"tf":1.0}}},"8":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"df":0,"docs":{},"x":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"38":{"tf":1.0}},"i":{"d":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"(":{"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":3,"docs":{"33":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"u":{"df":3,"docs":{"21":{"tf":1.4142135623730951},"36":{"tf":1.0},"38":{"tf":2.23606797749979}}}},"r":{"df":0,"docs":{},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"24":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"31":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"31":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.4142135623730951},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"e":{"c":{"!":{"[":{"(":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"0":{"df":0,"docs":{},"u":{"8":{"df":6,"docs":{"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"<":{"_":{"df":3,"docs":{"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":2,"docs":{"24":{"tf":1.0},"25":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"11":{"tf":1.0},"17":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"2":{"tf":1.7320508075688772},"21":{"tf":1.7320508075688772},"24":{"tf":1.0},"25":{"tf":1.4142135623730951},"34":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"6":{"tf":1.0},"7":{"tf":1.0}}}}}}}},"i":{"a":{"df":4,"docs":{"0":{"tf":1.0},"14":{"tf":1.4142135623730951},"28":{"tf":1.0},"35":{"tf":1.0}}},"df":0,"docs":{}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.4142135623730951}}}},"y":{"df":2,"docs":{"19":{"tf":1.0},"29":{"tf":1.0}}}},"df":0,"docs":{},"e":{"b":{"df":3,"docs":{"26":{"tf":1.7320508075688772},"27":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"19":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"30":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"33":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":5,"docs":{"1":{"tf":1.0},"13":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"5":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"30":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":1,"docs":{"17":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{":":{":":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"36":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":2,"docs":{"33":{"tf":1.0},"36":{"tf":1.0}}}}}}},"x":{"df":3,"docs":{"1":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"7":{"tf":1.0}}}},"z":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"7":{"tf":1.7320508075688772}}},"df":0,"docs":{}}}}}},"title":{"root":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"27":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":2,"docs":{"29":{"tf":1.0},"6":{"tf":1.0}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"5":{"tf":1.0}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"34":{"tf":1.0},"35":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"16":{"tf":1.0}}}}}},"c":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"16":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"5":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"8":{"tf":1.0},"9":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":3,"docs":{"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"10":{"tf":1.0}}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"32":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"14":{"tf":1.0}}}}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"12":{"tf":1.0},"13":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"31":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"6":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":8,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"26":{"tf":1.0}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"38":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"f":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"3":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"26":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"21":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"3":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"0":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"35":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":2,"docs":{"20":{"tf":1.0},"25":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"d":{"df":1,"docs":{"33":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":2,"docs":{"11":{"tf":1.0},"15":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"16":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"35":{"tf":1.0}}},"df":0,"docs":{}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"37":{"tf":1.0},"38":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"28":{"tf":1.0}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"35":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"18":{"tf":1.0},"5":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"36":{"tf":1.0}}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":3,"docs":{"30":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}},"t":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"21":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"9":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"20":{"tf":1.0},"24":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"29":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"2":{"tf":1.0}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"1":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"b":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}} \ No newline at end of file diff --git a/docs/manual/tomorrow-night.css b/docs/manual/tomorrow-night.css deleted file mode 100644 index 5b4aca77c..000000000 --- a/docs/manual/tomorrow-night.css +++ /dev/null @@ -1,102 +0,0 @@ -/* Tomorrow Night Theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ -/* Original theme - https://github.com/chriskempson/tomorrow-theme */ -/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */ - -/* Tomorrow Comment */ -.hljs-comment { - color: #969896; -} - -/* Tomorrow Red */ -.hljs-variable, -.hljs-attribute, -.hljs-tag, -.hljs-regexp, -.ruby .hljs-constant, -.xml .hljs-tag .hljs-title, -.xml .hljs-pi, -.xml .hljs-doctype, -.html .hljs-doctype, -.css .hljs-id, -.css .hljs-class, -.css .hljs-pseudo { - color: #cc6666; -} - -/* Tomorrow Orange */ -.hljs-number, -.hljs-preprocessor, -.hljs-pragma, -.hljs-built_in, -.hljs-literal, -.hljs-params, -.hljs-constant { - color: #de935f; -} - -/* Tomorrow Yellow */ -.ruby .hljs-class .hljs-title, -.css .hljs-rule .hljs-attribute { - color: #f0c674; -} - -/* Tomorrow Green */ -.hljs-string, -.hljs-value, -.hljs-inheritance, -.hljs-header, -.hljs-name, -.ruby .hljs-symbol, -.xml .hljs-cdata { - color: #b5bd68; -} - -/* Tomorrow Aqua */ -.hljs-title, -.css .hljs-hexcolor { - color: #8abeb7; -} - -/* Tomorrow Blue */ -.hljs-function, -.python .hljs-decorator, -.python .hljs-title, -.ruby .hljs-function .hljs-title, -.ruby .hljs-title .hljs-keyword, -.perl .hljs-sub, -.javascript .hljs-title, -.coffeescript .hljs-title { - color: #81a2be; -} - -/* Tomorrow Purple */ -.hljs-keyword, -.javascript .hljs-function { - color: #b294bb; -} - -.hljs { - display: block; - overflow-x: auto; - background: #1d1f21; - color: #c5c8c6; -} - -.coffeescript .javascript, -.javascript .xml, -.tex .hljs-formula, -.xml .javascript, -.xml .vbscript, -.xml .css, -.xml .hljs-cdata { - opacity: 0.5; -} - -.hljs-addition { - color: #718c00; -} - -.hljs-deletion { - color: #c82829; -} diff --git a/docs/manual/tracing.html b/docs/manual/tracing.html deleted file mode 100644 index 1f139fc26..000000000 --- a/docs/manual/tracing.html +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - Tracing and Logging - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Tracing and Logging

-

The driver utilizes the tracing crate to emit events at points of interest. To enable this, you must turn on the tracing-unstable feature flag.

-

Stability Guarantees

-

This functionality is considered unstable as the tracing crate has not reached 1.0 yet. Future minor versions of the driver may upgrade the tracing dependency -to a new version which is not backwards-compatible with Subscribers that depend on older versions of tracing. -Additionally, future minor releases may make changes such as:

-
    -
  • add or remove tracing events
  • -
  • add or remove values attached to tracing events
  • -
  • change the types and/or names of values attached to tracing events
  • -
  • add or remove driver-defined tracing spans
  • -
  • change the severity level of tracing events
  • -
-

Such changes will be called out in release notes.

-

Event Targets

-

Currently, events are emitted under the following targets:

-
- - - -
TargetDescription
mongodb::commandEvents describing commands sent to the database and their success or failure.
mongodb::server_selectionEvents describing the driver's process of selecting a server in the database deployment to send a command to.
mongodb::connectionEvents describing the behavior of driver connection pools and the connections they contain.
-
-

Consuming Events

-

To consume events in your application, in addition to enabling the tracing-unstable feature flag, you must either register a tracing-compatible subscriber or a log-compatible logger, as detailed in the following sections.

-

Consuming Events with tracing

-

To consume events with tracing, you will need to register a type implementing the tracing::Subscriber trait in your application, as discussed in the tracing docs.

-

Here's a minimal example of a program using the driver which uses a tracing subscriber.

-

First, add the following to Cargo.toml:

-
tracing = "LATEST_VERSION_HERE"
-tracing-subscriber = "LATEST_VERSION_HERE"
-mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] }
-
-

And then in main.rs:

-
extern crate mongodb;
-extern crate tokio;
-extern crate tracing_subscriber;
-use std::env;
-use mongodb::{bson::doc, error::Result, Client};
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // Register a global tracing subscriber which will obey the RUST_LOG environment variable
-    // config.
-    tracing_subscriber::fmt::init();
-
-    // Create a MongoDB client.
-    let mongodb_uri =
-        env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set.");
-    let client = Client::with_uri_str(mongodb_uri).await?;
-
-    // Insert a document.
-    let coll = client.database("test").collection("test_coll");
-    coll.insert_one(doc! { "x" : 1 }, None).await?;
-
-    Ok(())
-}
-

This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related events with severity debug or higher:

-
RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run
-
-

The output will look something like the following:

-
2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017
-2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0
-
-

Consuming Events with log

-

Alternatively, to consume events with log, you will need to add tracing as a dependency of your application, and enable either its log or log-always feature. -Those features are described in detail here.

-

Here's a minimal example of a program using the driver which uses env_logger.

-

In Cargo.toml:

-
tracing = { version = "LATEST_VERSION_HERE", features = ["log"] }
-mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] }
-env_logger = "LATEST_VERSION_HERE"
-
-

And in main.rs:

-
extern crate mongodb;
-extern crate tokio;
-extern crate env_logger;
-use std::env;
-use mongodb::{bson::doc, error::Result, Client};
-
-#[tokio::main]
-async fn main() -> Result<()> {
-    // Register a global logger.
-    env_logger::init();
-
-    // Create a MongoDB client.
-    let mongodb_uri =
-        env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set.");
-    let client = Client::with_uri_str(mongodb_uri).await?;
-
-    // Insert a document.
-    let coll = client.database("test").collection("test_coll");
-    coll.insert_one(doc! { "x" : 1 }, None).await?;
-
-    Ok(())
-}
-

This program can be run from the command line as follows, using the RUST_LOG environment variable to configure verbosity levels and observe command-related messages with severity debug or higher:

-
RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run
-
-

The output will look something like the following:

-
2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017
-2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0
-
- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/manual/web_framework_examples.html b/docs/manual/web_framework_examples.html deleted file mode 100644 index 10806982c..000000000 --- a/docs/manual/web_framework_examples.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - Web Framework Examples - MongoDB Rust Driver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - -
-
-

Web Framework Examples

-

Actix

-

The driver can be used easily with the Actix web framework by storing a Client in Actix application data. A full example application for using MongoDB with Actix can be found here.

-

Rocket

-

The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the rocket_db_pools crate contains instructions for using MongoDB with your Rocket application.

- -
- - -
-
- - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/docs/tour.md b/docs/tour.md new file mode 100644 index 000000000..4bce9dccc --- /dev/null +++ b/docs/tour.md @@ -0,0 +1,104 @@ +# A Guided Tour of the Rust Driver Codebase + +These are notes intended to accompany an informal walkthrough of key parts of the driver's code; they may be useful on their own but are not intended to be comprehensive or prescriptive. + +## Constructing the Client + +[src/client.rs](../src/client.rs) + +Entry point of the API. The first thing most users will interact with. + +* It's just a wrapper around an `Arc`-wrapped internal struct so users can cheaply `clone` it for convenient storage, passing to spawned tasks, etc. + * (actually a `TrackingArc`, which if a compile-time flag is turned on will track where clones are constructed for debugging) +* Notable internal bits: + * `topology`: tracks the servers we're connected to and maintains a pool of connections for each server. + * `options`: usually parsed from user-provided URI +* `Client` can be constructed from: + * A URI string. By far the most common. + * An options object directly. Power user tool. + * A builder if the user needs to enable in-use encryption. +* Events! + * Three different kinds: + * `Command`: "we told the server to do something" + * `CMAP`: "something happened with an open connection" + * `SDAM`: "something happened with a monitored server" + * plus logging (via `tracing`)! +* `pub fn database`: gateway to the rest of the public API +* `register_async_drop`: Rust doesn't have `async drop`, so we built our own. +* `select_server`: apply criteria to topology, get server (which has connection pool) + +## Doing Stuff to Data + +[src/db.rs](../src/db.rs) + +Gotta go through `Database` to do just about anything. Primarily users will be getting handles to `Collection`s but there are a bunch of bulk actions that can be done directly. + +* Like `Client`, it's just an `Arc` around an inner struct so users can cheaply `clone` it and pass it around. + * The inner struct is much lighter: a handle on the parent `Client`, a string name, and some options. +* `new` isn't public. Have to get it from `Client`. +* Can get a `Collection` from it, but there aren't any data operations in here, leading to... + +## Anatomy of an Action + +[src/action/aggregate.rs](../src/action/aggregate.rs) + +*Actions* are the leaves of the public API. They allow for fluent minimal-boilerplate option setting with Rustic type safety and minimal overhead; the drawback is that the internals are a little gnarly. + +A usage example: + +```rust +let cursor = db + .aggregate([doc!{ ... }]) // [1] + .bypass_document_validation(true) // [2] + .session(s) // [3] + .await?; // [4] +``` + +Breaking down what's happening here: + +1. This constructs the transient `Aggregate` type. Typically users will never deal with this type directly; it'll be constructed and consumed in the same method call chain. The transient action types can be thought of as _pending_ actions; they contain a reference to the target of the call, the parameters, the options, and the session. +2. This sets an option in the contained options object via the `option_setters!` proc macro, which also generates helpful doc links. +3. This sets the pending action to use an explicit session. Note that this can only be done if the action was using an implicit session; Rust lets us enforce at compile-time that you can't call `.session` twice :) We track this at the type level because it changes the type of the returned `Cursor`. +4. The `action_impl` proc macro will generate an `IntoFuture` impl for `Aggregate`, so when `await` is called it will be converted into a call to `execute`. + +With all that, the body of `execute` is pretty small - it constructs an `Aggregate` _operation_ and hands that to the client's `execute_cursor_operation`. This pairing between action and operation is very common: _action_ is the public API and _operation_ is the command sent to the server. + +## Observing an Operation + +[src/operation/insert.rs](../src/operation/insert.rs) + +Redirecting from aggregate to insert here; aggregate has the cursor machinery on top of operation. + +An `Operation` is a command that can be run on a mongodb server, with the bundled knowledge of how to construct the `Command` from the parameters of the `Operation` and how to interpret the `RawCommandResponse` from the server. + +The `Operation` trait is split into `Operation` (used for type constraints) and `OperationWithDefaults` (provides default impls and a blanket impl of `Operation`) to allow forwarding types to implement the base `Operation` without new methods silently introducing bugs. + +Most `Operation` impls are straightforward: aggregate components into a buffer with minor conditional logic, deserialize the response. + +## Examining an Executor + +[src/client/executor.rs](../src/client/executor.rs) + +This is very much where the sausage is made; it's also very rare for it to need changes. + +* `execute_operation` ... throws away some output of `execute_operation_with_details` +* `execute_operation_with_details` does some pre-retry-loop validation, calls into `execute_operation_with_retry` +* `execute_operation_with_retry` + * tracks transaction state + * selects a server + * checks out a connection from the pool of the selected server + * `execute_operation_on_connection` + * handles errors and retries +* `execute_operation_on_connection` + * builds command from operation + * lots of session-handling + * sends wire-format `Message` from `Command` + * does bookkeeping from response + + ## Future Fields + + This is far from comprehensive; most notably, this doesn't cover: + * the internals of `Topology` + * the `bson` crate and our integration with `serde` + * the testing infrastructure + \ No newline at end of file diff --git a/etc/list-dependencies.sh b/etc/list-dependencies.sh new file mode 100755 index 000000000..53c15e486 --- /dev/null +++ b/etc/list-dependencies.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# This script lightly formats the output of `cargo tree` in a way that's easy to paste into Google Sheets: +# +# 1. Get the output of this script into the paste buffer (on mac, ./etc/list-dependencies.sh | pbcopy) +# 2. Paste the contents into the sheet +# 3. On the clipboard icon that pops up for the pasted values, choose "Split text to columns" + +cargo tree --all-features --prefix none | sort | uniq | grep -v '(*)' | sed -e 's/ /,/' \ No newline at end of file diff --git a/etc/update_version/.gitignore b/etc/update_version/.gitignore new file mode 100644 index 000000000..c41cc9e35 --- /dev/null +++ b/etc/update_version/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/etc/update_version/Cargo.lock b/etc/update_version/Cargo.lock new file mode 100644 index 000000000..a1e91ae9e --- /dev/null +++ b/etc/update_version/Cargo.lock @@ -0,0 +1,141 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "argh" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219" +dependencies = [ + "argh_derive", + "argh_shared", +] + +[[package]] +name = "argh_derive" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a" +dependencies = [ + "argh_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "argh_shared" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531" +dependencies = [ + "serde", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "update_version" +version = "0.1.0" +dependencies = [ + "argh", + "regex", +] diff --git a/etc/update_version/Cargo.toml b/etc/update_version/Cargo.toml new file mode 100644 index 000000000..073e59af4 --- /dev/null +++ b/etc/update_version/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "update_version" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +argh = "0.1.12" +regex = "1.10.2" diff --git a/etc/update_version/src/main.rs b/etc/update_version/src/main.rs new file mode 100644 index 000000000..f1383bb04 --- /dev/null +++ b/etc/update_version/src/main.rs @@ -0,0 +1,127 @@ +use std::{collections::HashMap, path::Path}; + +use regex::Regex; + +#[derive(Debug)] +struct Location { + path: &'static Path, + pattern: Regex, // must contain a (?) match group +} + +impl Location { + fn new(path: &'static str, pattern: &str) -> Self { + Self { + path: Path::new(path), + pattern: Regex::new(pattern).unwrap(), + } + } +} + +struct PendingUpdates { + files: HashMap<&'static Path, String>, +} + +impl PendingUpdates { + fn new() -> Self { + Self { + files: HashMap::new(), + } + } + + fn apply(&mut self, location: &Location, update: &str) { + let text = self + .files + .entry(location.path) + .or_insert_with(|| std::fs::read_to_string(location.path).unwrap()); + + if !location.pattern.is_match(text) { + panic!("no match for {:?}", location); + } + let mut new_text = String::new(); + let mut last_match = 0; + for caps in location.pattern.captures_iter(text) { + let target = caps.name("target").expect(" capture group"); + let prefix = &text[last_match..target.start()]; + new_text.push_str(prefix); + new_text.push_str(update); + last_match = target.end(); + } + new_text.push_str(&text[last_match..]); + *text = new_text; + } + + fn write(self) { + for (path, contents) in self.files { + std::fs::write(path, contents).unwrap(); + } + } +} + +#[derive(argh::FromArgs)] +/// Update crate and git dependency versions in prep for release. +struct Args { + /// new version of the mongodb crate + #[argh(option)] + version: String, + + /// version of the bson crate + #[argh(option)] + bson: Option, + + /// version of the mongocrypt crate + #[argh(option)] + mongocrypt: Option, +} + +fn main() { + // nosemgrep: current-exe + let zero = std::env::current_exe().unwrap(); + let self_dir = zero.parent().unwrap(); + let main_dir = self_dir.join("../../../.."); + std::env::set_current_dir(main_dir).unwrap(); + + let args: Args = argh::from_env(); + + let version_locs = vec![ + Location::new( + "Cargo.toml", + r#"name = "mongodb"\nversion = "(?.*?)"\n"#, + ), + Location::new( + "Cargo.toml", + r#"mongodb-internal-macros = \{ path = "macros", version = "(?.*?)" \}\n"#, + ), + Location::new( + "macros/Cargo.toml", + r#"name = "mongodb-internal-macros"\nversion = "(?.*?)"\n"#, + ), + Location::new("README.md", r#"mongodb = "(?.*?)"\n"#), + Location::new( + "README.md", + r#"\[dependencies.mongodb\]\nversion = "(?.*?)"\n"#, + ), + Location::new( + "src/lib.rs", + r#"html_root_url = "https://docs.rs/mongodb/(?.*?)""#, + ), + ]; + let mut pending = PendingUpdates::new(); + for loc in &version_locs { + pending.apply(loc, &args.version); + } + + if let Some(bson) = args.bson { + let bson_version_loc = + Location::new("Cargo.toml", r#"bson =.*version = "(?.*?)".*"#); + pending.apply(&bson_version_loc, &bson); + } + + if let Some(mongocrypt) = args.mongocrypt { + let mongocrypt_version_loc = Location::new( + "Cargo.toml", + r#"mongocrypt =.*version = "(?.*?)".*"#, + ); + pending.apply(&mongocrypt_version_loc, &mongocrypt); + } + pending.write(); +} diff --git a/etc/wasmedge/.cargo/config.toml b/etc/wasmedge/.cargo/config.toml new file mode 100644 index 000000000..e29165276 --- /dev/null +++ b/etc/wasmedge/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +target = "wasm32-wasi" +rustflags = ["--cfg", "wasmedge", "--cfg", "tokio_unstable"] \ No newline at end of file diff --git a/etc/wasmedge/.gitignore b/etc/wasmedge/.gitignore new file mode 100644 index 000000000..2c96eb1b6 --- /dev/null +++ b/etc/wasmedge/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock diff --git a/etc/wasmedge/Cargo.toml b/etc/wasmedge/Cargo.toml new file mode 100644 index 000000000..257a72d08 --- /dev/null +++ b/etc/wasmedge/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wasmedge_mongodb" +version = "0.1.0" +edition = "2021" + +[patch.crates-io] +tokio = { git = "https://github.com/second-state/wasi_tokio.git", branch = "v1.36.x" } +socket2 = { git = "https://github.com/second-state/socket2.git", branch = "v0.5.x" } + +[dependencies] +mongodb = { path = "../../", default_features = false, features = ["compat-3-0-0", "rustls-tls"] } +tokio = { version = "1", features = ["io-util", "fs", "net", "time", "rt", "macros"] } \ No newline at end of file diff --git a/etc/wasmedge/README.md b/etc/wasmedge/README.md new file mode 100644 index 000000000..6ac6aeda5 --- /dev/null +++ b/etc/wasmedge/README.md @@ -0,0 +1,9 @@ +Please note: using the MongoDB Rust driver on WasmEdge is unsupported! + +This example program requires: +* WasmEdge (https://wasmedge.org/docs/start/install/) +* The Rust wasm toolchain (`rustup target add wasm32-wasi`) + +To compile: `cargo build --target wasm32-wasi --release` + +To run: `wasmedge target/wasm32-wasi/release/wasmedge_mongodb.wasm` \ No newline at end of file diff --git a/etc/wasmedge/src/main.rs b/etc/wasmedge/src/main.rs new file mode 100644 index 000000000..11d61a904 --- /dev/null +++ b/etc/wasmedge/src/main.rs @@ -0,0 +1,7 @@ +#[tokio::main(flavor = "current_thread")] +async fn main() { + let client = mongodb::Client::with_uri_str("mongodb://127.0.0.1:27017") + .await + .unwrap(); + println!("{:?}", client.list_database_names().await); +} diff --git a/macros/.gitignore b/macros/.gitignore new file mode 100644 index 000000000..b83d22266 --- /dev/null +++ b/macros/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 000000000..4c58f069d --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mongodb-internal-macros" +version = "3.2.3" +description = "Internal macros for the mongodb crate" +edition = "2021" +license = "Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +macro_magic = { version = "0.5.1", features = ["proc_support"] } +proc-macro2 = "1.0.78" +quote = "1.0.35" +syn = { version = "2.0.52", features = ["full", "parsing", "proc-macro", "extra-traits"] } + +[lib] +proc-macro = true diff --git a/macros/src/action_impl.rs b/macros/src/action_impl.rs new file mode 100644 index 000000000..45ed48201 --- /dev/null +++ b/macros/src/action_impl.rs @@ -0,0 +1,185 @@ +extern crate proc_macro; + +use quote::quote; +use syn::{ + braced, + parenthesized, + parse::{Parse, ParseStream}, + parse_macro_input, + parse_quote_spanned, + spanned::Spanned, + Block, + Generics, + Ident, + Lifetime, + Token, + Type, +}; + +use crate::parse_name; + +pub(crate) fn action_impl( + attrs: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let ActionImplAttrs { sync_type } = parse_macro_input!(attrs as ActionImplAttrs); + let ActionImpl { + generics, + lifetime, + action, + future_name, + exec_self_mut, + exec_output, + exec_body, + } = parse_macro_input!(input as ActionImpl); + + let mut unbounded_generics = generics.clone(); + for lt in unbounded_generics.lifetimes_mut() { + lt.bounds.clear(); + } + for ty in unbounded_generics.type_params_mut() { + ty.bounds.clear(); + } + + let sync_run = if let Some(sync_type) = sync_type { + quote! { + /// Synchronously execute this action. + pub fn run(self) -> Result<#sync_type> { + crate::sync::TOKIO_RUNTIME.block_on(std::future::IntoFuture::into_future(self)).map(<#sync_type>::new) + } + } + } else { + quote! { + /// Synchronously execute this action. + pub fn run(self) -> #exec_output { + crate::sync::TOKIO_RUNTIME.block_on(std::future::IntoFuture::into_future(self)) + } + } + }; + + quote! { + impl #generics crate::action::private::Sealed for #action { } + + impl #generics crate::action::Action for #action { } + + impl #generics std::future::IntoFuture for #action { + type Output = #exec_output; + type IntoFuture = #future_name #unbounded_generics; + + fn into_future(#exec_self_mut self) -> Self::IntoFuture { + #future_name (Box::pin(async move { + #exec_body + })) + } + } + + pub struct #future_name #generics (crate::BoxFuture<#lifetime, #exec_output>); + + impl #generics std::future::Future for #future_name #unbounded_generics { + type Output = #exec_output; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + self.0.as_mut().poll(cx) + } + } + + #[cfg(feature = "sync")] + impl #generics #action { + #sync_run + } + }.into() +} + +// impl Action for ActionType { +// type Future = FutureName; +// async fn execute([mut] self) -> OutType { } +// [SyncWrap] +// } +struct ActionImpl { + generics: Generics, + lifetime: Lifetime, + action: Type, + future_name: Ident, + exec_self_mut: Option, + exec_output: Type, + exec_body: Block, +} + +impl Parse for ActionImpl { + fn parse(input: ParseStream) -> syn::Result { + // impl Action for ActionType + input.parse::()?; + let generics: Generics = input.parse()?; + let mut lifetime = None; + for lt in generics.lifetimes() { + if lifetime.is_some() { + return Err(input.error("only one lifetime argument permitted")); + } + lifetime = Some(lt); + } + let lifetime = match lifetime { + Some(lt) => lt.lifetime.clone(), + None => parse_quote_spanned! { generics.span() => 'static }, + }; + parse_name(input, "Action")?; + input.parse::()?; + let action = input.parse()?; + + let impl_body; + braced!(impl_body in input); + + // type Future = FutureName; + impl_body.parse::()?; + parse_name(&impl_body, "Future")?; + impl_body.parse::()?; + let future_name = impl_body.parse()?; + impl_body.parse::()?; + + // async fn execute([mut] self) -> OutType { } + impl_body.parse::()?; + impl_body.parse::()?; + parse_name(&impl_body, "execute")?; + let exec_args; + parenthesized!(exec_args in impl_body); + let exec_self_mut = exec_args.parse()?; + exec_args.parse::()?; + if !exec_args.is_empty() { + return Err(exec_args.error("unexpected token")); + } + impl_body.parse::]>()?; + let exec_output = impl_body.parse()?; + let exec_body = impl_body.parse()?; + + if !impl_body.is_empty() { + return Err(exec_args.error("unexpected token")); + } + + Ok(ActionImpl { + generics, + lifetime, + action, + future_name, + exec_self_mut, + exec_output, + exec_body, + }) + } +} + +struct ActionImplAttrs { + sync_type: Option, +} + +impl Parse for ActionImplAttrs { + fn parse(input: ParseStream) -> syn::Result { + let mut out = Self { sync_type: None }; + if input.is_empty() { + return Ok(out); + } + + parse_name(input, "sync")?; + input.parse::()?; + out.sync_type = Some(input.parse()?); + Ok(out) + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 000000000..df8299391 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,87 @@ +extern crate proc_macro; + +mod action_impl; +mod option; +mod rustdoc; + +use macro_magic::import_tokens_attr; +use syn::{bracketed, parse::ParseStream, punctuated::Punctuated, Error, Ident, Token}; + +/// Generates: +/// * an `IntoFuture` executing the given method body +/// * an opaque wrapper type for the future in case we want to do something more fancy than +/// BoxFuture. +/// * a `run` method for sync execution, optionally with a wrapper function +#[proc_macro_attribute] +pub fn action_impl( + attrs: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + crate::action_impl::action_impl(attrs, input) +} + +/// Enables rustdoc links to types that link individually to each type +/// component. +#[proc_macro_attribute] +pub fn deeplink( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + crate::rustdoc::deeplink(attr, item) +} + +#[import_tokens_attr] +#[with_custom_parsing(crate::option::OptionSettersArgs)] +#[proc_macro_attribute] +pub fn option_setters( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + crate::option::option_setters(attr, item, __custom_tokens) +} + +#[proc_macro_attribute] +pub fn export_doc( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + crate::rustdoc::export_doc(attr, item) +} + +#[import_tokens_attr] +#[with_custom_parsing(crate::rustdoc::OptionsDocArgs)] +#[proc_macro_attribute] +pub fn options_doc( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + crate::rustdoc::options_doc(attr, item, __custom_tokens) +} + +/// Parse an identifier with a specific expected value. +fn parse_name(input: ParseStream, name: &str) -> syn::Result { + let ident = input.parse::()?; + if ident.to_string() != name { + return Err(Error::new( + ident.span(), + format!("expected '{}', got '{}'", name, ident), + )); + } + Ok(ident) +} + +macro_rules! macro_error { + ($span:expr, $($message:tt)+) => {{ + return Error::new($span, format!($($message)+)).into_compile_error().into(); + }}; +} +use macro_error; + +fn parse_ident_list(input: ParseStream, name: &str) -> syn::Result> { + parse_name(input, name)?; + input.parse::()?; + let content; + bracketed!(content in input); + let punc = Punctuated::::parse_terminated(&content)?; + Ok(punc.into_pairs().map(|p| p.into_value()).collect()) +} diff --git a/macros/src/option.rs b/macros/src/option.rs new file mode 100644 index 000000000..01b39d7e0 --- /dev/null +++ b/macros/src/option.rs @@ -0,0 +1,203 @@ +extern crate proc_macro; + +use std::collections::HashSet; + +use macro_magic::mm_core::ForeignPath; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + parse_quote, + spanned::Spanned, + Attribute, + Error, + Fields, + GenericArgument, + Ident, + ItemImpl, + ItemStruct, + Path, + PathArguments, + PathSegment, + Token, + Type, + Visibility, +}; + +use crate::macro_error; + +pub fn option_setters( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, + custom_tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let opt_struct = parse_macro_input!(attr as ItemStruct); + let mut impl_in = parse_macro_input!(item as ItemImpl); + let args = parse_macro_input!(custom_tokens as OptionSettersArgs); + + // Gather information about each option struct field + struct OptInfo { + name: Ident, + attrs: Vec, + type_: Path, + } + let mut opt_info = vec![]; + let fields = match &opt_struct.fields { + Fields::Named(f) => &f.named, + _ => macro_error!(opt_struct.span(), "options struct must have named fields"), + }; + for field in fields { + if !matches!(field.vis, Visibility::Public(..)) { + continue; + } + // name + let name = match &field.ident { + Some(f) => f.clone(), + None => continue, + }; + // doc and cfg attrs + let mut attrs = vec![]; + for attr in &field.attrs { + if attr.path().is_ident("doc") || attr.path().is_ident("cfg") { + attrs.push(attr.clone()); + } + } + // type, unwrapped from `Option` + let outer = match &field.ty { + Type::Path(ty) => &ty.path, + _ => macro_error!(field.span(), "invalid type"), + }; + let type_ = match inner_type(outer, "Option") { + Some(Type::Path(ty)) => ty.path.clone(), + _ => macro_error!(field.span(), "invalid type"), + }; + + opt_info.push(OptInfo { name, attrs, type_ }); + } + + // Append utility fns to `impl` block item list + let opt_field_type = &opt_struct.ident; + impl_in.items.push(parse_quote! { + #[allow(unused)] + fn options(&mut self) -> &mut #opt_field_type { + self.options.get_or_insert_with(<#opt_field_type>::default) + } + }); + impl_in.items.push(parse_quote! { + /// Set all options. Note that this will replace all previous values set. + pub fn with_options(mut self, value: impl Into>) -> Self { + self.options = value.into(); + self + } + }); + // Append setter fns to `impl` block item list + for OptInfo { name, attrs, type_ } in opt_info { + if args.skip.as_ref().is_some_and(|skip| skip.contains(&name)) { + continue; + } + let (accept, value) = if type_.is_ident("String") + || type_.is_ident("Bson") + || path_eq(&type_, &["bson", "Bson"]) + { + (quote! { impl Into<#type_> }, quote! { value.into() }) + } else if let Some(t) = inner_type(&type_, "Vec") { + ( + quote! { impl IntoIterator }, + quote! { value.into_iter().collect() }, + ) + } else { + (quote! { #type_ }, quote! { value }) + }; + impl_in.items.push(parse_quote! { + #(#attrs)* + pub fn #name(mut self, value: #accept) -> Self { + self.options().#name = Some(#value); + self + } + }); + } + + // All done. + impl_in.to_token_stream().into() +} + +pub(crate) struct OptionSettersArgs { + tokens: proc_macro2::TokenStream, + foreign_path: syn::Path, // + skip: Option>, // skip = [ident, ..] +} + +impl Parse for OptionSettersArgs { + fn parse(input: ParseStream) -> syn::Result { + let tokens: proc_macro2::TokenStream = input.fork().parse()?; + + let foreign_path = input.parse()?; + let mut out = Self { + tokens, + foreign_path, + skip: None, + }; + if input.parse::>()?.is_none() || input.is_empty() { + return Ok(out); + } + + out.skip = Some( + crate::parse_ident_list(input, "skip")? + .into_iter() + .collect(), + ); + input.parse::>()?; + + Ok(out) + } +} + +impl ToTokens for OptionSettersArgs { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(self.tokens.clone()); + } +} + +impl ForeignPath for OptionSettersArgs { + fn foreign_path(&self) -> &syn::Path { + &self.foreign_path + } +} + +fn inner_type<'a>(path: &'a Path, outer: &str) -> Option<&'a Type> { + if path.segments.len() != 1 { + return None; + } + let PathSegment { ident, arguments } = path.segments.first()?; + if ident != outer { + return None; + } + let args = if let PathArguments::AngleBracketed(angle) = arguments { + &angle.args + } else { + return None; + }; + if args.len() != 1 { + return None; + } + if let GenericArgument::Type(t) = args.first()? { + return Some(t); + } + + None +} + +fn path_eq(path: &Path, segments: &[&str]) -> bool { + if path.segments.len() != segments.len() { + return false; + } + for (actual, expected) in path.segments.iter().zip(segments.into_iter()) { + if actual.ident != expected { + return false; + } + if !actual.arguments.is_empty() { + return false; + } + } + true +} diff --git a/macros/src/rustdoc.rs b/macros/src/rustdoc.rs new file mode 100644 index 000000000..034b97849 --- /dev/null +++ b/macros/src/rustdoc.rs @@ -0,0 +1,260 @@ +extern crate proc_macro; + +use quote::{quote, ToTokens}; +use syn::{ + bracketed, + parse::{Parse, ParseStream}, + parse_macro_input, + parse_quote, + punctuated::Punctuated, + spanned::Spanned, + Error, + Expr, + Ident, + ImplItemFn, + ItemImpl, + Lit, + Meta, + PathArguments, + Token, +}; + +use crate::{macro_error, parse_name}; + +pub(crate) fn deeplink( + _attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let mut impl_fn = parse_macro_input!(item as ImplItemFn); + + for attr in &mut impl_fn.attrs { + // Skip non-`doc` attrs + if attr.path() != &parse_quote! { doc } { + continue; + } + // Get the string literal value from #[doc = "lit"] + let mut text = match &mut attr.meta { + Meta::NameValue(nv) => match &mut nv.value { + Expr::Lit(el) => match &mut el.lit { + Lit::Str(ls) => ls.value(), + _ => continue, + }, + _ => continue, + }, + _ => continue, + }; + // Process substrings delimited by "d[...]" + while let Some(ix) = text.find("d[") { + let pre = &text[..ix]; + let rest = &text[ix + 2..]; + let end = match rest.find(']') { + Some(v) => v, + None => macro_error!(attr.span(), "unterminated d["), + }; + let body = &rest[..end]; + let post = &rest[end + 1..]; + // Strip inner backticks, if any + let (fixed, body) = if body.starts_with('`') && body.ends_with('`') { + ( + true, + body.strip_prefix('`').unwrap().strip_suffix('`').unwrap(), + ) + } else { + (false, body) + }; + // Build new string + let mut new_text = pre.to_owned(); + if fixed { + new_text.push_str(""); + } + new_text.push_str(&text_link(body)); + if fixed { + new_text.push_str(""); + } + new_text.push_str(post); + text = new_text; + } + *attr = parse_quote! { #[doc = #text] }; + } + + impl_fn.into_token_stream().into() +} + +fn text_link(text: &str) -> String { + // Break into segments delimited by '<' or '>' + let segments = text.split_inclusive(&['<', '>']) + // Put each delimiter in its own segment + .flat_map(|s| { + if s == "<" || s == ">" { + vec![s] + } else if let Some(sub) = s.strip_suffix(&['<', '>']) { + vec![sub, &s[sub.len()..]] + } else { + vec![s] + } + }); + + // Build output + let mut out = vec![]; + for segment in segments { + match segment { + // Escape angle brackets + "<" => out.push("<"), + ">" => out.push(">"), + // Don't link unit + "()" => out.push("()"), + // Link to types + _ => { + // Use the short name + let short = segment + .rsplit_once("::") + .map(|(_, short)| short) + .unwrap_or(segment); + out.extend(["[", short, "](", segment, ")"]); + } + } + } + out.concat() +} + +pub(crate) fn options_doc( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, + custom_tokens: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let setters = parse_macro_input!(attr as ItemImpl); + let mut impl_fn = parse_macro_input!(item as ImplItemFn); + let args = parse_macro_input!(custom_tokens as OptionsDocArgs); + + // Collect a list of names from the setters impl + let mut setter_names = vec![]; + for item in &setters.items { + match item { + syn::ImplItem::Fn(item) if matches!(item.vis, syn::Visibility::Public(..)) => { + setter_names.push(item.sig.ident.to_token_stream().to_string()); + } + _ => continue, + } + } + + // Get the rustdoc path to the action type, i.e. the type with generic arguments stripped + let mut doc_path = match &*setters.self_ty { + syn::Type::Path(p) => p.path.clone(), + t => macro_error!(t.span(), "invalid options doc argument"), + }; + for seg in &mut doc_path.segments { + seg.arguments = PathArguments::None; + } + let doc_path = doc_path.to_token_stream().to_string(); + + // Add the list of setters to the rustdoc for the fn + impl_fn.attrs.push(parse_quote! { + #[doc = ""] + }); + let preamble = format!( + "These methods can be chained before `{}` to set options:", + if args.is_async() { ".await" } else { "run" } + ); + impl_fn.attrs.push(parse_quote! { + #[doc = #preamble] + }); + for name in setter_names { + let docstr = format!(" * [`{0}`]({1}::{0})", name, doc_path); + impl_fn.attrs.push(parse_quote! { + #[doc = #docstr] + }); + } + impl_fn.into_token_stream().into() +} + +pub(crate) struct OptionsDocArgs { + foreign_path: syn::Path, + sync: Option<(Token![,], Ident)>, +} + +impl OptionsDocArgs { + fn is_async(&self) -> bool { + self.sync.is_none() + } +} + +impl Parse for OptionsDocArgs { + fn parse(input: ParseStream) -> syn::Result { + let foreign_path = input.parse()?; + let sync = if input.is_empty() { + None + } else { + Some((input.parse()?, parse_name(input, "sync")?)) + }; + + Ok(Self { foreign_path, sync }) + } +} + +impl ToTokens for OptionsDocArgs { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + tokens.extend(self.foreign_path.to_token_stream()); + if let Some((comma, ident)) = &self.sync { + tokens.extend(comma.to_token_stream()); + tokens.extend(ident.to_token_stream()); + } + } +} + +impl macro_magic::mm_core::ForeignPath for OptionsDocArgs { + fn foreign_path(&self) -> &syn::Path { + &self.foreign_path + } +} + +pub(crate) fn export_doc( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let args = parse_macro_input!(attr as ExportDocArgs); + let impl_in = parse_macro_input!(item as ItemImpl); + + let mut doc_impl = impl_in.clone(); + // Synthesize a fn entry for each extra listed so it'll get a rustdoc entry + if let Some(extra) = args.extra { + for name in &extra { + doc_impl.items.push(parse_quote! { + pub fn #name(&self) {} + }); + } + } + + // All done. + let doc_name = args.name; + quote! { + #impl_in + + #[macro_magic::export_tokens_no_emit(#doc_name)] + #doc_impl + } + .into() +} + +struct ExportDocArgs { + name: Ident, + extra: Option>, // extra = [ident, ..] +} + +impl Parse for ExportDocArgs { + fn parse(input: ParseStream) -> syn::Result { + let name = input.parse()?; + let mut out = Self { name, extra: None }; + if input.parse::>()?.is_none() || input.is_empty() { + return Ok(out); + } + + parse_name(input, "extra")?; + input.parse::()?; + let content; + bracketed!(content in input); + let punc = Punctuated::::parse_terminated(&content)?; + out.extra = Some(punc.into_pairs().map(|p| p.into_value()).collect()); + + Ok(out) + } +} diff --git a/manual/.gitignore b/manual/.gitignore deleted file mode 100644 index 7585238ef..000000000 --- a/manual/.gitignore +++ /dev/null @@ -1 +0,0 @@ -book diff --git a/manual/book.toml b/manual/book.toml deleted file mode 100644 index 53d10d758..000000000 --- a/manual/book.toml +++ /dev/null @@ -1,12 +0,0 @@ -[book] -authors = ["Abraham Egnor"] -language = "en" -multilingual = false -src = "src" -title = "MongoDB Rust Driver" - -[rust] -edition = "2021" - -[build] -build-dir = "../docs/manual" diff --git a/manual/deps/.gitignore b/manual/deps/.gitignore deleted file mode 100644 index 2f7896d1d..000000000 --- a/manual/deps/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/manual/deps/Cargo.toml b/manual/deps/Cargo.toml deleted file mode 100644 index fb4a4714f..000000000 --- a/manual/deps/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "deps" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -mongodb = { path = "../..", features = ["in-use-encryption-unstable"] } -futures = "0.3" -once_cell = "1.10.0" -rand = "0.8.5" -tracing = "0.1" -tracing-subscriber = "0.3" -env_logger = "0.10" - -[dev-dependencies] -tokio = "1.23.0" -anyhow = { version = "1.0.66", features = ["backtrace"] } \ No newline at end of file diff --git a/manual/deps/src/example.rs b/manual/deps/src/example.rs deleted file mode 100644 index 03d8f22f3..000000000 --- a/manual/deps/src/example.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod automatic_queryable_encryption; -pub mod explicit_encryption; -pub mod explicit_encryption_auto_decryption; -pub mod explicit_queryable_encryption; -pub mod local_rules; -pub mod server_side_enforcement; diff --git a/manual/deps/src/example/automatic_queryable_encryption.rs b/manual/deps/src/example/automatic_queryable_encryption.rs deleted file mode 100644 index 539c23d4d..000000000 --- a/manual/deps/src/example/automatic_queryable_encryption.rs +++ /dev/null @@ -1,93 +0,0 @@ -use futures::TryStreamExt; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::KmsProvider, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - let key1_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["firstName".to_string()]) - .run() - .await?; - let key2_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["lastName".to_string()]) - .run() - .await?; - - let encrypted_fields_map = vec![( - "example.encryptedCollection", - doc! { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": key1_id, - "queries": [{"queryType": "equality"}], - }, - { - "path": "lastName", - "bsonType": "string", - "keyId": key2_id, - } - ] - }, - )]; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .encrypted_fields_map(encrypted_fields_map) - .build() - .await?; - let db = client.database("example"); - let coll = db.collection::("encryptedCollection"); - coll.drop(None).await?; - db.create_collection("encryptedCollection", None).await?; - coll.insert_one( - doc! { "_id": 1, "firstName": "Jane", "lastName": "Doe" }, - None, - ) - .await?; - let docs: Vec<_> = coll - .find(doc! {"firstName": "Jane"}, None) - .await? - .try_collect() - .await?; - println!("{:?}", docs); - - Ok(()) -} diff --git a/manual/deps/src/example/explicit_encryption.rs b/manual/deps/src/example/explicit_encryption.rs deleted file mode 100644 index a2aab9dfc..000000000 --- a/manual/deps/src/example/explicit_encryption.rs +++ /dev/null @@ -1,83 +0,0 @@ -use mongodb::{ - bson::{self, doc, Bson, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::{Algorithm, KmsProvider}, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // The MongoClient used to read/write application data. - let client = Client::with_uri_str(URI).await?; - let coll = client.database("test").collection::("coll"); - // Clear old data - coll.drop(None).await?; - - // Set up the key vault (key_vault_namespace) for this example. - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_3".to_string()]) - .run() - .await?; - - // Explicitly encrypt a field: - let encrypted_field = client_encryption - .encrypt( - "123456789", - data_key_id, - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - coll.insert_one(doc! { "encryptedField": encrypted_field }, None) - .await?; - let mut doc = coll.find_one(None, None).await?.unwrap(); - println!("Encrypted document: {:?}", doc); - - // Explicitly decrypt the field: - let field = match doc.get("encryptedField") { - Some(Bson::Binary(bin)) => bin, - _ => panic!("invalid field"), - }; - let decrypted: Bson = client_encryption - .decrypt(field.as_raw_binary()) - .await? - .try_into()?; - doc.insert("encryptedField", decrypted); - println!("Decrypted document: {:?}", doc); - - Ok(()) -} diff --git a/manual/deps/src/example/explicit_encryption_auto_decryption.rs b/manual/deps/src/example/explicit_encryption_auto_decryption.rs deleted file mode 100644 index 2c9344af1..000000000 --- a/manual/deps/src/example/explicit_encryption_auto_decryption.rs +++ /dev/null @@ -1,90 +0,0 @@ -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::{Algorithm, KmsProvider}, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // `bypass_auto_encryption(true)` disables automatic encryption but keeps - // the automatic _decryption_ behavior. bypass_auto_encryption will - // also disable spawning mongocryptd. - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace.clone(), - kms_providers.clone(), - )? - .bypass_auto_encryption(true) - .build() - .await?; - let coll = client.database("test").collection::("coll"); - // Clear old data - coll.drop(None).await?; - - // Set up the key vault (key_vault_namespace) for this example. - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_4".to_string()]) - .run() - .await?; - - // Explicitly encrypt a field: - let encrypted_field = client_encryption - .encrypt( - "123456789", - data_key_id, - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - coll.insert_one(doc! { "encryptedField": encrypted_field }, None) - .await?; - // Automatically decrypts any encrypted fields. - let doc = coll.find_one(None, None).await?.unwrap(); - println!("Decrypted document: {:?}", doc); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database("test") - .collection::("coll"); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - - Ok(()) -} diff --git a/manual/deps/src/example/explicit_queryable_encryption.rs b/manual/deps/src/example/explicit_queryable_encryption.rs deleted file mode 100644 index 4d6fdf0ab..000000000 --- a/manual/deps/src/example/explicit_queryable_encryption.rs +++ /dev/null @@ -1,138 +0,0 @@ -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::{Algorithm, KmsProvider}, - options::{ClientOptions, CreateCollectionOptions}, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // Set up the key vault (key_vault_namespace) for this example. - let client = Client::with_uri_str(URI).await?; - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let indexed_key_id = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - let unindexed_key_id = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - - let encrypted_fields = doc! { - "escCollection": "enxcol_.default.esc", - "eccCollection": "enxcol_.default.ecc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": indexed_key_id.clone(), - "path": "encryptedIndexed", - "bsonType": "string", - "queries": { - "queryType": "equality" - } - }, - { - "keyId": unindexed_key_id.clone(), - "path": "encryptedUnindexed", - "bsonType": "string", - } - ] - }; - - // The MongoClient used to read/write application data. - let encrypted_client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .bypass_query_analysis(true) - .build() - .await?; - let db = encrypted_client.database("test"); - db.drop(None).await?; - - // Create the collection with encrypted fields. - db.create_collection( - "coll", - CreateCollectionOptions::builder() - .encrypted_fields(encrypted_fields) - .build(), - ) - .await?; - let coll = db.collection::("coll"); - - // Create and encrypt an indexed and unindexed value. - let val = "encrypted indexed value"; - let unindexed_val = "encrypted unindexed value"; - let insert_payload_indexed = client_encryption - .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed) - .contention_factor(1) - .run() - .await?; - let insert_payload_unindexed = client_encryption - .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed) - .run() - .await?; - - // Insert the payloads. - coll.insert_one( - doc! { - "encryptedIndexed": insert_payload_indexed, - "encryptedUnindexed": insert_payload_unindexed, - }, - None, - ) - .await?; - - // Encrypt our find payload using QueryType.EQUALITY. - // The value of `data_key_id` must be the same as used to encrypt the values - // above. - let find_payload = client_encryption - .encrypt(val, indexed_key_id, Algorithm::Indexed) - .query_type("equality") - .contention_factor(1) - .run() - .await?; - - // Find the document we inserted using the encrypted payload. - // The returned document is automatically decrypted. - let doc = coll - .find_one(doc! { "encryptedIndexed": find_payload }, None) - .await?; - println!("Returned document: {:?}", doc); - - Ok(()) -} diff --git a/manual/deps/src/example/local_rules.rs b/manual/deps/src/example/local_rules.rs deleted file mode 100644 index b7c3a1e57..000000000 --- a/manual/deps/src/example/local_rules.rs +++ /dev/null @@ -1,93 +0,0 @@ -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::KmsProvider, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - // The MongoDB namespace (db.collection) used to store the - // encrypted documents in this example. - let encrypted_namespace = Namespace::new("test", "coll"); - - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("encryption", "__testKeyVault"); - - // The MongoClient used to access the key vault (key_vault_namespace). - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - // Create a new data key and json schema for the encryptedField. - // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_1".to_string()]) - .run() - .await?; - let schema = doc! { - "properties": { - "encryptedField": { - "encrypt": { - "keyId": [data_key_id], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - } - } - }, - "bsonType": "object", - }; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .schema_map([(encrypted_namespace.to_string(), schema)]) - .build() - .await?; - let coll = client - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - // Clear old data. - coll.drop(None).await?; - - coll.insert_one(doc! { "encryptedField": "123456789" }, None) - .await?; - println!("Decrypted document: {:?}", coll.find_one(None, None).await?); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - - Ok(()) -} diff --git a/manual/deps/src/example/server_side_enforcement.rs b/manual/deps/src/example/server_side_enforcement.rs deleted file mode 100644 index 08c34d40f..000000000 --- a/manual/deps/src/example/server_side_enforcement.rs +++ /dev/null @@ -1,103 +0,0 @@ -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - mongocrypt::ctx::KmsProvider, - options::{ClientOptions, CreateCollectionOptions, WriteConcern}, - Client, - Namespace, -}; -use rand::Rng; - -static URI: &str = "mongodb://localhost:27017"; - -type Result = anyhow::Result; - -pub async fn example() -> Result<()> { - // The MongoDB namespace (db.collection) used to store the - // encrypted documents in this example. - let encrypted_namespace = Namespace::new("test", "coll"); - - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("encryption", "__testKeyVault"); - - // The MongoClient used to access the key vault (key_vault_namespace). - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key and json schema for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_2".to_string()]) - .run() - .await?; - let schema = doc! { - "properties": { - "encryptedField": { - "encrypt": { - "keyId": [data_key_id], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - } - } - }, - "bsonType": "object", - }; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .build() - .await?; - let db = client.database(&encrypted_namespace.db); - let coll = db.collection::(&encrypted_namespace.coll); - // Clear old data - coll.drop(None).await?; - // Create the collection with the encryption JSON Schema. - db.create_collection( - &encrypted_namespace.coll, - CreateCollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .validator(doc! { "$jsonSchema": schema }) - .build(), - ) - .await?; - - coll.insert_one(doc! { "encryptedField": "123456789" }, None) - .await?; - println!("Decrypted document: {:?}", coll.find_one(None, None).await?); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - // This would return a Write error with the message "Document failed validation". - // unencrypted_coll.insert_one(doc! { "encryptedField": "123456789" }, None) - // .await?; - - Ok(()) -} diff --git a/manual/deps/src/lib.rs b/manual/deps/src/lib.rs deleted file mode 100644 index 86d7f5920..000000000 --- a/manual/deps/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[cfg(test)] -mod example; - -#[cfg(test)] -mod tests { - #[tokio::test] - async fn examples() -> anyhow::Result<()> { - super::example::local_rules::example().await?; - super::example::server_side_enforcement::example().await?; - super::example::automatic_queryable_encryption::example().await?; - super::example::explicit_queryable_encryption::example().await?; - super::example::explicit_encryption::example().await?; - super::example::explicit_encryption_auto_decryption::example().await?; - Ok(()) - } -} diff --git a/manual/src/README.md b/manual/src/README.md deleted file mode 100644 index 77449c8fd..000000000 --- a/manual/src/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Introduction - -[![Crates.io](https://img.shields.io/crates/v/mongodb.svg)](https://crates.io/crates/mongodb) [![docs.rs](https://docs.rs/mongodb/badge.svg)](https://docs.rs/mongodb) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) - -This is the manual for the officially supported MongoDB Rust driver, a client side library that can be used to interact with MongoDB deployments in Rust applications. It uses the [`bson`](https://docs.rs/bson/latest) crate for BSON support. The driver contains a fully async API that supports either [`tokio`](https://crates.io/crates/tokio) (default) or [`async-std`](https://crates.io/crates/async-std), depending on the feature flags set. The driver also has a sync API that may be enabled via feature flag. - -## Warning about timeouts / cancellation - -In async Rust, it is common to implement cancellation and timeouts by dropping a future after a certain period of time instead of polling it to completion. This is how [`tokio::time::timeout`](https://docs.rs/tokio/latest/tokio/time/fn.timeout.html) works, for example. However, doing this with futures returned by the driver can leave the driver's internals in an inconsistent state, which may lead to unpredictable or incorrect behavior (see [RUST-937](https://jira.mongodb.org/browse/RUST-937) for more details). As such, it is **_highly_** recommended to poll all futures returned from the driver to completion. In order to still use timeout mechanisms like `tokio::time::timeout` with the driver, one option is to spawn tasks and time out on their [`JoinHandle`](https://docs.rs/tokio/latest/tokio/task/struct.JoinHandle.html) futures instead of on the driver's futures directly. This will ensure the driver's futures will always be completely polled while also allowing the application to continue in the event of a timeout. - -e.g. -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# use std::time::Duration; -# use mongodb::{ -# Client, -# bson::doc, -# }; -# -# async fn foo() -> std::result::Result<(), Box> { -# -# let client = Client::with_uri_str("mongodb://example.com").await?; -let collection = client.database("foo").collection("bar"); -let handle = tokio::task::spawn(async move { - collection.insert_one(doc! { "x": 1 }, None).await -}); - -tokio::time::timeout(Duration::from_secs(5), handle).await???; -# Ok(()) -# } -``` - -## Minimum supported Rust version (MSRV) - -The MSRV for this crate is currently 1.57.0. This will rarely be increased, and if it ever is, -it will only happen in a minor or major version release. diff --git a/manual/src/SUMMARY.md b/manual/src/SUMMARY.md deleted file mode 100644 index 8b60d132b..000000000 --- a/manual/src/SUMMARY.md +++ /dev/null @@ -1,19 +0,0 @@ -# Summary - -- [Introduction](README.md) -- [Installation and Features](installation_features.md) -- [Connecting to the Database](connecting.md) -- [Reading From the Database](reading.md) -- [Writing To the Database]() -- [Performance](performance.md) -- [Serde Integration]() -- [Sessions and Transactions]() -- [Change Streams]() -- [Monitoring]() -- [Tracing and Logging](tracing.md) -- [Web Framework Examples](web_framework_examples.md) -- [Encryption](encryption.md) - -# Development - -- [Writing Tests]() \ No newline at end of file diff --git a/manual/src/connecting.md b/manual/src/connecting.md deleted file mode 100644 index 4ee5992d9..000000000 --- a/manual/src/connecting.md +++ /dev/null @@ -1,69 +0,0 @@ -# Connecting to the Database - -## Connection String -Connecting to a MongoDB database requires using a [connection string](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats), a URI of the form: -```uri -mongodb://[username:password@]host1[:port1][,...hostN[:portN]][/[defaultauthdb][?options]] -``` -At its simplest this can just specify the host and port, e.g. -```uri -mongodb://mongodb0.example.com:27017 -``` -For the full range of options supported by the Rust driver, see the documentation for the [`ClientOptions::parse`](https://docs.rs/mongodb/latest/mongodb/options/struct.ClientOptions.html#method.parse) method. That method will return a [`ClientOptions`](https://docs.rs/mongodb/latest/mongodb/options/struct.ClientOptions.html) struct, allowing for directly querying or setting any of the options supported by the Rust driver: -```rust,no_run -# extern crate mongodb; -# use mongodb::options::ClientOptions; -# async fn run() -> mongodb::error::Result<()> { -let mut options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?; -options.app_name = Some("My App".to_string()); -# Ok(()) -# } -``` - -## Creating a `Client` -The [`Client`](https://docs.rs/mongodb/latest/mongodb/struct.Client.html) struct is the main entry point for the driver. You can create one from a `ClientOptions` struct: -```rust,no_run -# extern crate mongodb; -# use mongodb::{Client, options::ClientOptions}; -# async fn run() -> mongodb::error::Result<()> { -# let options = ClientOptions::parse("mongodb://mongodb0.example.com:27017").await?; -let client = Client::with_options(options)?; -# Ok(()) -# } -``` -As a convenience, if you don't need to modify the `ClientOptions` before creating the `Client`, you can directly create one from the connection string: -```rust,no_run -# extern crate mongodb; -# use mongodb::Client; -# async fn run() -> mongodb::error::Result<()> { -let client = Client::with_uri_str("mongodb://mongodb0.example.com:27017").await?; -# Ok(()) -# } -``` -`Client` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, so it can safely be shared across threads or async tasks. For example: -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# use mongodb::{bson::Document, Client, error::Result}; -# use tokio::task; -# -# async fn start_workers() -> Result<()> { -let client = Client::with_uri_str("mongodb://example.com").await?; - -for i in 0..5 { - let client_ref = client.clone(); - - task::spawn(async move { - let collection = client_ref.database("items").collection::(&format!("coll{}", i)); - - // Do something with the collection - }); -} -# -# Ok(()) -# } -``` - -## Client Performance - -While cloning a `Client` is very lightweight, creating a new one is an expensive operation. For most use cases, it is highly recommended to create a single `Client` and persist it for the lifetime of your application. For more information, see the [Performance](performance.md) chapter. \ No newline at end of file diff --git a/manual/src/encryption.md b/manual/src/encryption.md deleted file mode 100644 index a1c644d02..000000000 --- a/manual/src/encryption.md +++ /dev/null @@ -1,774 +0,0 @@ -# Unstable API - -To enable support for in-use encryption ([client-side field level encryption](https://www.mongodb.com/docs/manual/core/csfle/) and [queryable encryption](https://www.mongodb.com/docs/manual/core/queryable-encryption/)), enable the `"in-use-encryption-unstable"` feature of the `mongodb` crate. As the name implies, the API for this feature is unstable, and may change in backwards-incompatible ways in minor releases. - -# Client-Side Field Level Encryption - -Starting with MongoDB 4.2, client-side field level encryption allows an application to encrypt specific data fields in addition to pre-existing MongoDB encryption features such as [Encryption at Rest](https://dochub.mongodb.org/core/security-encryption-at-rest) and [TLS/SSL (Transport Encryption)](https://dochub.mongodb.org/core/security-tls-transport-encryption). - -With field level encryption, applications can encrypt fields in documents prior to transmitting data over the wire to the server. Client-side field level encryption supports workloads where applications must guarantee that unauthorized parties, including server administrators, cannot read the encrypted data. - -See also the MongoDB documentation on [Client Side Field Level Encryption](https://dochub.mongodb.org/core/client-side-field-level-encryption). - -## Dependencies - -To get started using client-side field level encryption in your project, you will need to install [libmongocrypt](https://github.com/mongodb/libmongocrypt), which can be fetched from a [variety of package repositories](https://www.mongodb.com/docs/manual/core/csfle/reference/libmongocrypt/#std-label-csfle-reference-libmongocrypt). If you install libmongocrypt in a location outside of the system library search path, the `MONGOCRYPT_LIB_DIR` environment variable will need to be set when compiling your project. - -Additionally, either `crypt_shared` or `mongocryptd` are required in order to use automatic client-side encryption. - -### crypt_shared - -The Automatic Encryption Shared Library (crypt_shared) provides the same functionality as mongocryptd, but does not require you to spawn another process to perform automatic encryption. - -By default, the `mongodb` crate attempts to load crypt_shared from the system and if found uses it automatically. To load crypt_shared from another location, set the `"cryptSharedLibPath"` field in `extra_options`: -```rust,no_run -# extern crate mongodb; -# use mongodb::{bson::doc, Client, error::Result}; -# -# async fn func() -> Result<()> { -# let options = todo!(); -# let kv_namespace = todo!(); -# let kms_providers: Vec<_> = todo!(); -let client = Client::encrypted_builder(options, kv_namespace, kms_providers)? - .extra_options(doc! { - "cryptSharedLibPath": "/path/to/crypt/shared", - }) - .build(); -# -# Ok(()) -# } -``` -If the `mongodb` crate cannot load crypt_shared it will attempt to fallback to using mongocryptd by default. Include `"cryptSharedRequired": true` in the `extra_options` document to always use crypt_shared and fail if it could not be loaded. - -For detailed installation instructions see the [MongoDB documentation on Automatic Encryption Shared Library](https://www.mongodb.com/docs/manual/core/queryable-encryption/reference/shared-library). - -### mongocryptd - -If using `crypt_shared` is not an option, the `mongocryptd` binary is required for automatic client-side encryption and is included as a component in the [MongoDB Enterprise Server package](https://dochub.mongodb.org/core/install-mongodb-enterprise). For detailed installation instructions see the [MongoDB documentation on mongocryptd](https://dochub.mongodb.org/core/client-side-field-level-encryption-mongocryptd). - -`mongocryptd` performs the following: -* Parses the automatic encryption rules specified to the database connection. If the JSON schema contains invalid automatic encryption syntax or any document validation syntax, `mongocryptd` returns an error. -* Uses the specified automatic encryption rules to mark fields in read and write operations for encryption. -* Rejects read/write operations that may return unexpected or incorrect results when applied to an encrypted field. For supported and unsupported operations, see [Read/Write Support with Automatic Field Level Encryption](https://dochub.mongodb.org/core/client-side-field-level-encryption-read-write-support). - -A `Client` configured with auto encryption will automatically spawn the `mongocryptd` process from the application's `PATH`. Applications can control the spawning behavior as part of the automatic encryption options: -```rust,no_run -# extern crate mongodb; -# use mongodb::{bson::doc, Client, error::Result}; -# -# async fn func() -> Result<()> { -# let options = todo!(); -# let kv_namespace = todo!(); -# let kms_providers: Vec<_> = todo!(); -let client = Client::encrypted_builder(options, kv_namespace, kms_providers)? - .extra_options(doc! { - "mongocryptdSpawnPath": "/path/to/mongocryptd", - "mongocryptdSpawnArgs": ["--logpath=/path/to/mongocryptd.log", "--logappend"], - }) - .build(); -# -# Ok(()) -# } -``` -If your application wishes to manage the `mongocryptd` process manually, it is possible to disable spawning `mongocryptd`: -```rust,no_run -# extern crate mongodb; -# use mongodb::{bson::doc, Client, error::Result}; -# -# async fn func() -> Result<()> { -# let options = todo!(); -# let kv_namespace = todo!(); -# let kms_providers: Vec<_> = todo!(); -let client = Client::encrypted_builder(options, kv_namespace, kms_providers)? - .extra_options(doc! { - "mongocryptdBypassSpawn": true, - "mongocryptdURI": "mongodb://localhost:27020", - }) - .build(); -# -# Ok(()) -# } -``` -`mongocryptd` is only responsible for supporting automatic client-side field level encryption and does not itself perform any encryption or decryption. - -## Automatic Client-Side Field Level Encryption - -Automatic client-side field level encryption is enabled by using the `Client::encrypted_builder` constructor method. The following examples show how to setup automatic client-side field level encryption using `ClientEncryption` to create a new encryption data key. - -_Note_: Automatic client-side field level encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster. The community version of the server supports automatic decryption as well as explicit client-side encryption. - -### Providing Local Automatic Encryption Rules - -The following example shows how to specify automatic encryption rules via the `schema_map` option. The automatic encryption rules are expressed using a [strict subset of the JSON Schema syntax](https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules). - -Supplying a `schema_map` provides more security than relying on JSON Schemas obtained from the server. It protects against a malicious server advertising a false JSON Schema, which could trick the client into sending unencrypted data that should be encrypted. - -JSON Schemas supplied in the `schema_map` only apply to configuring automatic client-side field level encryption. Other validation rules in the JSON schema will not be enforced by the driver and will result in an error. - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# static URI: &str = "mongodb://example.com"; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::KmsProvider, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - // The MongoDB namespace (db.collection) used to store the - // encrypted documents in this example. - let encrypted_namespace = Namespace::new("test", "coll"); - - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("encryption", "__testKeyVault"); - - // The MongoClient used to access the key vault (key_vault_namespace). - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - // Create a new data key and json schema for the encryptedField. - // https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_1".to_string()]) - .run() - .await?; - let schema = doc! { - "properties": { - "encryptedField": { - "encrypt": { - "keyId": [data_key_id], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - } - } - }, - "bsonType": "object", - }; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .schema_map([(encrypted_namespace.to_string(), schema)]) - .build() - .await?; - let coll = client - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - // Clear old data. - coll.drop(None).await?; - - coll.insert_one(doc! { "encryptedField": "123456789" }, None) - .await?; - println!("Decrypted document: {:?}", coll.find_one(None, None).await?); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - - Ok(()) -} -``` - -### Server-Side Field Level Encryption Enforcement - -The MongoDB 4.2+ server supports using schema validation to enforce encryption of specific fields in a collection. This schema validation will prevent an application from inserting unencrypted values for any fields marked with the `"encrypt"` JSON schema keyword. - -The following example shows how to setup automatic client-side field level encryption using `ClientEncryption` to create a new encryption data key and create a collection with the [Automatic Encryption JSON Schema Syntax](https://dochub.mongodb.org/core/client-side-field-level-encryption-automatic-encryption-rules): - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# static URI: &str = "mongodb://example.com"; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::KmsProvider, - options::{ClientOptions, CreateCollectionOptions, WriteConcern}, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - // The MongoDB namespace (db.collection) used to store the - // encrypted documents in this example. - let encrypted_namespace = Namespace::new("test", "coll"); - - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("encryption", "__testKeyVault"); - - // The MongoClient used to access the key vault (key_vault_namespace). - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key and json schema for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_2".to_string()]) - .run() - .await?; - let schema = doc! { - "properties": { - "encryptedField": { - "encrypt": { - "keyId": [data_key_id], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", - } - } - }, - "bsonType": "object", - }; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .build() - .await?; - let db = client.database(&encrypted_namespace.db); - let coll = db.collection::(&encrypted_namespace.coll); - // Clear old data - coll.drop(None).await?; - // Create the collection with the encryption JSON Schema. - db.create_collection( - &encrypted_namespace.coll, - CreateCollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .validator(doc! { "$jsonSchema": schema }) - .build(), - ).await?; - - coll.insert_one(doc! { "encryptedField": "123456789" }, None) - .await?; - println!("Decrypted document: {:?}", coll.find_one(None, None).await?); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database(&encrypted_namespace.db) - .collection::(&encrypted_namespace.coll); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - // This would return a Write error with the message "Document failed validation". - // unencrypted_coll.insert_one(doc! { "encryptedField": "123456789" }, None) - // .await?; - - Ok(()) -} -``` - -### Automatic Queryable Encryption - -Verison 2.4.0 of the `mongodb` crate brings support for Queryable Encryption with MongoDB >=6.0. - -Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. - -You must have MongoDB 6.0 Enterprise to preview the feature. - -Automatic encryption in Queryable Encryption is configured with an `encrypted_fields` mapping, as demonstrated by the following example: - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# extern crate futures; -# static URI: &str = "mongodb://example.com"; -use futures::TryStreamExt; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::KmsProvider, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - let key_vault_client = Client::with_uri_str(URI).await?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - let client_encryption = ClientEncryption::new( - key_vault_client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - let key1_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["firstName".to_string()]) - .run() - .await?; - let key2_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["lastName".to_string()]) - .run() - .await?; - - let encrypted_fields_map = vec![( - "example.encryptedCollection", - doc! { - "escCollection": "encryptedCollection.esc", - "eccCollection": "encryptedCollection.ecc", - "ecocCollection": "encryptedCollection.ecoc", - "fields": [ - { - "path": "firstName", - "bsonType": "string", - "keyId": key1_id, - "queries": [{"queryType": "equality"}], - }, - { - "path": "lastName", - "bsonType": "string", - "keyId": key2_id, - } - ] - }, - )]; - - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .encrypted_fields_map(encrypted_fields_map) - .build() - .await?; - let db = client.database("example"); - let coll = db.collection::("encryptedCollection"); - coll.drop(None).await?; - db.create_collection("encryptedCollection", None).await?; - coll.insert_one( - doc! { "_id": 1, "firstName": "Jane", "lastName": "Doe" }, - None, - ) - .await?; - let docs: Vec<_> = coll - .find(doc! {"firstName": "Jane"}, None) - .await? - .try_collect() - .await?; - println!("{:?}", docs); - - Ok(()) -} -``` - -### Explicit Queryable Encryption - -Verison 2.4.0 of the `mongodb` crate brings support for Queryable Encryption with MongoDB >=6.0. - -Queryable Encryption is the second version of Client-Side Field Level Encryption. Data is encrypted client-side. Queryable Encryption supports indexed encrypted fields, which are further processed server-side. - -Explicit encryption in Queryable Encryption is performed using the `encrypt` and `decrypt` methods. Automatic encryption (to allow the `find_one` to automatically decrypt) is configured using an `encrypted_fields` mapping, as demonstrated by the following example: - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# static URI: &str = "mongodb://example.com"; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::{KmsProvider, Algorithm}, - options::{ClientOptions, CreateCollectionOptions}, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // Set up the key vault (key_vault_namespace) for this example. - let client = Client::with_uri_str(URI).await?; - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let indexed_key_id = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - let unindexed_key_id = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - - let encrypted_fields = doc! { - "escCollection": "enxcol_.default.esc", - "eccCollection": "enxcol_.default.ecc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": indexed_key_id.clone(), - "path": "encryptedIndexed", - "bsonType": "string", - "queries": { - "queryType": "equality" - } - }, - { - "keyId": unindexed_key_id.clone(), - "path": "encryptedUnindexed", - "bsonType": "string", - } - ] - }; - - // The MongoClient used to read/write application data. - let encrypted_client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace, - kms_providers, - )? - .bypass_query_analysis(true) - .build() - .await?; - let db = encrypted_client.database("test"); - db.drop(None).await?; - - // Create the collection with encrypted fields. - db.create_collection( - "coll", - CreateCollectionOptions::builder() - .encrypted_fields(encrypted_fields) - .build(), - ) - .await?; - let coll = db.collection::("coll"); - - // Create and encrypt an indexed and unindexed value. - let val = "encrypted indexed value"; - let unindexed_val = "encrypted unindexed value"; - let insert_payload_indexed = client_encryption - .encrypt(val, indexed_key_id.clone(), Algorithm::Indexed) - .contention_factor(1) - .run() - .await?; - let insert_payload_unindexed = client_encryption - .encrypt(unindexed_val, unindexed_key_id, Algorithm::Unindexed) - .run() - .await?; - - // Insert the payloads. - coll.insert_one( - doc! { - "encryptedIndexed": insert_payload_indexed, - "encryptedUnindexed": insert_payload_unindexed, - }, - None, - ) - .await?; - - // Encrypt our find payload using QueryType.EQUALITY. - // The value of `data_key_id` must be the same as used to encrypt the values - // above. - let find_payload = client_encryption - .encrypt(val, indexed_key_id, Algorithm::Indexed) - .query_type("equality") - .contention_factor(1) - .run() - .await?; - - // Find the document we inserted using the encrypted payload. - // The returned document is automatically decrypted. - let doc = coll - .find_one(doc! { "encryptedIndexed": find_payload }, None) - .await?; - println!("Returned document: {:?}", doc); - - Ok(()) -} -``` - -## Explicit Encryption - -Explicit encryption is a MongoDB community feature and does not use the mongocryptd process. Explicit encryption is provided by the `ClientEncryption` struct, for example: - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# static URI: &str = "mongodb://example.com"; -use mongodb::{ - bson::{self, doc, Bson, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::{Algorithm, KmsProvider}, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // The MongoClient used to read/write application data. - let client = Client::with_uri_str(URI).await?; - let coll = client.database("test").collection::("coll"); - // Clear old data - coll.drop(None).await?; - - // Set up the key vault (key_vault_namespace) for this example. - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_3".to_string()]) - .run() - .await?; - - // Explicitly encrypt a field: - let encrypted_field = client_encryption - .encrypt( - "123456789", - data_key_id, - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - coll.insert_one(doc! { "encryptedField": encrypted_field }, None) - .await?; - let mut doc = coll.find_one(None, None).await?.unwrap(); - println!("Encrypted document: {:?}", doc); - - // Explicitly decrypt the field: - let field = match doc.get("encryptedField") { - Some(Bson::Binary(bin)) => bin, - _ => panic!("invalid field"), - }; - let decrypted: Bson = client_encryption - .decrypt(field.as_raw_binary()) - .await? - .try_into()?; - doc.insert("encryptedField", decrypted); - println!("Decrypted document: {:?}", doc); - - Ok(()) -} -``` - -## Explicit Encryption with Automatic Decryption - -Although automatic encryption requires MongoDB 4.2+ enterprise or a MongoDB 4.2+ Atlas cluster, automatic decryption is supported for all users. To configure automatic decryption without automatic encryption set `bypass_auto_encryption` to `true` in the `EncryptedClientBuilder`: - - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate rand; -# static URI: &str = "mongodb://example.com"; -use mongodb::{ - bson::{self, doc, Document}, - client_encryption::{ClientEncryption, MasterKey}, - error::Result, - mongocrypt::ctx::{Algorithm, KmsProvider}, - options::ClientOptions, - Client, - Namespace, -}; -use rand::Rng; - -#[tokio::main] -async fn main() -> Result<()> { - // This must be the same master key that was used to create - // the encryption key. - let mut key_bytes = vec![0u8; 96]; - rand::thread_rng().fill(&mut key_bytes[..]); - let local_master_key = bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: key_bytes, - }; - let kms_providers = vec![(KmsProvider::Local, doc! { "key": local_master_key }, None)]; - - // The MongoDB namespace (db.collection) used to store - // the encryption data keys. - let key_vault_namespace = Namespace::new("keyvault", "datakeys"); - - // `bypass_auto_encryption(true)` disables automatic encryption but keeps - // the automatic _decryption_ behavior. bypass_auto_encryption will - // also disable spawning mongocryptd. - let client = Client::encrypted_builder( - ClientOptions::parse(URI).await?, - key_vault_namespace.clone(), - kms_providers.clone(), - )? - .bypass_auto_encryption(true) - .build() - .await?; - let coll = client.database("test").collection::("coll"); - // Clear old data - coll.drop(None).await?; - - // Set up the key vault (key_vault_namespace) for this example. - let key_vault = client - .database(&key_vault_namespace.db) - .collection::(&key_vault_namespace.coll); - key_vault.drop(None).await?; - - let client_encryption = ClientEncryption::new( - // The MongoClient to use for reading/writing to the key vault. - // This can be the same MongoClient used by the main application. - client, - key_vault_namespace.clone(), - kms_providers.clone(), - )?; - - // Create a new data key for the encryptedField. - let data_key_id = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(["encryption_example_4".to_string()]) - .run() - .await?; - - // Explicitly encrypt a field: - let encrypted_field = client_encryption - .encrypt( - "123456789", - data_key_id, - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - coll.insert_one(doc! { "encryptedField": encrypted_field }, None) - .await?; - // Automatically decrypts any encrypted fields. - let doc = coll.find_one(None, None).await?.unwrap(); - println!("Decrypted document: {:?}", doc); - let unencrypted_coll = Client::with_uri_str(URI) - .await? - .database("test") - .collection::("coll"); - println!( - "Encrypted document: {:?}", - unencrypted_coll.find_one(None, None).await? - ); - - Ok(()) -} -``` \ No newline at end of file diff --git a/manual/src/installation_features.md b/manual/src/installation_features.md deleted file mode 100644 index 7446aec94..000000000 --- a/manual/src/installation_features.md +++ /dev/null @@ -1,47 +0,0 @@ -# Installation and Features - -## Importing -The driver is available on [crates.io](https://crates.io/crates/mongodb). To use the driver in your application, simply add it to your project's `Cargo.toml`. -```toml -[dependencies] -mongodb = "2.1.0" -``` - -## Configuring the async runtime -The driver supports both of the most popular async runtime crates, namely [`tokio`](https://crates.io/crates/tokio) and [`async-std`](https://crates.io/crates/async-std). By default, the driver will use [`tokio`](https://crates.io/crates/tokio), but you can explicitly choose a runtime by specifying one of `"tokio-runtime"` or `"async-std-runtime"` feature flags in your `Cargo.toml`. - -For example, to instruct the driver to work with [`async-std`](https://crates.io/crates/async-std), add the following to your `Cargo.toml`: -```toml -[dependencies.mongodb] -version = "2.5.0" -default-features = false -features = ["async-std-runtime"] -``` - -## Enabling the sync API -The driver also provides a blocking sync API. To enable this, add the `"sync"` or `"tokio-sync"` feature to your `Cargo.toml`: -```toml -[dependencies.mongodb] -version = "2.5.0" -features = ["tokio-sync"] -``` -Using the `"sync"` feature also requires using `default-features = false`. -**Note:** The sync-specific types can be imported from `mongodb::sync` (e.g. `mongodb::sync::Client`). - -## All Feature Flags - -| Feature | Description | Extra dependencies | Default | -|:---------------------|:--------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------|:--------| -| `tokio-runtime` | Enable support for the `tokio` async runtime | `tokio` 1.0 with the `full` feature | yes | -| `async-std-runtime` | Enable support for the `async-std` runtime | `async-std` 1.0 | no | -| `sync` | Expose the synchronous API (`mongodb::sync`), using an async-std backend. Cannot be used with the `tokio-runtime` feature flag. | `async-std` 1.0 | no | -| `tokio-sync` | Expose the synchronous API (`mongodb::sync`), using a tokio backend. Cannot be used with the `async-std-runtime` feature flag. | `tokio` 1.0 with the `full` feature | no | -| `aws-auth` | Enable support for the MONGODB-AWS authentication mechanism. | `reqwest` 0.11 | no | -| `bson-uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-uuid-1` | Enable support for v1.x of the [`uuid`](docs.rs/uuid/1.0) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API of the re-exported `bson` crate. | n/a | no | -| `bson-serde_with` | Enable support for the [`serde_with`](docs.rs/serde_with/latest) crate in the public API of the re-exported `bson` crate. | `serde_with` 1.0 | no | -| `zlib-compression` | Enable support for compressing messages with [`zlib`](https://zlib.net/) | `flate2` 1.0 | no | -| `zstd-compression` | Enable support for compressing messages with [`zstd`](http://facebook.github.io/zstd/). This flag requires Rust version 1.54. | `zstd` 0.9.0 | no | -| `snappy-compression` | Enable support for compressing messages with [`snappy`](http://google.github.io/snappy/) | `snap` 1.0.5 | no | -| `openssl-tls` | Switch TLS connection handling to use ['openssl'](https://docs.rs/openssl/0.10.38/). | `openssl` 0.10.38 | no | \ No newline at end of file diff --git a/manual/src/performance.md b/manual/src/performance.md deleted file mode 100644 index 1ad84d076..000000000 --- a/manual/src/performance.md +++ /dev/null @@ -1,121 +0,0 @@ -# Performance - -## `Client` Best Practices - -The [`Client`](https://docs.rs/mongodb/latest/mongodb/struct.Client.html) handles many aspects of database connection behind the scenes that can require manual management for other database drivers; it discovers server topology, monitors it for any changes, and maintains an internal connection pool. This has implications for how a `Client` should be used for best performance. - -### Lifetime -A `Client` should be as long-lived as possible. Establishing a new `Client` is relatively slow and resource-intensive, so ideally that should only be done once at application startup. Because `Client` is implemented using an internal [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html), it can safely be shared across threads or tasks, and `clone`ing it to pass to new contexts is extremely cheap. -```rust,no_run -# extern crate mongodb; -# use mongodb::Client; -# use std::error::Error; -// This will be very slow because it's constructing and tearing down a `Client` -// with every request. -async fn handle_request_bad() -> Result<(), Box> { - let client = Client::with_uri_str("mongodb://example.com").await?; - // Do something with the client - Ok(()) -} - -// This will be much faster. -async fn handle_request_good(client: &Client) -> Result<(), Box> { - // Do something with the client - Ok(()) -} -``` - -This is especially noticeable when using a framework that provides connection pooling; because `Client` does its own pooling internally, attempting to maintain a pool of `Client`s will (somewhat counter-intuitively) result in worse performance than using a single one. - -### Runtime - -A `Client` is implicitly bound to the instance of the `tokio` or `async-std` runtime in which it was created. Attempting to execute operations on a different runtime instance will cause incorrect behavior and unpredictable failures. This is easy to accidentally invoke when testing, as the `tokio::test` or `async_std::test` helper macros create a new runtime for each test. -```rust,no_run -# extern crate mongodb; -# extern crate once_cell; -# extern crate tokio; -# use mongodb::Client; -# use std::error::Error; -use tokio::runtime::Runtime; -use once_cell::sync::Lazy; - -static CLIENT: Lazy = Lazy::new(|| { - let rt = Runtime::new().unwrap(); - rt.block_on(async { - Client::with_uri_str("mongodb://example.com").await.unwrap() - }) -}); - -// This will inconsistently fail. -#[tokio::test] -async fn test_list_dbs() -> Result<(), Box> { - CLIENT.list_database_names(None, None).await?; - Ok(()) -} -``` -To work around this issue, either create a new `Client` for every async test, or bundle the `Runtime` along with the client and don't use the test helper macros. -```rust,no_run -# extern crate mongodb; -# extern crate once_cell; -# extern crate tokio; -# use mongodb::Client; -# use std::error::Error; -use tokio::runtime::Runtime; -use once_cell::sync::Lazy; - -static CLIENT_RUNTIME: Lazy<(Client, Runtime)> = Lazy::new(|| { - let rt = Runtime::new().unwrap(); - let client = rt.block_on(async { - Client::with_uri_str("mongodb://example.com").await.unwrap() - }); - (client, rt) -}); - -#[test] -fn test_list_dbs() -> Result<(), Box> { - let (client, rt) = &*CLIENT_RUNTIME; - rt.block_on(async { - client.list_database_names(None, None).await - })?; - Ok(()) -} -``` -or -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# use mongodb::Client; -# use std::error::Error; -#[tokio::test] -async fn test_list_dbs() -> Result<(), Box> { - let client = Client::with_uri_str("mongodb://example.com").await?; - CLIENT.list_database_names(None, None).await?; - Ok(()) -} -``` - -## Parallelism - -Where data operations are naturally parallelizable, spawning many asynchronous tasks that use the driver concurrently is often the best way to achieve maximum performance, as the driver is designed to work well in such situations. -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# use mongodb::{bson::Document, Client, error::Result}; -# use tokio::task; -# -# async fn start_workers() -> Result<()> { -let client = Client::with_uri_str("mongodb://example.com").await?; - -for i in 0..5 { - let client_ref = client.clone(); - - task::spawn(async move { - let collection = client_ref.database("items").collection::(&format!("coll{}", i)); - - // Do something with the collection - }); -} -# -# Ok(()) -# } -``` \ No newline at end of file diff --git a/manual/src/reading.md b/manual/src/reading.md deleted file mode 100644 index cf064d821..000000000 --- a/manual/src/reading.md +++ /dev/null @@ -1,103 +0,0 @@ -# Reading From the Database - -## Database and Collection Handles - -Once you have a `Client`, you can call [`Client::database`](https://docs.rs/mongodb/latest/mongodb/struct.Client.html#method.database) to create a handle to a particular database on the server, and [`Database::collection`](https://docs.rs/mongodb/latest/mongodb/struct.Database.html#method.collection) to create a handle to a particular collection in that database. [`Database`](https://docs.rs/mongodb/latest/mongodb/struct.Database.html) and [`Collection`](https://docs.rs/mongodb/latest/mongodb/struct.Collection.html) handles are lightweight - creating them requires no IO, `clone`ing them is cheap, and they can be safely shared across threads or async tasks. For example: -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# use mongodb::{bson::Document, Client, error::Result}; -# use tokio::task; -# -# async fn start_workers() -> Result<()> { -# let client = Client::with_uri_str("mongodb://example.com").await?; -let db = client.database("items"); - -for i in 0..5 { - let db_ref = db.clone(); - - task::spawn(async move { - let collection = db_ref.collection::(&format!("coll{}", i)); - - // Do something with the collection - }); -} -# -# Ok(()) -# } -``` - -A `Collection` can be parameterized with a type for the documents in the collection; this includes but is not limited to just `Document`. The various methods that accept instances of the documents (e.g. [`Collection::insert_one`](https://docs.rs/mongodb/latest/mongodb/struct.Collection.html#method.insert_one)) require that it implement the `Serialize` trait from the [`serde`](http://serde.rs/) crate. Similarly, the methods that return instances (e.g. [`Collection::find_one`](https://docs.rs/mongodb/latest/mongodb/struct.Collection.html#method.find_one)) require that it implement `Deserialize`. - -`Document` implements both and can always be used as the type parameter. However, it is recommended to define types that model your data which you can parameterize your `Collection`s with instead, since doing so eliminates a lot of boilerplate deserialization code and is often more performant. - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate serde; -# use mongodb::{ -# bson::doc, -# error::Result, -# }; -# use tokio::task; -# -# async fn start_workers() -> Result<()> { -# use mongodb::Client; -# -# let client = Client::with_uri_str("mongodb://example.com").await?; -use serde::{Deserialize, Serialize}; - -// Define a type that models our data. -#[derive(Clone, Debug, Deserialize, Serialize)] -struct Item { - id: u32, -} - -// Parameterize our collection with the model. -let coll = client.database("items").collection::("in_stock"); - -for i in 0..5 { - // Perform operations that work with directly our model. - coll.insert_one(Item { id: i }, None).await; -} -# -# Ok(()) -# } -``` - -For more information, see the [Serde Integration](serde_integration.md) section. - -## Cursors - -Results from queries are generally returned via [`Cursor`](https://docs.rs/mongodb/latest/mongodb/struct.Cursor.html), a struct which streams the results back from the server as requested. The `Cursor` type implements the [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html) trait from the [`futures`](https://crates.io/crates/futures) crate, and in order to access its streaming functionality you need to import at least one of the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) or [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html) traits. - -```toml -# In Cargo.toml, add the following dependency. -futures = "0.3" -``` -```rust,no_run -# extern crate mongodb; -# extern crate serde; -# extern crate futures; -# use serde::Deserialize; -# #[derive(Deserialize)] -# struct Book { title: String } -# async fn foo() -> mongodb::error::Result<()> { -# let typed_collection = mongodb::Client::with_uri_str("").await?.database("").collection::(""); -// This trait is required to use `try_next()` on the cursor -use futures::stream::TryStreamExt; -use mongodb::{bson::doc, options::FindOptions}; - -// Query the books in the collection with a filter and an option. -let filter = doc! { "author": "George Orwell" }; -let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build(); -let mut cursor = typed_collection.find(filter, find_options).await?; - -// Iterate over the results of the cursor. -while let Some(book) = cursor.try_next().await? { - println!("title: {}", book.title); -} -# Ok(()) } -``` - -If a [`Cursor`](https://docs.rs/mongodb/latest/mongodb/struct.Cursor.html) is still open when it goes out of scope, it will automatically be closed via an asynchronous [killCursors](https://www.mongodb.com/docs/manual/reference/command/killCursors/) command executed from its [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation. diff --git a/manual/src/tracing.md b/manual/src/tracing.md deleted file mode 100644 index f981edef6..000000000 --- a/manual/src/tracing.md +++ /dev/null @@ -1,132 +0,0 @@ -# Tracing and Logging - -The driver utilizes the [`tracing`](https://crates.io/crates/tracing) crate to emit events at points of interest. To enable this, you must turn on the `tracing-unstable` feature flag. - -## Stability Guarantees -This functionality is considered unstable as the `tracing` crate has not reached 1.0 yet. Future minor versions of the driver may upgrade the `tracing` dependency -to a new version which is not backwards-compatible with `Subscriber`s that depend on older versions of `tracing`. -Additionally, future minor releases may make changes such as: -* add or remove tracing events -* add or remove values attached to tracing events -* change the types and/or names of values attached to tracing events -* add or remove driver-defined tracing spans -* change the severity level of tracing events - -Such changes will be called out in release notes. - -## Event Targets - -Currently, events are emitted under the following targets: - -| Target | Description | -|-----------------------------|---------------------------------------------------------------------------------------------------------------| -| `mongodb::command` | Events describing commands sent to the database and their success or failure. | -| `mongodb::server_selection` | Events describing the driver's process of selecting a server in the database deployment to send a command to. | -| `mongodb::connection` | Events describing the behavior of driver connection pools and the connections they contain. | - -## Consuming Events -To consume events in your application, in addition to enabling the `tracing-unstable` feature flag, you must either register a `tracing`-compatible subscriber or a `log`-compatible logger, as detailed in the following sections. - -### Consuming Events with `tracing` - -To consume events with `tracing`, you will need to register a type implementing the `tracing::Subscriber` trait in your application, as [discussed in the `tracing` docs](https://docs.rs/tracing/latest/tracing/#in-executables). - -Here's a minimal example of a program using the driver which uses a tracing subscriber. - -First, add the following to `Cargo.toml`: -```toml,no_run -tracing = "LATEST_VERSION_HERE" -tracing-subscriber = "LATEST_VERSION_HERE" -mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] } -``` - -And then in `main.rs`: - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate tracing_subscriber; -# use std::env; -use mongodb::{bson::doc, error::Result, Client}; - -#[tokio::main] -async fn main() -> Result<()> { - // Register a global tracing subscriber which will obey the RUST_LOG environment variable - // config. - tracing_subscriber::fmt::init(); - - // Create a MongoDB client. - let mongodb_uri = - env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set."); - let client = Client::with_uri_str(mongodb_uri).await?; - - // Insert a document. - let coll = client.database("test").collection("test_coll"); - coll.insert_one(doc! { "x" : 1 }, None).await?; - - Ok(()) -} -``` - -This program can be run from the command line as follows, using the [`RUST_LOG`](https://docs.rs/tracing-subscriber/0.3.16/tracing_subscriber/fmt/index.html#filtering-events-with-environment-variables) environment variable to configure verbosity levels and observe command-related events with severity debug or higher: -```sh,no_run -RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run -``` - -The output will look something like the following: -```text -2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 -2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0 -``` - -### Consuming Events with `log` - -Alternatively, to consume events with `log`, you will need to add `tracing` as a dependency of your application, and enable either its `log` or `log-always` feature. -Those features are described in detail [here](https://docs.rs/tracing/latest/tracing/#log-compatibility). - -Here's a minimal example of a program using the driver which uses [`env_logger`](https://crates.io/crates/env_logger). - -In `Cargo.toml`: -```toml,no_run -tracing = { version = "LATEST_VERSION_HERE", features = ["log"] } -mongodb = { version = "LATEST_VERSION_HERE", features = ["tracing-unstable"] } -env_logger = "LATEST_VERSION_HERE" -``` - -And in `main.rs`: - -```rust,no_run -# extern crate mongodb; -# extern crate tokio; -# extern crate env_logger; -use std::env; -use mongodb::{bson::doc, error::Result, Client}; - -#[tokio::main] -async fn main() -> Result<()> { - // Register a global logger. - env_logger::init(); - - // Create a MongoDB client. - let mongodb_uri = - env::var("MONGODB_URI").expect("The MONGODB_URI environment variable was not set."); - let client = Client::with_uri_str(mongodb_uri).await?; - - // Insert a document. - let coll = client.database("test").collection("test_coll"); - coll.insert_one(doc! { "x" : 1 }, None).await?; - - Ok(()) -} -``` - -This program can be run from the command line as follows, using the [`RUST_LOG`](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) environment variable to configure verbosity levels and observe command-related messages with severity debug or higher: -```sh,no_run -RUST_LOG='mongodb::command=debug' MONGODB_URI='YOUR_URI_HERE' cargo run -``` - -The output will look something like the following: -```text -2023-02-03T19:20:16.091822Z DEBUG mongodb::command: Command started topologyId="63dd5e706af9908fc834fd94" command="{\"insert\":\"test_coll\",\"ordered\":true,\"$db\":\"test\",\"lsid\":{\"id\":{\"$binary\":{\"base64\":\"y/v7PiLaRwOhT0RBFRDtNw==\",\"subType\":\"04\"}}},\"documents\":[{\"_id\":{\"$oid\":\"63dd5e706af9908fc834fd95\"},\"x\":1}]}" databaseName="test" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 -2023-02-03T19:20:16.092700Z DEBUG mongodb::command: Command succeeded topologyId="63dd5e706af9908fc834fd94" reply="{\"n\":1,\"ok\":1.0}" commandName="insert" requestId=4 driverConnectionId=1 serverConnectionId=16 serverHost="localhost" serverPort=27017 durationMS=0 -``` diff --git a/manual/src/web_framework_examples.md b/manual/src/web_framework_examples.md deleted file mode 100644 index 0389ac268..000000000 --- a/manual/src/web_framework_examples.md +++ /dev/null @@ -1,7 +0,0 @@ -# Web Framework Examples - -## Actix -The driver can be used easily with the Actix web framework by storing a `Client` in Actix application data. A full example application for using MongoDB with Actix can be found [here](https://github.com/actix/examples/tree/master/databases/mongodb). - -## Rocket -The Rocket web framework provides built-in support for MongoDB via the Rust driver. The documentation for the [`rocket_db_pools`](https://api.rocket.rs/v0.5-rc/rocket_db_pools/index.html) crate contains instructions for using MongoDB with your Rocket application. \ No newline at end of file diff --git a/manual/test.sh b/manual/test.sh deleted file mode 100755 index 2030c6b33..000000000 --- a/manual/test.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -o errexit - -source ./.evergreen/configure-rust.sh - -cd $(dirname $0)/deps -cargo build -cd .. -mdbook test -L deps/target/debug/deps \ No newline at end of file diff --git a/migration-3.0.md b/migration-3.0.md new file mode 100644 index 000000000..330290a42 --- /dev/null +++ b/migration-3.0.md @@ -0,0 +1,140 @@ +# Migrating from 2.x to 3.0 +3.0 introduces a wide variety of improvements that required backwards-incompatible API changes; in most cases these changes should require only minor updates in application code. + +## Fluent API +Async methods that accepted options have been updated to allow the individual options to be given directly in line with the call to reduce required boilerplate. A session can now also be set using a chained method rather than a distinct `_with_session` method. + +In 2.x: +```rust +// Without options +let cursor = collection.find(doc! { "status": "A" }, None).await?; +// With options +let cursor = collection + .find( + doc! { "status": "A" }, + FindOptions::builder() + .projection(doc! { + "status": 0, + "instock": 0, + }) + .build(), + ) + .await?; +// With options and a session +let cursor = collection + .find_with_session( + doc! { "status": "A" }, + FindOptions::builder() + .projection(doc! { + "status": 0, + "instock": 0, + }) + .build(), + &mut session, + ) + .await?; +``` + +In 3.0: +```rust +// Without options +let cursor = collection.find(doc! { "status": "A" }).await?; +// With options +let cursor = collection + .find(doc! { "status": "A" }) + .projection(doc! { + "status": 0, + "instock": 0, + }) + .await?; +// With options and a session +let cursor = collection + .find(doc! { "status": "A" }) + .projection(doc! { + "status": 0, + "instock": 0, + }) + .session(&mut session) + .await?; +``` + +If an option needs to be conditionally set, the `optional` convenience method can be used: +```rust +async fn run_find(opt_projection: Option) -> Result<()> { + let cursor = collection + .find(doc! { "status": "A" }) + .optional(opt_projection, |f, p| f.projection(p)) + .await?; + ... +} +``` + +For those cases where options still need to be constructed independently, the `with_options` method is provided: +```rust +let options = FindOptions::builder() + .projection(doc! { + "status": 0, + "instock": 0, + }) + .build(); +let cursor = collection + .find(doc! { "status": "A" }) + .with_options(options) + .await?; +``` + +## Listening for Events +The 2.x event API required that applications provide an `Arc` (or `CommandEventHandler` / `SdamEventHandler`). This required a fair amount of boilerplate, especially for simpler handlers (e.g. logging), and did not provide any way for event handlers to perform async work. To address both of those, 3.0 introduces the `EventHandler` type. + +In 2.x: +```rust +struct CmapEventLogger; +impl CmapEventHandler for CmapEventLogger { + fn handle_pool_created_event(&self, event: PoolCreatedEvent) { + println!("{:?}", event); + } + /// repeat for handle_pool_ready_event, etc. +} +let options = ClientOptions::builder() + .cmap_event_handler(Arc::new(CmapEventLogger)) + .build(); +``` + +In 3.0: +```rust +let options = ClientOptions::builder() + .cmap_event_handler(EventHandler::callback(|ev| println!("{:?}", ev))) + .build(); +``` + +`EventHandler` can be constructed from a callback, async callback, or an async channel sender. To ease migration, it can also be constructed from the now-deprecated 2.x `CmapEventHandler`/`CommandEventHandler`/`SdamEventHandler` types. + +## Async Runtime +2.x supported both `tokio` (default) and `async-std` as async runtimes. Maintaining this dual support was a significant ongoing development cost, and with both Rust mongodb driver usage and overall Rust ecosystem usage heavily favoring `tokio`, 3.0 only supports `tokio`. + +## Future-proof Features +Starting in 3.0, if the Rust driver is compiled with `no-default-features` it will require the use of a `compat` feature; this provides the flexibility to make features optional in future versions of the driver. Lack of this had prevented `rustls` and `dns-resolution` from becoming optional in 2.x; they are now optional in 3.0. + +## ReadConcern / WriteConcern Helpers +The Rust driver provides convenience helpers for constructing commonly used read or write concerns (e.g. "majority"). In 2.x, these were an overlapping mix of constants and methods (`ReadConcern::MAJORITY` and `ReadConcern::majority()`). In 3.0, these are only provided as methods. + +## `Send + Sync` Constraints on `Collection` +In 2.x, whether a method on `Collection` required `T: Send` or `T: Send + Sync` was ad-hoc and inconsistent. Whether these constraints were required for compilation depended on details like whether the transitive call graph of the implementation held a value of `T` over an `await` point, which could easily change as development continued. For consistency and to allow for future development, in 3.0 `Collection` requires `T: Send + Sync`. + +## Compression Option Behind Feature Flags +In 2.x, the `ClientOptions::compressor` field was always present, but with no compressor features selected could not be populated. For simplicity and consistency with our other optional features, in 3.0 that field will not be present if no compressor features are enabled. + +## Optional `ReadPreferenceOptions` +In 3.0, the `ReadPreferenceOptions` fields of `ReadPreference` arms are now `Option`. + +## `human_readable_serialization` Option Removed in favor of `HumanReadable` +In some uncommon cases, applications prefer user data types to be serialized in their human-readable form; the `CollectionOptions::human_readable_serialization` option enabled that. In 3.x, this option has been removed; the `bson::HumanReadable` wrapper type can be used to achieve the same effect. + +## Consistent Defaults for `TypedBuilder` Implementations +In 2.x, most but not all option struct builders would allow value conversion via `Into` and would use the `default()` value if not set; this is now the behavior across all option struct builders. + +## `comment_bson` is now `comment` +In 2.x, the `AggregateOptions`, `FindOptions`, and `FindOneOptions` structs had both `comment_bson` and legacy `comment` fields. In 3.x, `comment_bson` has been renamed to `comment`, replacing the legacy field. + +## `bson-*` features removed +The 2.x driver provided features like `bson-chrono-0_4` that did not add any additional driver functionality but would enable the corresponding feature of the `bson` dependency. These have been removed from 3.x; if your project needs specific `bson` features, you should list it as a top-level dependency with those features enabled. \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 38aa12156..9a0e68abc 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,9 +1,8 @@ -edition = "2018" +edition = "2021" combine_control_expr = false comment_width = 100 condense_wildcard_suffixes = true format_strings = true -normalize_comments = true use_try_shorthand = true wrap_comments = true imports_layout = "HorizontalVertical" diff --git a/sbom.json b/sbom.json new file mode 100644 index 000000000..6f4df5d7c --- /dev/null +++ b/sbom.json @@ -0,0 +1,10 @@ +{ + "serialNumber": "urn:uuid:32a42d94-3729-4ef1-8c0c-8c6f9f9e0f53", + "version": 1, + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "metadata": { + "timestamp": "2024-05-01T15:43:13Z" + } +} \ No newline at end of file diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 000000000..91ebc6fd4 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,135 @@ +//! Action builder types. + +mod aggregate; +mod bulk_write; +mod client_options; +mod count; +mod create_collection; +mod create_index; +#[cfg(feature = "in-use-encryption")] +pub mod csfle; +mod delete; +mod distinct; +mod drop; +mod drop_index; +mod find; +mod find_and_modify; +pub mod gridfs; +mod insert_many; +mod insert_one; +mod list_collections; +mod list_databases; +mod list_indexes; +mod perf; +mod replace_one; +mod run_command; +mod search_index; +mod session; +mod shutdown; +pub(crate) mod transaction; +mod update; +mod watch; + +use std::{future::IntoFuture, marker::PhantomData, ops::Deref}; + +use crate::bson::Document; + +pub use aggregate::Aggregate; +pub use bulk_write::BulkWrite; +pub use client_options::ParseConnectionString; +pub use count::{CountDocuments, EstimatedDocumentCount}; +pub use create_collection::CreateCollection; +pub use create_index::CreateIndex; +pub use delete::Delete; +pub use distinct::Distinct; +pub use drop::{DropCollection, DropDatabase}; +pub use drop_index::DropIndex; +pub use find::{Find, FindOne}; +pub use find_and_modify::{FindOneAndDelete, FindOneAndReplace, FindOneAndUpdate}; +pub use insert_many::InsertMany; +pub use insert_one::InsertOne; +pub use list_collections::ListCollections; +pub use list_databases::ListDatabases; +pub use list_indexes::ListIndexes; +pub use perf::WarmConnectionPool; +pub use replace_one::ReplaceOne; +pub use run_command::{RunCommand, RunCursorCommand}; +pub use search_index::{CreateSearchIndex, DropSearchIndex, ListSearchIndexes, UpdateSearchIndex}; +pub use session::StartSession; +pub use shutdown::Shutdown; +pub use transaction::{AbortTransaction, CommitTransaction, StartTransaction}; +pub use update::Update; +pub use watch::Watch; + +#[allow(missing_docs)] +pub struct ListSpecifications; +#[allow(missing_docs)] +pub struct ListNames; + +#[allow(missing_docs)] +pub struct ImplicitSession; +#[allow(missing_docs)] +pub struct ExplicitSession<'a>(&'a mut crate::ClientSession); + +#[allow(missing_docs)] +pub struct Single; +#[allow(missing_docs)] +pub struct Multiple; + +use mongodb_internal_macros::{export_doc, option_setters, options_doc}; + +pub(crate) mod private { + pub trait Sealed {} +} + +/// A pending action to execute on the server. The action can be configured via chained methods and +/// executed via `await` (or `run` if using the sync client). +pub trait Action: private::Sealed + IntoFuture { + /// If the value is `Some`, call the provided function on `self`. Convenient for chained + /// updates with values that need to be set conditionally. For example: + /// ```rust + /// # use mongodb::{Client, error::Result, bson::Document}; + /// use mongodb::action::Action; + /// async fn list_my_collections(client: &Client, filter: Option) -> Result> { + /// client.database("my_db") + /// .list_collection_names() + /// .optional(filter, |a, f| a.filter(f)) + /// .await + /// } + /// ``` + fn optional(self, value: Option, f: impl FnOnce(Self, Value) -> Self) -> Self + where + Self: Sized, + { + match value { + Some(value) => f(self, value), + None => self, + } + } +} + +pub(crate) use mongodb_internal_macros::{action_impl, deeplink}; + +use crate::Collection; + +pub(crate) struct CollRef<'a> { + inner: Collection, + _ref: PhantomData<&'a ()>, +} + +impl<'a> CollRef<'a> { + fn new(coll: &'a Collection) -> Self { + Self { + inner: coll.clone_with_type(), + _ref: PhantomData, + } + } +} + +impl Deref for CollRef<'_> { + type Target = Collection; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} diff --git a/src/action/aggregate.rs b/src/action/aggregate.rs new file mode 100644 index 000000000..deca70703 --- /dev/null +++ b/src/action/aggregate.rs @@ -0,0 +1,284 @@ +use std::{marker::PhantomData, time::Duration}; + +use crate::bson::{Bson, Document}; + +use crate::{ + coll::options::{AggregateOptions, Hint}, + collation::Collation, + error::Result, + operation::aggregate::AggregateTarget, + options::{ReadConcern, WriteConcern}, + selection_criteria::SelectionCriteria, + Client, + ClientSession, + Collection, + Cursor, + Database, + SessionCursor, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + CollRef, + ExplicitSession, + ImplicitSession, +}; + +impl Database { + /// Runs an aggregation operation. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more + /// information on aggregations. + /// + /// `await` will return d[`Result>`]. If a [`ClientSession`] was provided, the + /// returned cursor will be a [`SessionCursor`]. If [`with_type`](Aggregate::with_type) was + /// called, the returned cursor will be generic over the `T` specified. + #[deeplink] + #[options_doc(aggregate)] + pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { + Aggregate { + target: AggregateTargetRef::Database(self), + pipeline: pipeline.into_iter().collect(), + options: None, + session: ImplicitSession, + _phantom: PhantomData, + } + } +} + +impl Collection +where + T: Send + Sync, +{ + /// Runs an aggregation operation. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more + /// information on aggregations. + /// + /// `await` will return d[`Result>`]. If a [`ClientSession`] was provided, the + /// returned cursor will be a [`SessionCursor`]. If [`with_type`](Aggregate::with_type) was + /// called, the returned cursor will be generic over the `T` specified. + #[deeplink] + #[options_doc(aggregate)] + pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { + Aggregate { + target: AggregateTargetRef::Collection(CollRef::new(self)), + pipeline: pipeline.into_iter().collect(), + options: None, + session: ImplicitSession, + _phantom: PhantomData, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Runs an aggregation operation. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more + /// information on aggregations. + /// + /// [`run`](Aggregate::run) will return d[Result>`]. If a + /// [`crate::sync::ClientSession`] was provided, the returned cursor will be a + /// [`crate::sync::SessionCursor`]. If [`with_type`](Aggregate::with_type) was called, the + /// returned cursor will be generic over the `T` specified. + #[deeplink] + #[options_doc(aggregate, sync)] + pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { + self.async_database.aggregate(pipeline) + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Runs an aggregation operation. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more + /// information on aggregations. + /// + /// [`run`](Aggregate::run) will return d[Result>`]. If a + /// `crate::sync::ClientSession` was provided, the returned cursor will be a + /// `crate::sync::SessionCursor`. If [`with_type`](Aggregate::with_type) was called, the + /// returned cursor will be generic over the `T` specified. + #[deeplink] + #[options_doc(aggregate, sync)] + pub fn aggregate(&self, pipeline: impl IntoIterator) -> Aggregate { + self.async_collection.aggregate(pipeline) + } +} + +/// Run an aggregation operation. Construct with [`Database::aggregate`] or +/// [`Collection::aggregate`]. +#[must_use] +pub struct Aggregate<'a, Session = ImplicitSession, T = Document> { + target: AggregateTargetRef<'a>, + pipeline: Vec, + options: Option, + session: Session, + _phantom: PhantomData, +} + +#[option_setters(crate::coll::options::AggregateOptions)] +#[export_doc(aggregate, extra = [session])] +impl<'a, Session, T> Aggregate<'a, Session, T> { + /// Use the provided type for the returned cursor. + /// + /// ```rust + /// # use futures_util::TryStreamExt; + /// # use mongodb::{bson::Document, error::Result, Cursor, Database}; + /// # use serde::Deserialize; + /// # async fn run() -> Result<()> { + /// # let database: Database = todo!(); + /// # let pipeline: Vec = todo!(); + /// #[derive(Deserialize)] + /// struct PipelineOutput { + /// len: usize, + /// } + /// + /// let aggregate_cursor = database + /// .aggregate(pipeline) + /// .with_type::() + /// .await?; + /// let aggregate_results: Vec = aggregate_cursor.try_collect().await?; + /// # Ok(()) + /// # } + /// ``` + pub fn with_type(self) -> Aggregate<'a, Session, U> { + Aggregate { + target: self.target, + pipeline: self.pipeline, + options: self.options, + session: self.session, + _phantom: PhantomData, + } + } +} + +impl<'a, T> Aggregate<'a, ImplicitSession, T> { + /// Use the provided session when running the operation. + pub fn session( + self, + value: impl Into<&'a mut ClientSession>, + ) -> Aggregate<'a, ExplicitSession<'a>, T> { + Aggregate { + target: self.target, + pipeline: self.pipeline, + options: self.options, + session: ExplicitSession(value.into()), + _phantom: PhantomData, + } + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a, T> Action for Aggregate<'a, ImplicitSession, T> { + type Future = AggregateFuture; + + async fn execute(mut self) -> Result> { + resolve_options!( + self.target, + self.options, + [read_concern, write_concern, selection_criteria] + ); + + let aggregate = crate::operation::aggregate::Aggregate::new( + self.target.target(), + self.pipeline, + self.options, + ); + let client = self.target.client(); + client.execute_cursor_operation(aggregate).await + } +} + +#[action_impl(sync = crate::sync::SessionCursor)] +impl<'a, T> Action for Aggregate<'a, ExplicitSession<'a>, T> { + type Future = AggregateSessionFuture; + + async fn execute(mut self) -> Result> { + resolve_read_concern_with_session!(self.target, self.options, Some(&mut *self.session.0))?; + resolve_write_concern_with_session!(self.target, self.options, Some(&mut *self.session.0))?; + resolve_selection_criteria_with_session!( + self.target, + self.options, + Some(&mut *self.session.0) + )?; + + let aggregate = crate::operation::aggregate::Aggregate::new( + self.target.target(), + self.pipeline, + self.options, + ); + let client = self.target.client(); + let session = self.session; + client + .execute_session_cursor_operation(aggregate, session.0) + .await + } +} + +enum AggregateTargetRef<'a> { + Database(&'a Database), + Collection(CollRef<'a>), +} + +impl AggregateTargetRef<'_> { + fn target(&self) -> AggregateTarget { + match self { + Self::Collection(cr) => AggregateTarget::Collection(cr.namespace()), + Self::Database(db) => AggregateTarget::Database(db.name().to_string()), + } + } + + fn client(&self) -> &Client { + match self { + Self::Collection(cr) => cr.client(), + Self::Database(db) => db.client(), + } + } + + fn read_concern(&self) -> Option<&ReadConcern> { + match self { + Self::Collection(cr) => cr.read_concern(), + Self::Database(db) => db.read_concern(), + } + } + + fn write_concern(&self) -> Option<&WriteConcern> { + match self { + Self::Collection(cr) => cr.write_concern(), + Self::Database(db) => db.write_concern(), + } + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + match self { + Self::Collection(cr) => cr.selection_criteria(), + Self::Database(db) => db.selection_criteria(), + } + } +} + +#[test] +fn aggregate_session_type() { + // Assert that this code compiles but do not actually run it. + #[allow( + unreachable_code, + unused_variables, + dead_code, + clippy::diverging_sub_expression + )] + fn compile_ok() { + let agg: Aggregate = todo!(); + let typed: Aggregate<'_, _, ()> = agg.with_type::<()>(); + let mut session: ClientSession = todo!(); + let typed_session: Aggregate<'_, _, ()> = typed.session(&mut session); + } +} diff --git a/src/action/bulk_write.rs b/src/action/bulk_write.rs new file mode 100644 index 000000000..49ed5a665 --- /dev/null +++ b/src/action/bulk_write.rs @@ -0,0 +1,301 @@ +use std::{collections::HashMap, marker::PhantomData}; + +use crate::{ + bson::{Bson, Document}, + error::{BulkWriteError, Error, ErrorKind, Result}, + operation::bulk_write::BulkWrite as BulkWriteOperation, + options::{BulkWriteOptions, WriteConcern, WriteModel}, + results::{BulkWriteResult, SummaryBulkWriteResult, VerboseBulkWriteResult}, + Client, + ClientSession, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc}; + +impl Client { + /// Executes the provided list of write operations. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`] or + /// d[`Result`] if [`verbose_results`](BulkWrite::verbose_results) + /// is configured. + /// + /// Bulk write is only available on MongoDB 8.0+. + #[deeplink] + #[options_doc(bulk_write)] + pub fn bulk_write( + &self, + models: impl IntoIterator>, + ) -> BulkWrite { + let mut models_vec = Vec::new(); + for model in models.into_iter() { + models_vec.push(model.into()); + } + BulkWrite::new(self, models_vec) + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Client { + /// Executes the provided list of write operations. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](BulkWrite::run) will return d[`Result>, + ) -> BulkWrite { + self.async_client.bulk_write(models) + } +} + +/// Performs multiple write operations. Construct with [`Client::bulk_write`]. +#[must_use] +pub struct BulkWrite<'a, R> { + client: &'a Client, + models: Vec, + options: Option, + session: Option<&'a mut ClientSession>, + _phantom: PhantomData, +} + +impl<'a> BulkWrite<'a, SummaryBulkWriteResult> { + /// Return a [`VerboseBulkWriteResult`] with individual results for each successfully performed + /// write. + pub fn verbose_results(self) -> BulkWrite<'a, VerboseBulkWriteResult> { + BulkWrite { + client: self.client, + models: self.models, + options: self.options, + session: self.session, + _phantom: PhantomData, + } + } +} + +#[option_setters(crate::client::options::BulkWriteOptions)] +#[export_doc(bulk_write, extra = [verbose_results])] +impl<'a, R> BulkWrite<'a, R> +where + R: BulkWriteResult, +{ + /// Use the provided session when running the operation. + pub fn session(mut self, session: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(session.into()); + self + } + + fn new(client: &'a Client, models: Vec) -> Self { + Self { + client, + models, + options: None, + session: None, + _phantom: PhantomData, + } + } + + fn is_ordered(&self) -> bool { + self.options + .as_ref() + .and_then(|options| options.ordered) + .unwrap_or(true) + } + + async fn execute_inner(mut self) -> Result { + #[cfg(feature = "in-use-encryption")] + if self.client.should_auto_encrypt().await { + use mongocrypt::error::{Error as EncryptionError, ErrorKind as EncryptionErrorKind}; + + let error = EncryptionError { + kind: EncryptionErrorKind::Client, + code: None, + message: Some( + "bulkWrite does not currently support automatic encryption".to_string(), + ), + }; + return Err(ErrorKind::Encryption(error).into()); + } + + resolve_write_concern_with_session!( + self.client, + self.options, + self.session.as_deref_mut() + )?; + + let mut total_attempted = 0; + let mut execution_status = ExecutionStatus::None; + + while total_attempted < self.models.len() + && execution_status.should_continue(self.is_ordered()) + { + let mut operation = BulkWriteOperation::new( + self.client.clone(), + &self.models[total_attempted..], + total_attempted, + self.options.as_ref(), + ); + let result = self + .client + .execute_operation::>( + &mut operation, + self.session.as_deref_mut(), + ) + .await; + total_attempted += operation.n_attempted; + + match result { + Ok(result) => { + execution_status = execution_status.with_success(result); + } + Err(error) => { + execution_status = execution_status.with_failure(error); + } + } + } + + match execution_status { + ExecutionStatus::Success(bulk_write_result) => Ok(bulk_write_result), + ExecutionStatus::Error(error) => Err(error), + ExecutionStatus::None => Err(ErrorKind::InvalidArgument { + message: "bulk_write must be provided at least one write operation".into(), + } + .into()), + } + } +} + +#[action_impl] +impl<'a> Action for BulkWrite<'a, SummaryBulkWriteResult> { + type Future = SummaryBulkWriteFuture; + + async fn execute(mut self) -> Result { + self.execute_inner().await + } +} + +#[action_impl] +impl<'a> Action for BulkWrite<'a, VerboseBulkWriteResult> { + type Future = VerboseBulkWriteFuture; + + async fn execute(mut self) -> Result { + self.execute_inner().await + } +} + +/// Represents the execution status of a bulk write. The status starts at `None`, indicating that no +/// writes have been attempted yet, and transitions to either `Success` or `Error` as batches are +/// executed. The contents of `Error` can be inspected to determine whether a bulk write can +/// continue with further batches or should be terminated. +enum ExecutionStatus +where + R: BulkWriteResult, +{ + Success(R), + Error(Error), + None, +} + +impl ExecutionStatus +where + R: BulkWriteResult, +{ + fn with_success(mut self, result: R) -> Self { + match self { + // Merge two successful sets of results together. + Self::Success(ref mut current_result) => { + current_result.merge(result); + self + } + // Merge the results of the new batch into the existing bulk write error. + Self::Error(ref mut current_error) => { + let bulk_write_error = Self::get_current_bulk_write_error(current_error); + bulk_write_error.merge_partial_results(result.into_partial_result()); + self + } + Self::None => Self::Success(result), + } + } + + fn with_failure(self, mut error: Error) -> Self { + match self { + // If the new error is a BulkWriteError, merge the successful results into the error's + // partial result. Otherwise, create a new BulkWriteError with the existing results and + // set its source as the error that just occurred. + Self::Success(current_result) => match *error.kind { + ErrorKind::BulkWrite(ref mut bulk_write_error) => { + bulk_write_error.merge_partial_results(current_result.into_partial_result()); + Self::Error(error) + } + _ => { + let bulk_write_error: Error = ErrorKind::BulkWrite(BulkWriteError { + write_errors: HashMap::new(), + write_concern_errors: Vec::new(), + partial_result: Some(current_result.into_partial_result()), + }) + .into(); + Self::Error(bulk_write_error.with_source(error)) + } + }, + // If the new error is a BulkWriteError, merge its contents with the existing error. + // Otherwise, set the new error as the existing error's source. + Self::Error(mut current_error) => match *error.kind { + ErrorKind::BulkWrite(bulk_write_error) => { + let current_bulk_write_error = + Self::get_current_bulk_write_error(&mut current_error); + current_bulk_write_error.merge(bulk_write_error); + Self::Error(current_error) + } + _ => Self::Error(current_error.with_source(error)), + }, + Self::None => Self::Error(error), + } + } + + /// Gets a BulkWriteError from a given Error. This method should only be called when adding a + /// new result or error to the existing state, as it requires that the given Error's kind is + /// ClientBulkWrite. + fn get_current_bulk_write_error(error: &mut Error) -> &mut BulkWriteError { + match *error.kind { + ErrorKind::BulkWrite(ref mut bulk_write_error) => bulk_write_error, + _ => unreachable!(), + } + } + + /// Whether further bulk write batches should be executed based on the current status of + /// execution. + fn should_continue(&self, ordered: bool) -> bool { + match self { + Self::Error(ref error) => { + match *error.kind { + ErrorKind::BulkWrite(ref bulk_write_error) => { + // A top-level error is always fatal. + let top_level_error_occurred = error.source.is_some(); + // A write error occurring during an ordered bulk write is fatal. + let terminal_write_error_occurred = + ordered && !bulk_write_error.write_errors.is_empty(); + + !top_level_error_occurred && !terminal_write_error_occurred + } + // A top-level error is always fatal. + _ => false, + } + } + _ => true, + } + } +} diff --git a/src/action/client_options.rs b/src/action/client_options.rs new file mode 100644 index 000000000..a3234edc6 --- /dev/null +++ b/src/action/client_options.rs @@ -0,0 +1,112 @@ +use macro_magic::export_tokens; + +use crate::{ + client::options::{ClientOptions, ConnectionString, ResolverConfig}, + error::{Error, Result}, +}; + +use super::options_doc; + +impl ClientOptions { + /// Parses a MongoDB connection string (as either a `&str` or a [`ConnectionString`]) into a + /// [`ClientOptions`] struct. If the string is malformed or one of the options has an + /// invalid value, an error will be returned. + /// + /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done as + /// part of this method. + /// + /// The format of a MongoDB connection string is described [here](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats). + /// + /// Note that [default_database](ClientOptions::default_database) will be set from + /// `/defaultauthdb` in connection string. + /// + /// The following options are supported in the options query string: + /// + /// * `appName`: maps to the `app_name` field + /// * `authMechanism`: maps to the `mechanism` field of the `credential` field + /// * `authSource`: maps to the `source` field of the `credential` field + /// * `authMechanismProperties`: maps to the `mechanism_properties` field of the `credential` + /// field + /// * `compressors`: maps to the `compressors` field + /// * `connectTimeoutMS`: maps to the `connect_timeout` field + /// * `direct`: maps to the `direct` field + /// * `heartbeatFrequencyMS`: maps to the `heartbeat_frequency` field + /// * `journal`: maps to the `journal` field of the `write_concern` field + /// * `localThresholdMS`: maps to the `local_threshold` field + /// * `maxIdleTimeMS`: maps to the `max_idle_time` field + /// * `maxStalenessSeconds`: maps to the `max_staleness` field of the `selection_criteria` + /// field + /// * `maxPoolSize`: maps to the `max_pool_size` field + /// * `minPoolSize`: maps to the `min_pool_size` field + /// * `readConcernLevel`: maps to the `read_concern` field + /// * `readPreferenceField`: maps to the ReadPreference enum variant of the + /// `selection_criteria` field + /// * `readPreferenceTags`: maps to the `tags` field of the `selection_criteria` field. Note + /// that this option can appear more than once; each instance will be mapped to a separate + /// tag set + /// * `replicaSet`: maps to the `repl_set_name` field + /// * `retryWrites`: not yet implemented + /// * `retryReads`: maps to the `retry_reads` field + /// * `serverSelectionTimeoutMS`: maps to the `server_selection_timeout` field + /// * `socketTimeoutMS`: unsupported, does not map to any field + /// * `ssl`: an alias of the `tls` option + /// * `tls`: maps to the TLS variant of the `tls` field`. + /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; currently is just + /// an alias of `tlsAllowInvalidCertificates`, but more behavior may be added to this option + /// in the future + /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalidCertificates` field of the + /// `tls` field + /// * `tlsCAFile`: maps to the `ca_file_path` field of the `tls` field + /// * `tlsCertificateKeyFile`: maps to the `cert_key_file_path` field of the `tls` field + /// * `w`: maps to the `w` field of the `write_concern` field + /// * `waitQueueTimeoutMS`: unsupported, does not map to any field + /// * `wTimeoutMS`: maps to the `w_timeout` field of the `write_concern` field + /// * `zlibCompressionLevel`: maps to the `level` field of the `Compressor::Zlib` variant + /// (which requires the `zlib-compression` feature flag) of the + /// [`Compressor`](crate::compression::compressors::Compressor) enum + /// + /// `await` will return `Result`. + #[options_doc(parse_conn_str_setters)] + pub fn parse(conn_str: C) -> ParseConnectionString + where + C: TryInto, + E: Into, + { + ParseConnectionString { + conn_str: conn_str.try_into().map_err(Into::into), + resolver_config: None, + } + } +} + +fn _assert_accept_str() { + let _ = ClientOptions::parse("foo"); +} + +#[allow(unreachable_code, clippy::diverging_sub_expression)] +fn _assert_accept_conn_str() { + let _c: ConnectionString = todo!(); + let _ = ClientOptions::parse(_c); +} + +/// Parses a MongoDB connection string into a [`ClientOptions`] struct. Construct with +/// [`ClientOptions::parse`]. +#[must_use] +pub struct ParseConnectionString { + pub(crate) conn_str: Result, + pub(crate) resolver_config: Option, +} + +#[export_tokens(parse_conn_str_setters)] +impl ParseConnectionString { + /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the + /// provided `ResolverConfig` as part of this method. In the case that "GSSAPI" auth is used, + /// hostname canonicalization will be done using the provided `ResolverConfig`. + #[cfg(feature = "dns-resolver")] + pub fn resolver_config(mut self, value: ResolverConfig) -> Self { + self.resolver_config = Some(value); + self + } +} + +// Action impl in src/client/options/parse.rs. diff --git a/src/action/count.rs b/src/action/count.rs new file mode 100644 index 000000000..c99aeb26a --- /dev/null +++ b/src/action/count.rs @@ -0,0 +1,154 @@ +use crate::bson::{Bson, Document}; +use std::time::Duration; + +use crate::{ + coll::options::{CountOptions, EstimatedDocumentCountOptions, Hint}, + collation::Collation, + concern::ReadConcern, + error::Result, + selection_criteria::SelectionCriteria, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection +where + T: Send + Sync, +{ + /// Estimates the number of documents in the collection using collection metadata. + /// + /// Due to an oversight in versions 5.0.0 - 5.0.7 of MongoDB, the `count` server command, + /// which `estimatedDocumentCount` uses in its implementation, was not included in v1 of the + /// Stable API. Users of the Stable API with `estimatedDocumentCount` are recommended to + /// upgrade their cluster to 5.0.8+ or set + /// [`ServerApi::strict`](crate::options::ServerApi::strict) to false to avoid encountering + /// errors. + /// + /// For more information on the behavior of the `count` server command, see + /// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior). + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(estimated_doc_count)] + pub fn estimated_document_count(&self) -> EstimatedDocumentCount { + EstimatedDocumentCount { + cr: CollRef::new(self), + options: None, + } + } + + /// Gets the number of documents. This method returns an accurate count. + /// + /// Certain query operators cannot be used in the filter provided to this method, including + /// `$where` and `$near`. See the documentation for the `$match` aggregation pipeline stage for + /// full details on these restrictions: + /// https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#restrictions + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(count_docs)] + pub fn count_documents(&self, filter: Document) -> CountDocuments { + CountDocuments { + cr: CollRef::new(self), + filter, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Estimates the number of documents in the collection using collection metadata. + /// + /// Due to an oversight in versions 5.0.0 - 5.0.7 of MongoDB, the `count` server command, + /// which `estimatedDocumentCount` uses in its implementation, was not included in v1 of the + /// Stable API. Users of the Stable API with `estimatedDocumentCount` are recommended to + /// upgrade their cluster to 5.0.8+ or set + /// [`ServerApi::strict`](crate::options::ServerApi::strict) to false to avoid encountering + /// errors. + /// + /// For more information on the behavior of the `count` server command, see + /// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior). + /// + /// [`run`](EstimatedDocumentCount::run) will return d[`Result`]. + #[deeplink] + #[options_doc(estimated_doc_count, sync)] + pub fn estimated_document_count(&self) -> EstimatedDocumentCount { + self.async_collection.estimated_document_count() + } + + /// Gets the number of documents. + /// + /// Note that this method returns an accurate count. + /// + /// [`run`](CountDocuments::run) will return d[`Result`]. + #[deeplink] + #[options_doc(count_docs, sync)] + pub fn count_documents(&self, filter: Document) -> CountDocuments { + self.async_collection.count_documents(filter) + } +} + +/// Gather an estimated document count. Construct with [`Collection::estimated_document_count`]. +#[must_use] +pub struct EstimatedDocumentCount<'a> { + cr: CollRef<'a>, + options: Option, +} + +#[option_setters(crate::coll::options::EstimatedDocumentCountOptions)] +#[export_doc(estimated_doc_count)] +impl EstimatedDocumentCount<'_> {} + +#[action_impl] +impl<'a> Action for EstimatedDocumentCount<'a> { + type Future = EstimatedDocumentCountFuture; + + async fn execute(mut self) -> Result { + resolve_options!(self.cr, self.options, [read_concern, selection_criteria]); + let op = crate::operation::count::Count::new(self.cr.namespace(), self.options); + self.cr.client().execute_operation(op, None).await + } +} + +/// Get an accurate count of documents. Construct with [`Collection::count_documents`]. +#[must_use] +pub struct CountDocuments<'a> { + cr: CollRef<'a>, + filter: Document, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::CountOptions)] +#[export_doc(count_docs)] +impl<'a> CountDocuments<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for CountDocuments<'a> { + type Future = CountDocumentsFuture; + + async fn execute(mut self) -> Result { + resolve_read_concern_with_session!(self.cr, self.options, self.session.as_ref())?; + resolve_selection_criteria_with_session!(self.cr, self.options, self.session.as_ref())?; + + let op = crate::operation::count_documents::CountDocuments::new( + self.cr.namespace(), + self.filter, + self.options, + )?; + self.cr.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/create_collection.rs b/src/action/create_collection.rs new file mode 100644 index 000000000..2aa1cec55 --- /dev/null +++ b/src/action/create_collection.rs @@ -0,0 +1,75 @@ +use crate::bson::{Bson, Document}; +use std::time::Duration; + +use crate::{ + collation::Collation, + concern::WriteConcern, + db::options::{ + ChangeStreamPreAndPostImages, + ClusteredIndex, + IndexOptionDefaults, + TimeseriesOptions, + ValidationAction, + ValidationLevel, + }, + options::CreateCollectionOptions, + ClientSession, + Database, +}; + +use super::{deeplink, export_doc, option_setters, options_doc}; + +impl Database { + /// Creates a new collection in the database with the given `name`. + /// + /// Note that MongoDB creates collections implicitly when data is inserted, so this method is + /// not needed if no special options are required. + /// + /// `await` will return d[`Result<()>`]. + #[deeplink] + #[options_doc(create_coll)] + pub fn create_collection(&self, name: impl Into) -> CreateCollection { + CreateCollection { + db: self, + name: name.into(), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Creates a new collection in the database with the given `name`. + /// + /// Note that MongoDB creates collections implicitly when data is inserted, so this method is + /// not needed if no special options are required. + /// + /// [`run`](CreateCollection::run) will return d[`Result<()>`]. + #[deeplink] + #[options_doc(create_coll, sync)] + pub fn create_collection(&self, name: impl Into) -> CreateCollection { + self.async_database.create_collection(name) + } +} + +/// Creates a new collection. Construct with [`Database::create_collection`]. +#[must_use] +pub struct CreateCollection<'a> { + pub(crate) db: &'a Database, + pub(crate) name: String, + pub(crate) options: Option, + pub(crate) session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::db::options::CreateCollectionOptions)] +#[export_doc(create_coll)] +impl<'a> CreateCollection<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +// Action impl in src/db/action/create_collection.rs diff --git a/src/action/create_index.rs b/src/action/create_index.rs new file mode 100644 index 000000000..75dc2ba8d --- /dev/null +++ b/src/action/create_index.rs @@ -0,0 +1,140 @@ +use std::{marker::PhantomData, time::Duration}; + +use crate::bson::Bson; + +use crate::{ + coll::options::{CommitQuorum, CreateIndexOptions}, + error::Result, + operation::CreateIndexes as Op, + options::WriteConcern, + results::{CreateIndexResult, CreateIndexesResult}, + ClientSession, + Collection, + IndexModel, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + CollRef, + Multiple, + Single, +}; + +impl Collection +where + T: Send + Sync, +{ + /// Creates the given index on this collection. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(create_index)] + pub fn create_index(&self, index: IndexModel) -> CreateIndex { + CreateIndex { + coll: CollRef::new(self), + indexes: vec![index], + options: None, + session: None, + _mode: PhantomData, + } + } + + /// Creates the given indexes on this collection. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(create_index)] + pub fn create_indexes( + &self, + indexes: impl IntoIterator, + ) -> CreateIndex<'_, Multiple> { + CreateIndex { + coll: CollRef::new(self), + indexes: indexes.into_iter().collect(), + options: None, + session: None, + _mode: PhantomData, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Creates the given index on this collection. + /// + /// [`run`](CreateIndex::run) will return d[`Result`]. + #[deeplink] + #[options_doc(create_index, sync)] + pub fn create_index(&self, index: IndexModel) -> CreateIndex { + self.async_collection.create_index(index) + } + + /// Creates the given indexes on this collection. + /// + /// [`run`](CreateIndex::run) will return d[`Result`]. + #[deeplink] + #[options_doc(create_index, sync)] + pub fn create_indexes( + &self, + indexes: impl IntoIterator, + ) -> CreateIndex<'_, Multiple> { + self.async_collection.create_indexes(indexes) + } +} + +/// Perform creation of an index or indexes. Construct by calling [`Collection::create_index`] or +/// [`Collection::create_indexes`]. +#[must_use] +pub struct CreateIndex<'a, M = Single> { + coll: CollRef<'a>, + indexes: Vec, + options: Option, + session: Option<&'a mut ClientSession>, + _mode: PhantomData, +} + +#[option_setters(crate::coll::options::CreateIndexOptions)] +#[export_doc(create_index)] +impl<'a, M> CreateIndex<'a, M> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for CreateIndex<'a, Single> { + type Future = CreateIndexFuture; + + async fn execute(self) -> Result { + let inner: CreateIndex<'a, Multiple> = CreateIndex { + coll: self.coll, + indexes: self.indexes, + options: self.options, + session: self.session, + _mode: PhantomData, + }; + let response = inner.await?; + Ok(response.into_create_index_result()) + } +} + +#[action_impl] +impl<'a> Action for CreateIndex<'a, Multiple> { + type Future = CreateIndexesFuture; + + async fn execute(mut self) -> Result { + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let op = Op::new(self.coll.namespace(), self.indexes, self.options); + self.coll.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/csfle.rs b/src/action/csfle.rs new file mode 100644 index 000000000..25dc387af --- /dev/null +++ b/src/action/csfle.rs @@ -0,0 +1,9 @@ +//! Action builders for in-use encryption. + +mod create_data_key; +mod create_encrypted_collection; +pub(crate) mod encrypt; + +pub use create_data_key::{CreateDataKey, DataKeyOptions}; +pub use create_encrypted_collection::CreateEncryptedCollection; +pub use encrypt::{Encrypt, EncryptOptions}; diff --git a/src/action/csfle/create_data_key.rs b/src/action/csfle/create_data_key.rs new file mode 100644 index 000000000..555fb4c65 --- /dev/null +++ b/src/action/csfle/create_data_key.rs @@ -0,0 +1,60 @@ +use macro_magic::export_tokens; + +use crate::{ + action::{deeplink, export_doc, option_setters, options_doc}, + client_encryption::{ClientEncryption, MasterKey}, +}; + +impl ClientEncryption { + /// Creates a new key document and inserts into the key vault collection. + /// + /// `await` will return d[`Result`] (subtype 0x04) with the _id of the created + /// document as a UUID. + #[deeplink] + #[options_doc(create_data_keys)] + pub fn create_data_key(&self, master_key: impl Into) -> CreateDataKey { + CreateDataKey { + client_enc: self, + master_key: master_key.into(), + options: None, + #[cfg(test)] + test_kms_provider: None, + } + } +} + +/// Create a new key document and insert it into the key vault collection. Construct via +/// [`ClientEncryption::create_data_key`]. +#[must_use] +pub struct CreateDataKey<'a> { + pub(crate) client_enc: &'a ClientEncryption, + pub(crate) master_key: MasterKey, + pub(crate) options: Option, + #[cfg(test)] + pub(crate) test_kms_provider: Option, +} + +/// Options for creating a data key. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +#[export_tokens] +pub struct DataKeyOptions { + /// An optional list of alternate names that can be used to reference the key. + pub key_alt_names: Option>, + /// A buffer of 96 bytes to use as custom key material for the data key being + /// created. If unset, key material for the new data key is generated from a cryptographically + /// secure random device. + pub key_material: Option>, +} + +#[option_setters(DataKeyOptions)] +#[export_doc(create_data_keys)] +impl CreateDataKey<'_> { + #[cfg(test)] + pub(crate) fn test_kms_provider(mut self, value: mongocrypt::ctx::KmsProvider) -> Self { + self.test_kms_provider = Some(value); + self + } +} + +// Action impl in src/client/csfle/client_encryption/create_data_key.rs diff --git a/src/action/csfle/create_encrypted_collection.rs b/src/action/csfle/create_encrypted_collection.rs new file mode 100644 index 000000000..141f210bc --- /dev/null +++ b/src/action/csfle/create_encrypted_collection.rs @@ -0,0 +1,118 @@ +use std::time::Duration; + +use crate::bson::{doc, Bson, Document}; + +use crate::{ + action::{action_impl, export_doc, option_setters, options_doc}, + client_encryption::{ClientEncryption, MasterKey}, + collation::Collation, + concern::WriteConcern, + db::options::{ + ChangeStreamPreAndPostImages, + ClusteredIndex, + CreateCollectionOptions, + IndexOptionDefaults, + TimeseriesOptions, + ValidationAction, + ValidationLevel, + }, + error::{Error, Result}, + Database, +}; + +impl ClientEncryption { + /// Creates a new collection with encrypted fields, automatically creating new data encryption + /// keys when needed based on the configured [`CreateCollectionOptions::encrypted_fields`]. + /// + /// `await` will return `(Document, Result<()>)` containing the potentially updated + /// `encrypted_fields` along with the collection creation status, as keys may have been created + /// even when a failure occurs. + /// + /// Does not affect any auto encryption settings on existing MongoClients that are already + /// configured with auto encryption. + #[options_doc(create_enc_coll)] + pub fn create_encrypted_collection<'a>( + &'a self, + db: &'a Database, + name: &'a str, + master_key: MasterKey, + ) -> CreateEncryptedCollection<'a> { + CreateEncryptedCollection { + client_enc: self, + db, + name, + master_key, + options: None, + } + } +} + +/// Create a new collection with encrypted fields. Construct with +/// [`ClientEncryption::create_encrypted_collection`]. +#[must_use] +pub struct CreateEncryptedCollection<'a> { + client_enc: &'a ClientEncryption, + db: &'a Database, + name: &'a str, + master_key: MasterKey, + options: Option, +} + +#[option_setters(crate::db::options::CreateCollectionOptions)] +#[export_doc(create_enc_coll)] +impl CreateEncryptedCollection<'_> {} + +#[action_impl] +impl<'a> Action for CreateEncryptedCollection<'a> { + type Future = CreateEncryptedCollectionFuture; + + async fn execute(self) -> (Document, Result<()>) { + let ef = match self + .options + .as_ref() + .and_then(|o| o.encrypted_fields.as_ref()) + { + Some(ef) => ef, + None => { + return ( + doc! {}, + Err(Error::invalid_argument( + "no encrypted_fields defined for collection", + )), + ); + } + }; + let mut ef_prime = ef.clone(); + if let Ok(fields) = ef_prime.get_array_mut("fields") { + for f in fields { + let f_doc = if let Some(d) = f.as_document_mut() { + d + } else { + continue; + }; + if f_doc.get("keyId") == Some(&Bson::Null) { + let d = match self + .client_enc + .create_data_key(self.master_key.clone()) + .await + { + Ok(v) => v, + Err(e) => return (ef_prime, Err(e)), + }; + f_doc.insert("keyId", d); + } + } + } + // Unwrap safety: the check for `encrypted_fields` at the top won't succeed if + // `self.options` is `None`. + let mut opts_prime = self.options.unwrap(); + opts_prime.encrypted_fields = Some(ef_prime.clone()); + ( + ef_prime, + self.db + .create_collection(self.name) + .with_options(opts_prime) + .await, + ) + } +} diff --git a/src/action/csfle/encrypt.rs b/src/action/csfle/encrypt.rs new file mode 100644 index 000000000..1e2c99fcd --- /dev/null +++ b/src/action/csfle/encrypt.rs @@ -0,0 +1,166 @@ +use crate::bson::{Binary, Bson, RawDocumentBuf}; +use macro_magic::export_tokens; +use mongocrypt::ctx::Algorithm; +use serde::Serialize; +use serde_with::skip_serializing_none; +use typed_builder::TypedBuilder; + +use crate::{ + action::{deeplink, export_doc, option_setters, options_doc}, + client_encryption::ClientEncryption, +}; + +impl ClientEncryption { + /// Encrypts a BsonValue with a given key and algorithm. + /// + /// To insert or query with an "Indexed" encrypted payload, use a `Client` configured with + /// `AutoEncryptionOptions`. `AutoEncryptionOptions.bypass_query_analysis` may be true. + /// `AutoEncryptionOptions.bypass_auto_encryption` must be false. + /// + /// `await` will return a d[`Result`] (subtype 6) containing the encrypted value. + #[deeplink] + #[options_doc(encrypt)] + pub fn encrypt( + &self, + value: impl Into, + key: impl Into, + algorithm: Algorithm, + ) -> Encrypt { + Encrypt { + client_enc: self, + mode: Value { + value: value.into(), + }, + key: key.into(), + algorithm, + options: None, + } + } + + /// Encrypts a Match Expression or Aggregate Expression to query a range index. + /// `expression` is expected to be a BSON document of one of the following forms: + /// 1. A Match Expression of this form: + /// `{$and: [{: {$gt: }}, {: {$lt: }}]}` + /// 2. An Aggregate Expression of this form: + /// `{$and: [{$gt: [, ]}, {$lt: [, ]}]` + /// + /// For either expression, `$gt` may also be `$gte`, and `$lt` may also be `$lte`. + /// + /// The expression will be encrypted using the [`Algorithm::Range`] algorithm and the + /// "range" query type. It is not valid to set a query type in [`EncryptOptions`] when calling + /// this method. + /// + /// `await` will return a d[`Result`] containing the encrypted expression. + #[deeplink] + #[options_doc(encrypt_expr)] + pub fn encrypt_expression( + &self, + expression: RawDocumentBuf, + key: impl Into, + ) -> Encrypt<'_, Expression> { + Encrypt { + client_enc: self, + mode: Expression { value: expression }, + key: key.into(), + algorithm: Algorithm::Range, + options: None, + } + } +} + +/// An encryption key reference. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum EncryptKey { + /// Find the key by _id value. + Id(Binary), + /// Find the key by alternate name. + AltName(String), +} + +impl From for EncryptKey { + fn from(bin: Binary) -> Self { + Self::Id(bin) + } +} + +impl From for EncryptKey { + fn from(s: String) -> Self { + Self::AltName(s) + } +} + +/// Encrypt a value. Construct with [`ClientEncryption::encrypt`]. +#[must_use] +pub struct Encrypt<'a, Mode = Value> { + pub(crate) client_enc: &'a ClientEncryption, + pub(crate) mode: Mode, + pub(crate) key: EncryptKey, + pub(crate) algorithm: Algorithm, + pub(crate) options: Option, +} + +pub struct Value { + pub(crate) value: crate::bson::RawBson, +} + +pub struct Expression { + pub(crate) value: RawDocumentBuf, +} + +/// Options for encrypting a value. +#[derive(Debug, Clone, Default, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +#[export_tokens] +pub struct EncryptOptions { + /// The contention factor. + pub contention_factor: Option, + + /// The query type. + pub query_type: Option, + + /// Set the range options. This should only be set when the algorithm is + /// [`Algorithm::Range`]. + pub range_options: Option, +} + +/// The index options for a Queryable Encryption field supporting "range" queries. +/// The options set must match the values set in the encryptedFields of the destination collection. +#[skip_serializing_none] +#[derive(Clone, Default, Debug, Serialize, TypedBuilder)] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct RangeOptions { + /// The minimum value. This option must be set if `precision` is set. + pub min: Option, + + /// The maximum value. This option must be set if `precision` is set. + pub max: Option, + + /// May be used to tune performance. When omitted, a default value is used. + pub trim_factor: Option, + + /// May be used to tune performance. When omitted, a default value is used. + pub sparsity: Option, + + /// Determines the number of significant digits after the decimal point. This value must only + /// be set for Double and Decimal128 fields. + pub precision: Option, +} + +#[option_setters(EncryptOptions, skip = [query_type])] +#[export_doc(encrypt, extra = [query_type])] +#[export_doc(encrypt_expr)] +impl Encrypt<'_, Mode> {} + +impl Encrypt<'_, Value> { + /// Set the [`EncryptOptions::query_type`] option. + pub fn query_type(mut self, value: impl Into) -> Self { + self.options().query_type = Some(value.into()); + self + } +} + +// Action impl in src/client/csfle/client_encryption/encrypt.rs diff --git a/src/action/delete.rs b/src/action/delete.rs new file mode 100644 index 000000000..4bf2dd78a --- /dev/null +++ b/src/action/delete.rs @@ -0,0 +1,116 @@ +use crate::bson::{Bson, Document}; + +use crate::{ + coll::options::{DeleteOptions, Hint}, + collation::Collation, + error::Result, + operation::Delete as Op, + options::WriteConcern, + results::DeleteResult, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection +where + T: Send + Sync, +{ + /// Deletes up to one document found matching `query`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(delete)] + pub fn delete_one(&self, query: Document) -> Delete { + Delete { + coll: CollRef::new(self), + query, + options: None, + session: None, + limit: Some(1), + } + } + + /// Deletes all documents stored in the collection matching `query`. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(delete)] + pub fn delete_many(&self, query: Document) -> Delete { + Delete { + coll: CollRef::new(self), + query, + options: None, + session: None, + limit: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Deletes up to one document found matching `query`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](Delete::run) will return d[`Result`]. + #[deeplink] + #[options_doc(delete, sync)] + pub fn delete_one(&self, query: Document) -> Delete { + self.async_collection.delete_one(query) + } + + /// Deletes all documents stored in the collection matching `query`. + /// + /// [`run`](Delete::run) will return d[`Result`]. + #[deeplink] + #[options_doc(delete, sync)] + pub fn delete_many(&self, query: Document) -> Delete { + self.async_collection.delete_many(query) + } +} + +/// Deletes documents matching a query. Construct with [`Collection::delete_one`] or +/// [`Collection::delete_many`]. +#[must_use] +pub struct Delete<'a> { + coll: CollRef<'a>, + query: Document, + options: Option, + session: Option<&'a mut ClientSession>, + limit: Option, +} + +#[option_setters(crate::coll::options::DeleteOptions)] +#[export_doc(delete)] +impl<'a> Delete<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for Delete<'a> { + type Future = DeleteFuture; + + async fn execute(mut self) -> Result { + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let op = Op::new(self.coll.namespace(), self.query, self.limit, self.options); + self.coll.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/distinct.rs b/src/action/distinct.rs new file mode 100644 index 000000000..447238f7e --- /dev/null +++ b/src/action/distinct.rs @@ -0,0 +1,89 @@ +use std::time::Duration; + +use crate::bson::{Bson, Document}; + +use crate::{ + coll::options::{DistinctOptions, Hint}, + collation::Collation, + error::Result, + operation::Distinct as Op, + options::ReadConcern, + selection_criteria::SelectionCriteria, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection +where + T: Send + Sync, +{ + /// Finds the distinct values of the field specified by `field_name` across the collection. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(distinct)] + pub fn distinct(&self, field_name: impl AsRef, filter: Document) -> Distinct { + Distinct { + coll: CollRef::new(self), + field_name: field_name.as_ref().to_string(), + filter, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Finds the distinct values of the field specified by `field_name` across the collection. + /// + /// [`run`](Distinct::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(distinct, sync)] + pub fn distinct(&self, field_name: impl AsRef, filter: Document) -> Distinct { + self.async_collection.distinct(field_name, filter) + } +} + +/// Finds the distinct values of a field. Construct with [`Collection::distinct`]. +#[must_use] +pub struct Distinct<'a> { + coll: CollRef<'a>, + field_name: String, + filter: Document, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::DistinctOptions)] +#[export_doc(distinct)] +impl<'a> Distinct<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for Distinct<'a> { + type Future = DistinctFuture; + + async fn execute(mut self) -> Result> { + resolve_read_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + resolve_selection_criteria_with_session!(self.coll, self.options, self.session.as_ref())?; + + let op = Op::new( + self.coll.namespace(), + self.field_name, + self.filter, + self.options, + ); + self.coll.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/drop.rs b/src/action/drop.rs new file mode 100644 index 000000000..3571a53ed --- /dev/null +++ b/src/action/drop.rs @@ -0,0 +1,126 @@ +#[cfg(feature = "in-use-encryption")] +use crate::bson::Document; + +use crate::{ + coll::options::DropCollectionOptions, + db::options::DropDatabaseOptions, + error::Result, + operation::drop_database, + options::WriteConcern, + ClientSession, + Collection, + Database, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Database { + /// Drops the database, deleting all data, collections, and indexes stored in it. + /// + /// `await` will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_db)] + pub fn drop(&self) -> DropDatabase { + DropDatabase { + db: self, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Drops the database, deleting all data, collections, and indexes stored in it. + /// + /// [`run`](DropDatabase::run) will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_db, sync)] + pub fn drop(&self) -> DropDatabase { + self.async_database.drop() + } +} + +/// Drops the database, deleting all data, collections, and indexes stored in it. Construct with +/// [`Database::drop`]. +#[must_use] +pub struct DropDatabase<'a> { + db: &'a Database, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::db::options::DropDatabaseOptions)] +#[export_doc(drop_db)] +impl<'a> DropDatabase<'a> { + /// Runs the drop using the provided session. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for DropDatabase<'a> { + type Future = DropDatabaseFuture; + + async fn execute(mut self) -> Result<()> { + resolve_options!(self.db, self.options, [write_concern]); + let op = drop_database::DropDatabase::new(self.db.name().to_string(), self.options); + self.db.client().execute_operation(op, self.session).await + } +} + +impl Collection +where + T: Send + Sync, +{ + /// Drops the collection, deleting all data and indexes stored in it. + /// + /// `await` will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_coll)] + pub fn drop(&self) -> DropCollection { + DropCollection { + cr: CollRef::new(self), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Drops the collection, deleting all data and indexes stored in it. + /// + /// [`run`](DropCollection::run) will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_coll, sync)] + pub fn drop(&self) -> DropCollection { + self.async_collection.drop() + } +} + +/// Drops the collection, deleting all data and indexes stored in it. Construct with +/// [`Collection::drop`]. +#[must_use] +pub struct DropCollection<'a> { + pub(crate) cr: CollRef<'a>, + pub(crate) options: Option, + pub(crate) session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::DropCollectionOptions)] +#[export_doc(drop_coll)] +impl<'a> DropCollection<'a> { + /// Runs the drop using the provided session. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +// Action impl in src/coll/action/drop.rs diff --git a/src/action/drop_index.rs b/src/action/drop_index.rs new file mode 100644 index 000000000..e729d3aba --- /dev/null +++ b/src/action/drop_index.rs @@ -0,0 +1,114 @@ +use std::time::Duration; + +use crate::bson::Bson; + +use crate::{ + coll::options::DropIndexOptions, + error::{ErrorKind, Result}, + operation::DropIndexes as Op, + options::WriteConcern, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection +where + T: Send + Sync, +{ + /// Drops the index specified by `name` from this collection. + /// + /// `await` will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_index)] + pub fn drop_index(&self, name: impl AsRef) -> DropIndex { + DropIndex { + coll: CollRef::new(self), + name: Some(name.as_ref().to_string()), + options: None, + session: None, + } + } + + /// Drops all indexes associated with this collection. + /// + /// `await` will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_index)] + pub fn drop_indexes(&self) -> DropIndex { + DropIndex { + coll: CollRef::new(self), + name: None, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Drops the index specified by `name` from this collection. + /// + /// [`run`](DropIndex::run) will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_index, sync)] + pub fn drop_index(&self, name: impl AsRef) -> DropIndex { + self.async_collection.drop_index(name) + } + + /// Drops all indexes associated with this collection. + /// + /// [`run`](DropIndex::run) will return d[`Result<()>`]. + #[deeplink] + #[options_doc(drop_index, sync)] + pub fn drop_indexes(&self) -> DropIndex { + self.async_collection.drop_indexes() + } +} + +/// Drop an index or indexes. Construct with [`Collection::drop_index`] or +/// [`Collection::drop_indexes`]. +#[must_use] +pub struct DropIndex<'a> { + coll: CollRef<'a>, + name: Option, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::DropIndexOptions)] +#[export_doc(drop_index)] +impl<'a> DropIndex<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for DropIndex<'a> { + type Future = DropIndexFuture; + + async fn execute(mut self) -> Result<()> { + if matches!(self.name.as_deref(), Some("*")) { + return Err(ErrorKind::InvalidArgument { + message: "Cannot pass name \"*\" to drop_index since more than one index would be \ + dropped." + .to_string(), + } + .into()); + } + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + // If there is no provided name, that means we should drop all indexes. + let index_name = self.name.unwrap_or_else(|| "*".to_string()); + + let op = Op::new(self.coll.namespace(), index_name, self.options); + self.coll.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/find.rs b/src/action/find.rs new file mode 100644 index 000000000..7730e25e2 --- /dev/null +++ b/src/action/find.rs @@ -0,0 +1,183 @@ +use std::time::Duration; + +use crate::bson::{Bson, Document}; +use serde::de::DeserializeOwned; + +use crate::{ + coll::options::{CursorType, FindOneOptions, FindOptions, Hint}, + collation::Collation, + error::Result, + operation::Find as Op, + options::ReadConcern, + selection_criteria::SelectionCriteria, + ClientSession, + Collection, + Cursor, + SessionCursor, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + ExplicitSession, + ImplicitSession, +}; + +impl Collection { + /// Finds the documents in the collection matching `filter`. + /// + /// `await` will return d[`Result>`] (or d[`Result>`] if a session is + /// provided). + #[deeplink] + #[options_doc(find)] + pub fn find(&self, filter: Document) -> Find<'_, T> { + Find { + coll: self, + filter, + options: None, + session: ImplicitSession, + } + } +} + +impl Collection { + /// Finds a single document in the collection matching `filter`. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one)] + pub fn find_one(&self, filter: Document) -> FindOne<'_, T> { + FindOne { + coll: self, + filter, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Finds the documents in the collection matching `filter`. + /// + /// [`run`](Find::run) will return d[`Result>`] (or + /// d[`Result>`] if a session is provided). + #[deeplink] + #[options_doc(find, sync)] + pub fn find(&self, filter: Document) -> Find<'_, T> { + self.async_collection.find(filter) + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Finds a single document in the collection matching `filter`. + /// + /// [`run`](FindOne::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one, sync)] + pub fn find_one(&self, filter: Document) -> FindOne<'_, T> { + self.async_collection.find_one(filter) + } +} + +/// Finds the documents in a collection matching a filter. Construct with [`Collection::find`]. +#[must_use] +pub struct Find<'a, T: Send + Sync, Session = ImplicitSession> { + coll: &'a Collection, + filter: Document, + options: Option, + session: Session, +} + +#[option_setters(crate::coll::options::FindOptions)] +#[export_doc(find)] +impl<'a, T: Send + Sync, Session> Find<'a, T, Session> { + /// Use the provided session when running the operation. + pub fn session<'s>( + self, + value: impl Into<&'s mut ClientSession>, + ) -> Find<'a, T, ExplicitSession<'s>> { + Find { + coll: self.coll, + filter: self.filter, + options: self.options, + session: ExplicitSession(value.into()), + } + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a, T: Send + Sync> Action for Find<'a, T, ImplicitSession> { + type Future = FindFuture; + + async fn execute(mut self) -> Result> { + resolve_options!(self.coll, self.options, [read_concern, selection_criteria]); + + let find = Op::new(self.coll.namespace(), self.filter, self.options); + self.coll.client().execute_cursor_operation(find).await + } +} + +#[action_impl(sync = crate::sync::SessionCursor)] +impl<'a, T: Send + Sync> Action for Find<'a, T, ExplicitSession<'a>> { + type Future = FindSessionFuture; + + async fn execute(mut self) -> Result> { + resolve_read_concern_with_session!(self.coll, self.options, Some(&mut *self.session.0))?; + resolve_selection_criteria_with_session!( + self.coll, + self.options, + Some(&mut *self.session.0) + )?; + + let find = Op::new(self.coll.namespace(), self.filter, self.options); + self.coll + .client() + .execute_session_cursor_operation(find, self.session.0) + .await + } +} + +/// Finds a single document in a collection matching a filter. Construct with +/// [`Collection::find_one`]. +#[must_use] +pub struct FindOne<'a, T: Send + Sync> { + coll: &'a Collection, + filter: Document, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::FindOneOptions)] +#[export_doc(find_one)] +impl<'a, T: Send + Sync> FindOne<'a, T> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a, T: DeserializeOwned + Send + Sync> Action for FindOne<'a, T> { + type Future = FindOneFuture; + + async fn execute(self) -> Result> { + use futures_util::stream::StreamExt; + let mut options: FindOptions = self.options.unwrap_or_default().into(); + options.limit = Some(-1); + let find = self.coll.find(self.filter).with_options(options); + if let Some(session) = self.session { + let mut cursor = find.session(&mut *session).await?; + let mut stream = cursor.stream(session); + stream.next().await.transpose() + } else { + let mut cursor = find.await?; + cursor.next().await.transpose() + } + } +} diff --git a/src/action/find_and_modify.rs b/src/action/find_and_modify.rs new file mode 100644 index 000000000..72be715c3 --- /dev/null +++ b/src/action/find_and_modify.rs @@ -0,0 +1,287 @@ +use std::{borrow::Borrow, time::Duration}; + +use crate::bson::{Bson, Document, RawDocumentBuf}; +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + coll::options::{ + FindOneAndDeleteOptions, + FindOneAndReplaceOptions, + FindOneAndUpdateOptions, + Hint, + ReturnDocument, + UpdateModifications, + }, + collation::Collation, + error::Result, + operation::{ + find_and_modify::options::{FindAndModifyOptions, Modification}, + FindAndModify as Op, + UpdateOrReplace, + }, + options::WriteConcern, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc}; + +impl Collection { + async fn find_and_modify( + &self, + filter: Document, + modification: Modification, + mut options: Option, + session: Option<&mut ClientSession>, + ) -> Result> { + resolve_write_concern_with_session!(self, options, session.as_ref())?; + + let op = Op::::with_modification(self.namespace(), filter, modification, options)?; + self.client().execute_operation(op, session).await + } + + /// Atomically finds up to one document in the collection matching `filter` and deletes it. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_delete)] + pub fn find_one_and_delete(&self, filter: Document) -> FindOneAndDelete<'_, T> { + FindOneAndDelete { + coll: self, + filter, + options: None, + session: None, + } + } + + /// Atomically finds up to one document in the collection matching `filter` and updates it. + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_update)] + pub fn find_one_and_update( + &self, + filter: Document, + update: impl Into, + ) -> FindOneAndUpdate<'_, T> { + FindOneAndUpdate { + coll: self, + filter, + update: update.into(), + options: None, + session: None, + } + } +} + +impl Collection { + /// Atomically finds up to one document in the collection matching `filter` and replaces it with + /// `replacement`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_replace)] + pub fn find_one_and_replace( + &self, + filter: Document, + replacement: impl Borrow, + ) -> FindOneAndReplace<'_, T> { + FindOneAndReplace { + coll: self, + filter, + replacement: crate::bson_compat::serialize_to_raw_document_buf(replacement.borrow()) + .map_err(Into::into), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Atomically finds up to one document in the collection matching `filter` and deletes it. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](FindOneAndDelete::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_delete, sync)] + pub fn find_one_and_delete(&self, filter: Document) -> FindOneAndDelete<'_, T> { + self.async_collection.find_one_and_delete(filter) + } + + /// Atomically finds up to one document in the collection matching `filter` and updates it. + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](FindOneAndDelete::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_update, sync)] + pub fn find_one_and_update( + &self, + filter: Document, + update: impl Into, + ) -> FindOneAndUpdate<'_, T> { + self.async_collection.find_one_and_update(filter, update) + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Atomically finds up to one document in the collection matching `filter` and replaces it with + /// `replacement`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](FindOneAndReplace::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one_and_replace, sync)] + pub fn find_one_and_replace( + &self, + filter: Document, + replacement: impl Borrow, + ) -> FindOneAndReplace<'_, T> { + self.async_collection + .find_one_and_replace(filter, replacement) + } +} + +/// Atomically finds up to one document in the collection matching a filter and deletes it. +/// Construct with [`Collection::find_one_and_delete`]. +#[must_use] +pub struct FindOneAndDelete<'a, T: Send + Sync> { + coll: &'a Collection, + filter: Document, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::FindOneAndDeleteOptions)] +#[export_doc(find_one_and_delete)] +impl<'a, T: Send + Sync> FindOneAndDelete<'a, T> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a, T: DeserializeOwned + Send + Sync> Action for FindOneAndDelete<'a, T> { + type Future = FindOneAndDeleteFuture; + + async fn execute(self) -> Result> { + self.coll + .find_and_modify( + self.filter, + Modification::Delete, + self.options.map(FindAndModifyOptions::from), + self.session, + ) + .await + } +} + +/// Atomically finds up to one document in the collection matching a filter and updates it. +/// Construct with [`Collection::find_one_and_update`]. +#[must_use] +pub struct FindOneAndUpdate<'a, T: Send + Sync> { + coll: &'a Collection, + filter: Document, + update: UpdateModifications, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::FindOneAndUpdateOptions)] +#[export_doc(find_one_and_update)] +impl<'a, T: Send + Sync> FindOneAndUpdate<'a, T> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a, T: DeserializeOwned + Send + Sync> Action for FindOneAndUpdate<'a, T> { + type Future = FindOneAndUpdateFuture; + + async fn execute(self) -> Result> { + self.coll + .find_and_modify( + self.filter, + Modification::Update(self.update.into()), + self.options.map(FindAndModifyOptions::from), + self.session, + ) + .await + } +} + +/// Atomically finds up to one document in the collection matching a filter and replaces it. +/// Construct with [`Collection::find_one_and_replace`]. +#[must_use] +pub struct FindOneAndReplace<'a, T: Send + Sync> { + coll: &'a Collection, + filter: Document, + replacement: Result, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::FindOneAndReplaceOptions)] +#[export_doc(find_one_and_replace)] +impl<'a, T: Send + Sync> FindOneAndReplace<'a, T> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a, T: DeserializeOwned + Send + Sync> Action for FindOneAndReplace<'a, T> { + type Future = FindOneAndReplaceFuture; + + async fn execute(self) -> Result> { + self.coll + .find_and_modify( + self.filter, + Modification::Update(UpdateOrReplace::Replacement(self.replacement?)), + self.options.map(FindAndModifyOptions::from), + self.session, + ) + .await + } +} diff --git a/src/action/gridfs.rs b/src/action/gridfs.rs new file mode 100644 index 000000000..c4c3e12a4 --- /dev/null +++ b/src/action/gridfs.rs @@ -0,0 +1,15 @@ +//! Action builders for gridfs. + +mod delete; +mod download; +mod drop; +mod find; +mod rename; +mod upload; + +pub use delete::{Delete, DeleteByName}; +pub use download::{OpenDownloadStream, OpenDownloadStreamByName}; +pub use drop::Drop; +pub use find::{Find, FindOne}; +pub use rename::{Rename, RenameByName}; +pub use upload::OpenUploadStream; diff --git a/src/action/gridfs/delete.rs b/src/action/gridfs/delete.rs new file mode 100644 index 000000000..99c0534fd --- /dev/null +++ b/src/action/gridfs/delete.rs @@ -0,0 +1,141 @@ +use crate::bson::{doc, Bson}; + +#[cfg(docsrs)] +use crate::gridfs::FilesCollectionDocument; +use crate::{ + action::action_impl, + error::{ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result}, + gridfs::GridFsBucket, +}; + +impl GridFsBucket { + /// Deletes the [`FilesCollectionDocument`] with the given `id` and its associated chunks from + /// this bucket. This method returns an error if the `id` does not match any files in the + /// bucket. + /// + /// `await` will return [`Result<()>`]. + pub fn delete(&self, id: Bson) -> Delete { + Delete { bucket: self, id } + } + + /// Deletes the [`FilesCollectionDocument`] with the given name and its associated chunks from + /// this bucket. This method returns an error if the name does not match any files in the + /// bucket. + /// + /// `await` will return [`Result<()>`]. + pub fn delete_by_name(&self, filename: impl Into) -> DeleteByName { + DeleteByName { + bucket: self, + filename: filename.into(), + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Deletes the [`FilesCollectionDocument`] with the given `id` and its associated chunks from + /// this bucket. This method returns an error if the `id` does not match any files in the + /// bucket. + /// + /// [`run`](Delete::run) will return [`Result<()>`]. + pub fn delete(&self, id: Bson) -> Delete { + self.async_bucket.delete(id) + } + + /// Deletes the [`FilesCollectionDocument`] with the given name and its associated chunks from + /// this bucket. This method returns an error if the name does not match any files in the + /// bucket. + /// + /// [`run`](DeleteByName::run) will return [`Result<()>`]. + pub fn delete_by_name(&self, filename: impl Into) -> DeleteByName { + self.async_bucket.delete_by_name(filename) + } +} + +/// Deletes a specific [`FilesCollectionDocument`] and its associated chunks. Construct with +/// [`GridFsBucket::delete`]. +#[must_use] +pub struct Delete<'a> { + bucket: &'a GridFsBucket, + id: Bson, +} + +#[action_impl] +impl<'a> Action for Delete<'a> { + type Future = DeleteFuture; + + async fn execute(self) -> Result<()> { + let delete_result = self + .bucket + .files() + .delete_one(doc! { "_id": self.id.clone() }) + .await?; + // Delete chunks regardless of whether a file was found. This will remove any possibly + // orphaned chunks. + self.bucket + .chunks() + .delete_many(doc! { "files_id": self.id.clone() }) + .await?; + + if delete_result.deleted_count == 0 { + return Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Id(self.id), + }) + .into()); + } + + Ok(()) + } +} + +/// Deletes a named [`FilesCollectionDocument`] and its associated chunks. Construct with +/// [`GridFsBucket::delete_by_name`]. +#[must_use] +pub struct DeleteByName<'a> { + bucket: &'a GridFsBucket, + filename: String, +} + +#[action_impl] +impl<'a> Action for DeleteByName<'a> { + type Future = DeleteByNameFuture; + + async fn execute(self) -> Result<()> { + use futures_util::stream::{StreamExt, TryStreamExt}; + let ids: Vec<_> = self + .bucket + .files() + .find(doc! { "filename": self.filename.clone() }) + .projection(doc! { "_id": 1 }) + .await? + .with_type::() + .map(|r| match r { + Ok(mut d) => d + .remove("_id") + .ok_or_else(|| crate::error::Error::internal("_id field expected")), + Err(e) => Err(e), + }) + .try_collect() + .await?; + + let count = self + .bucket + .files() + .delete_many(doc! { "_id": { "$in": ids.clone() } }) + .await? + .deleted_count; + self.bucket + .chunks() + .delete_many(doc! { "files_id": { "$in": ids } }) + .await?; + + if count == 0 { + return Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Filename(self.filename), + }) + .into()); + } + + Ok(()) + } +} diff --git a/src/action/gridfs/download.rs b/src/action/gridfs/download.rs new file mode 100644 index 000000000..888608e80 --- /dev/null +++ b/src/action/gridfs/download.rs @@ -0,0 +1,171 @@ +use crate::bson::{doc, Bson}; + +use crate::{ + action::{action_impl, deeplink, export_doc, option_setters, options_doc}, + error::{ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result}, + gridfs::{ + FilesCollectionDocument, + GridFsBucket, + GridFsDownloadByNameOptions, + GridFsDownloadStream, + }, +}; + +impl GridFsBucket { + /// Opens and returns a [`GridFsDownloadStream`] from which the application can read + /// the contents of the stored file specified by `id`. + /// + /// `await` will return d[`Result`]. + #[deeplink] + pub fn open_download_stream(&self, id: Bson) -> OpenDownloadStream { + OpenDownloadStream { bucket: self, id } + } + + /// Opens and returns a [`GridFsDownloadStream`] from which the application can read + /// the contents of the stored file specified by `filename`. + /// + /// If there are multiple files in the bucket with the given filename, the `revision` in the + /// options provided is used to determine which one to download. See the documentation for + /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is + /// provided, the file with `filename` most recently uploaded will be downloaded. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(download_by_name)] + pub fn open_download_stream_by_name( + &self, + filename: impl Into, + ) -> OpenDownloadStreamByName { + OpenDownloadStreamByName { + bucket: self, + filename: filename.into(), + options: None, + } + } + + // Utility functions for finding files within the bucket. + + async fn find_file_by_id(&self, id: &Bson) -> Result { + match self.find_one(doc! { "_id": id }).await? { + Some(file) => Ok(file), + None => Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Id(id.clone()), + }) + .into()), + } + } + + async fn find_file_by_name( + &self, + filename: &str, + options: Option, + ) -> Result { + let revision = options.and_then(|opts| opts.revision).unwrap_or(-1); + let (sort, skip) = if revision >= 0 { + (1, revision) + } else { + (-1, -revision - 1) + }; + + match self + .files() + .find_one(doc! { "filename": filename }) + .sort(doc! { "uploadDate": sort }) + .skip(skip as u64) + .await? + { + Some(fcd) => Ok(fcd), + None => { + if self + .files() + .find_one(doc! { "filename": filename }) + .await? + .is_some() + { + Err(ErrorKind::GridFs(GridFsErrorKind::RevisionNotFound { revision }).into()) + } else { + Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Filename(filename.into()), + }) + .into()) + } + } + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Opens and returns a [`GridFsDownloadStream`] from which the application can read + /// the contents of the stored file specified by `id`. + /// + /// [`run`](OpenDownloadStream::run) will return d[`Result`]. + #[deeplink] + pub fn open_download_stream(&self, id: Bson) -> OpenDownloadStream { + self.async_bucket.open_download_stream(id) + } + + /// Opens and returns a [`GridFsDownloadStream`] from which the application can read + /// the contents of the stored file specified by `filename`. + /// + /// If there are multiple files in the bucket with the given filename, the `revision` in the + /// options provided is used to determine which one to download. See the documentation for + /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is + /// provided, the file with `filename` most recently uploaded will be downloaded. + /// + /// [`run`](OpenDownloadStreamByName::run) will return d[`Result`]. + #[deeplink] + #[options_doc(download_by_name, sync)] + pub fn open_download_stream_by_name( + &self, + filename: impl Into, + ) -> OpenDownloadStreamByName { + self.async_bucket.open_download_stream_by_name(filename) + } +} + +/// Opens and returns a [`GridFsDownloadStream`] from which the application can read +/// the contents of the stored file specified by an id. Construct with +/// [`GridFsBucket::open_download_stream`]. +#[must_use] +pub struct OpenDownloadStream<'a> { + bucket: &'a GridFsBucket, + id: Bson, +} + +#[action_impl(sync = crate::sync::gridfs::GridFsDownloadStream)] +impl<'a> Action for OpenDownloadStream<'a> { + type Future = OpenDownloadStreamFuture; + + async fn execute(self) -> Result { + let file = self.bucket.find_file_by_id(&self.id).await?; + GridFsDownloadStream::new(file, self.bucket.chunks()).await + } +} + +/// Opens and returns a [`GridFsDownloadStream`] from which the application can read +/// the contents of the stored file specified by a filename. Construct with +/// [`GridFsBucket::open_download_stream_by_name`]. +#[must_use] +pub struct OpenDownloadStreamByName<'a> { + bucket: &'a GridFsBucket, + filename: String, + options: Option, +} + +#[option_setters(crate::gridfs::GridFsDownloadByNameOptions)] +#[export_doc(download_by_name)] +impl OpenDownloadStreamByName<'_> {} + +#[action_impl(sync = crate::sync::gridfs::GridFsDownloadStream)] +impl<'a> Action for OpenDownloadStreamByName<'a> { + type Future = OpenDownloadStreamByNameFuture; + + async fn execute(self) -> Result { + let file = self + .bucket + .find_file_by_name(&self.filename, self.options) + .await?; + GridFsDownloadStream::new(file, self.bucket.chunks()).await + } +} diff --git a/src/action/gridfs/drop.rs b/src/action/gridfs/drop.rs new file mode 100644 index 000000000..a63b72731 --- /dev/null +++ b/src/action/gridfs/drop.rs @@ -0,0 +1,39 @@ +use crate::{action::action_impl, error::Result, gridfs::GridFsBucket}; + +impl GridFsBucket { + /// Removes all of the files and their associated chunks from this bucket. + /// + /// `await` will return [`Result<()>`]. + pub fn drop(&self) -> Drop { + Drop { bucket: self } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Removes all of the files and their associated chunks from this bucket. + /// + /// [`run`](Drop::run) will return [`Result<()>`]. + pub fn drop(&self) -> Drop { + self.async_bucket.drop() + } +} + +/// Removes all of the files and their associated chunks from a bucket. Construct with +/// [`GridFsBucket::drop`]. +#[must_use] +pub struct Drop<'a> { + bucket: &'a GridFsBucket, +} + +#[action_impl] +impl<'a> Action for Drop<'a> { + type Future = DropFuture; + + async fn execute(self) -> Result<()> { + self.bucket.files().drop().await?; + self.bucket.chunks().drop().await?; + + Ok(()) + } +} diff --git a/src/action/gridfs/find.rs b/src/action/gridfs/find.rs new file mode 100644 index 000000000..df96b9787 --- /dev/null +++ b/src/action/gridfs/find.rs @@ -0,0 +1,118 @@ +use std::time::Duration; + +use crate::bson::Document; + +use crate::{ + action::{action_impl, deeplink, export_doc, option_setters, options_doc}, + coll::options::{FindOneOptions, FindOptions}, + error::Result, + gridfs::{FilesCollectionDocument, GridFsBucket, GridFsFindOneOptions, GridFsFindOptions}, + Cursor, +}; + +impl GridFsBucket { + /// Finds and returns the [`FilesCollectionDocument`]s within this bucket that match the given + /// filter. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find)] + pub fn find(&self, filter: Document) -> Find { + Find { + bucket: self, + filter, + options: None, + } + } + + /// Finds and returns a single [`FilesCollectionDocument`] within this bucket that matches the + /// given filter. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one)] + pub fn find_one(&self, filter: Document) -> FindOne { + FindOne { + bucket: self, + filter, + options: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Finds and returns the [`FilesCollectionDocument`]s within this bucket that match the given + /// filter. + /// + /// [`run`](Find::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find, sync)] + pub fn find(&self, filter: Document) -> Find { + self.async_bucket.find(filter) + } + + /// Finds and returns a single [`FilesCollectionDocument`] within this bucket that matches the + /// given filter. + /// + /// [`run`](FindOne::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(find_one, sync)] + pub fn find_one(&self, filter: Document) -> FindOne { + self.async_bucket.find_one(filter) + } +} + +/// Finds and returns the [`FilesCollectionDocument`]s within a bucket that match a given +/// filter. Construct with [`GridFsBucket::find`]. +#[must_use] +pub struct Find<'a> { + bucket: &'a GridFsBucket, + filter: Document, + options: Option, +} + +#[option_setters(crate::gridfs::options::GridFsFindOptions)] +#[export_doc(find)] +impl Find<'_> {} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a> Action for Find<'a> { + type Future = FindFuture; + + async fn execute(self) -> Result> { + let find_options = self.options.map(FindOptions::from); + self.bucket + .files() + .find(self.filter) + .with_options(find_options) + .await + } +} + +/// Finds and returns a single [`FilesCollectionDocument`] within a bucket that matches a +/// given filter. Construct with [`GridFsBucket::find_one`]. +#[must_use] +pub struct FindOne<'a> { + bucket: &'a GridFsBucket, + filter: Document, + options: Option, +} + +#[option_setters(crate::gridfs::options::GridFsFindOneOptions)] +#[export_doc(find_one)] +impl FindOne<'_> {} + +#[action_impl] +impl<'a> Action for FindOne<'a> { + type Future = FindOneFuture; + + async fn execute(self) -> Result> { + let find_options = self.options.map(FindOneOptions::from); + self.bucket + .files() + .find_one(self.filter) + .with_options(find_options) + .await + } +} diff --git a/src/action/gridfs/rename.rs b/src/action/gridfs/rename.rs new file mode 100644 index 000000000..b60a28d52 --- /dev/null +++ b/src/action/gridfs/rename.rs @@ -0,0 +1,126 @@ +use crate::bson::{doc, Bson}; + +use crate::{ + action::action_impl, + error::{ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result}, + gridfs::GridFsBucket, +}; + +impl GridFsBucket { + /// Renames the file with the given 'id' to the provided `new_filename`. This method returns an + /// error if the `id` does not match any files in the bucket. + /// + /// `await` will return [`Result<()>`]. + pub fn rename(&self, id: Bson, new_filename: impl Into) -> Rename { + Rename { + bucket: self, + id, + new_filename: new_filename.into(), + } + } + + /// Renames all revisions of the file with the given name to the provided `new_filename`. This + /// method returns an error if the name does not match any files in the bucket. + /// + /// `await` will return [`Result<()>`]. + pub fn rename_by_name( + &self, + filename: impl Into, + new_filename: impl Into, + ) -> RenameByName { + RenameByName { + bucket: self, + filename: filename.into(), + new_filename: new_filename.into(), + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Renames the file with the given `id` to the provided `new_filename`. This method returns an + /// error if the `id` does not match any files in the bucket. + /// + /// [`run`](Rename::run) will return [`Result<()>`]. + pub fn rename(&self, id: Bson, new_filename: impl Into) -> Rename { + self.async_bucket.rename(id, new_filename) + } + + /// Renames all revisions of the file with the given name to the provided `new_filename`. This + /// method returns an error if the name does not match any files in the bucket. + /// + /// [`run`](RenameByName::run) will return [`Result<()>`]. + pub fn rename_by_name( + &self, + filename: impl Into, + new_filename: impl Into, + ) -> RenameByName { + self.async_bucket.rename_by_name(filename, new_filename) + } +} + +/// Renames a file. Construct with [`GridFsBucket::rename`]. +#[must_use] +pub struct Rename<'a> { + bucket: &'a GridFsBucket, + id: Bson, + new_filename: String, +} + +#[action_impl] +impl<'a> Action for Rename<'a> { + type Future = RenameFuture; + + async fn execute(self) -> Result<()> { + let count = self + .bucket + .files() + .update_one( + doc! { "_id": self.id.clone() }, + doc! { "$set": { "filename": self.new_filename } }, + ) + .await? + .matched_count; + if count == 0 { + return Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Id(self.id), + }) + .into()); + } + + Ok(()) + } +} + +/// Renames a file selected by name. Construct with [`GridFsBucket::rename_by_name`]. +#[must_use] +pub struct RenameByName<'a> { + bucket: &'a GridFsBucket, + filename: String, + new_filename: String, +} + +#[action_impl] +impl<'a> Action for RenameByName<'a> { + type Future = RenameByNameFuture; + + async fn execute(self) -> Result<()> { + let count = self + .bucket + .files() + .update_many( + doc! { "filename": self.filename.clone() }, + doc! { "$set": { "filename": self.new_filename } }, + ) + .await? + .matched_count; + if count == 0 { + return Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { + identifier: GridFsFileIdentifier::Filename(self.filename), + }) + .into()); + } + + Ok(()) + } +} diff --git a/src/action/gridfs/upload.rs b/src/action/gridfs/upload.rs new file mode 100644 index 000000000..f313c455b --- /dev/null +++ b/src/action/gridfs/upload.rs @@ -0,0 +1,84 @@ +use crate::bson::{oid::ObjectId, Bson, Document}; + +#[cfg(docsrs)] +use crate::gridfs::FilesCollectionDocument; +use crate::{ + action::{action_impl, deeplink, export_doc, option_setters, options_doc}, + error::Result, + gridfs::{GridFsBucket, GridFsUploadOptions, GridFsUploadStream}, +}; + +impl GridFsBucket { + /// Creates and returns a [`GridFsUploadStream`] that the application can write the contents of + /// the file to. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(open_upload_stream)] + pub fn open_upload_stream(&self, filename: impl AsRef) -> OpenUploadStream { + OpenUploadStream { + bucket: self, + filename: filename.as_ref().to_owned(), + id: None, + options: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::gridfs::GridFsBucket { + /// Creates and returns a [`GridFsUploadStream`] that the application can write the contents of + /// the file to. + /// + /// [`run`](OpenUploadStream::run) will return d[`Result`]. + #[deeplink] + #[options_doc(open_upload_stream, sync)] + pub fn open_upload_stream(&self, filename: impl AsRef) -> OpenUploadStream { + self.async_bucket.open_upload_stream(filename) + } +} + +/// Creates and returns a [`GridFsUploadStream`] that the application can write the contents of +/// a file to. Construct with [`GridFsBucket::open_upload_stream`]. +#[must_use] +pub struct OpenUploadStream<'a> { + bucket: &'a GridFsBucket, + filename: String, + id: Option, + options: Option, +} + +#[option_setters(crate::gridfs::options::GridFsUploadOptions)] +#[export_doc(open_upload_stream)] +impl OpenUploadStream<'_> { + /// Set the value to be used for the corresponding [`FilesCollectionDocument`]'s `id` + /// field. If not set, a unique [`ObjectId`] will be generated that can be accessed via the + /// stream's [`id`](GridFsUploadStream::id) method. + pub fn id(mut self, value: Bson) -> Self { + self.id = Some(value); + self + } +} + +#[action_impl(sync = crate::sync::gridfs::GridFsUploadStream)] +impl<'a> Action for OpenUploadStream<'a> { + type Future = OpenUploadStreamFuture; + + async fn execute(self) -> Result { + let id = self.id.unwrap_or_else(|| ObjectId::new().into()); + let chunk_size_bytes = self + .options + .as_ref() + .and_then(|opts| opts.chunk_size_bytes) + .unwrap_or_else(|| self.bucket.chunk_size_bytes()); + let metadata = self.options.and_then(|opts| opts.metadata); + Ok(GridFsUploadStream::new( + self.bucket.clone(), + id, + self.filename, + chunk_size_bytes, + metadata, + self.bucket.client().register_async_drop(), + )) + } +} diff --git a/src/action/insert_many.rs b/src/action/insert_many.rs new file mode 100644 index 000000000..62754ba4e --- /dev/null +++ b/src/action/insert_many.rs @@ -0,0 +1,192 @@ +use std::{borrow::Borrow, collections::HashSet, ops::Deref}; + +use crate::bson::{Bson, RawDocumentBuf}; +use serde::Serialize; + +use crate::{ + coll::options::InsertManyOptions, + error::{Error, ErrorKind, IndexedWriteError, InsertManyError, Result}, + operation::Insert as Op, + options::WriteConcern, + results::InsertManyResult, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection { + /// Inserts the data in `docs` into the collection. + /// + /// Note that this method accepts both owned and borrowed values, so the input documents + /// do not need to be cloned in order to be passed in. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(insert_many)] + pub fn insert_many(&self, docs: impl IntoIterator>) -> InsertMany { + InsertMany { + coll: CollRef::new(self), + docs: docs + .into_iter() + .map(|v| { + crate::bson_compat::serialize_to_raw_document_buf(v.borrow()) + .map_err(Into::into) + }) + .collect(), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Inserts the data in `docs` into the collection. + /// + /// Note that this method accepts both owned and borrowed values, so the input documents + /// do not need to be cloned in order to be passed in. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](InsertMany::run) will return d[`Result`]. + #[deeplink] + #[options_doc(insert_many, sync)] + pub fn insert_many(&self, docs: impl IntoIterator>) -> InsertMany { + self.async_collection.insert_many(docs) + } +} + +/// Inserts documents into a collection. Construct with [`Collection::insert_many`]. +#[must_use] +pub struct InsertMany<'a> { + coll: CollRef<'a>, + docs: Result>, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::InsertManyOptions)] +#[export_doc(insert_many)] +impl<'a> InsertMany<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for InsertMany<'a> { + type Future = InsertManyFuture; + + async fn execute(mut self) -> Result { + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let ds = self.docs?; + if ds.is_empty() { + return Err(ErrorKind::InvalidArgument { + message: "No documents provided to insert_many".to_string(), + } + .into()); + } + let ordered = self + .options + .as_ref() + .and_then(|o| o.ordered) + .unwrap_or(true); + let encrypted = self.coll.client().should_auto_encrypt().await; + + let mut cumulative_failure: Option = None; + let mut error_labels: HashSet = Default::default(); + let mut cumulative_result: Option = None; + + let mut n_attempted = 0; + + while n_attempted < ds.len() { + let docs: Vec<_> = ds.iter().skip(n_attempted).map(Deref::deref).collect(); + let insert = Op::new(self.coll.namespace(), docs, self.options.clone(), encrypted); + + match self + .coll + .client() + .execute_operation(insert, self.session.as_deref_mut()) + .await + { + Ok(result) => { + let current_batch_size = result.inserted_ids.len(); + + let cumulative_result = cumulative_result.get_or_insert_with(Default::default); + for (index, id) in result.inserted_ids { + cumulative_result + .inserted_ids + .insert(index + n_attempted, id); + } + + n_attempted += current_batch_size; + } + Err(e) => { + let labels = e.labels().clone(); + match *e.kind { + ErrorKind::InsertMany(bw) => { + // for ordered inserts this size will be incorrect, but knowing the + // batch size isn't needed for ordered + // failures since we return immediately from + // them anyways. + let current_batch_size = bw.inserted_ids.len() + + bw.write_errors.as_ref().map(|we| we.len()).unwrap_or(0); + + let failure_ref = + cumulative_failure.get_or_insert_with(InsertManyError::new); + if let Some(write_errors) = bw.write_errors { + for err in write_errors { + let index = n_attempted + err.index; + + failure_ref + .write_errors + .get_or_insert_with(Default::default) + .push(IndexedWriteError { index, ..err }); + } + } + + if let Some(wc_error) = bw.write_concern_error { + failure_ref.write_concern_error = Some(wc_error); + } + + error_labels.extend(labels); + + if ordered { + // this will always be true since we invoked get_or_insert_with + // above. + if let Some(failure) = cumulative_failure { + return Err(Error::new( + ErrorKind::InsertMany(failure), + Some(error_labels), + )); + } + } + n_attempted += current_batch_size; + } + _ => return Err(e), + } + } + } + } + + match cumulative_failure { + Some(failure) => Err(Error::new( + ErrorKind::InsertMany(failure), + Some(error_labels), + )), + None => Ok(cumulative_result.unwrap_or_default()), + } + } +} diff --git a/src/action/insert_one.rs b/src/action/insert_one.rs new file mode 100644 index 000000000..b08a0d7d5 --- /dev/null +++ b/src/action/insert_one.rs @@ -0,0 +1,104 @@ +use std::{borrow::Borrow, ops::Deref}; + +use crate::bson::{Bson, RawDocumentBuf}; +use serde::Serialize; + +use crate::{ + coll::options::{InsertManyOptions, InsertOneOptions}, + error::{convert_insert_many_error, Result}, + operation::Insert as Op, + options::WriteConcern, + results::InsertOneResult, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection { + /// Inserts `doc` into the collection. + /// + /// Note that either an owned or borrowed value can be inserted here, so the input document + /// does not need to be cloned to be passed in. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(insert_one)] + pub fn insert_one(&self, doc: impl Borrow) -> InsertOne { + InsertOne { + coll: CollRef::new(self), + doc: crate::bson_compat::serialize_to_raw_document_buf(doc.borrow()) + .map_err(Into::into), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Inserts `doc` into the collection. + /// + /// Note that either an owned or borrowed value can be inserted here, so the input document + /// does not need to be cloned to be passed in. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](InsertOne::run) will return d[`Result`]. + #[deeplink] + #[options_doc(insert_one, sync)] + pub fn insert_one(&self, doc: impl Borrow) -> InsertOne { + self.async_collection.insert_one(doc) + } +} + +/// Inserts a document into a collection. Construct with ['Collection::insert_one`]. +#[must_use] +pub struct InsertOne<'a> { + coll: CollRef<'a>, + doc: Result, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::InsertOneOptions)] +#[export_doc(insert_one)] +impl<'a> InsertOne<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for InsertOne<'a> { + type Future = InsertOneFuture; + + async fn execute(mut self) -> Result { + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let doc = self.doc?; + + let insert = Op::new( + self.coll.namespace(), + vec![doc.deref()], + self.options.map(InsertManyOptions::from_insert_one_options), + self.coll.client().should_auto_encrypt().await, + ); + self.coll + .client() + .execute_operation(insert, self.session) + .await + .map(InsertOneResult::from_insert_many_result) + .map_err(convert_insert_many_error) + } +} diff --git a/src/action/list_collections.rs b/src/action/list_collections.rs new file mode 100644 index 000000000..21f261fb5 --- /dev/null +++ b/src/action/list_collections.rs @@ -0,0 +1,187 @@ +use std::marker::PhantomData; + +use crate::bson::{Bson, Document}; +use futures_util::TryStreamExt; + +use crate::{ + db::options::ListCollectionsOptions, + error::{Error, ErrorKind, Result}, + operation::list_collections as op, + results::CollectionSpecification, + ClientSession, + Cursor, + Database, + SessionCursor, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + ExplicitSession, + ImplicitSession, + ListNames, + ListSpecifications, +}; + +impl Database { + /// Gets information about each of the collections in the database. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_collections)] + pub fn list_collections(&self) -> ListCollections { + ListCollections { + db: self, + options: None, + mode: PhantomData, + session: ImplicitSession, + } + } + + /// Gets the names of the collections in the database. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_collections)] + pub fn list_collection_names(&self) -> ListCollections<'_, ListNames> { + ListCollections { + db: self, + options: None, + mode: PhantomData, + session: ImplicitSession, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Gets information about each of the collections in the database. + /// + /// [`run`](ListCollections::run) will return + /// d[`Result>`]. + #[deeplink] + #[options_doc(list_collections, sync)] + pub fn list_collections(&self) -> ListCollections { + self.async_database.list_collections() + } + + /// Gets the names of the collections in the database. + /// + /// [`run`](ListCollections::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(list_collections, sync)] + pub fn list_collection_names(&self) -> ListCollections<'_, ListNames> { + self.async_database.list_collection_names() + } +} + +/// Gets information about each of the collections in the database. Create by +/// calling [`Database::list_collections`] or [`Database::list_collection_names`]. +#[must_use] +pub struct ListCollections<'a, M = ListSpecifications, S = ImplicitSession> { + db: &'a Database, + options: Option, + mode: PhantomData, + session: S, +} + +#[option_setters(crate::db::options::ListCollectionsOptions)] +#[export_doc(list_collections, extra = [session])] +impl ListCollections<'_, M, S> {} + +impl<'a, M> ListCollections<'a, M, ImplicitSession> { + /// Use the provided session when running the operation. + pub fn session<'s>( + self, + value: impl Into<&'s mut ClientSession>, + ) -> ListCollections<'a, M, ExplicitSession<'s>> { + ListCollections { + db: self.db, + options: self.options, + mode: PhantomData, + session: ExplicitSession(value.into()), + } + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a> Action for ListCollections<'a, ListSpecifications, ImplicitSession> { + type Future = ListCollectionsFuture; + + async fn execute(self) -> Result> { + let list_collections = + op::ListCollections::new(self.db.name().to_string(), false, self.options); + self.db + .client() + .execute_cursor_operation(list_collections) + .await + } +} + +#[action_impl(sync = crate::sync::SessionCursor)] +impl<'a> Action for ListCollections<'a, ListSpecifications, ExplicitSession<'a>> { + type Future = ListCollectionsSessionFuture; + + async fn execute(self) -> Result> { + let list_collections = + op::ListCollections::new(self.db.name().to_string(), false, self.options); + self.db + .client() + .execute_session_cursor_operation(list_collections, self.session.0) + .await + } +} + +async fn list_collection_names_common( + cursor: impl TryStreamExt, +) -> Result> { + cursor + .and_then(|doc| match doc.get("name").and_then(Bson::as_str) { + Some(name) => futures_util::future::ok(name.into()), + None => futures_util::future::err( + ErrorKind::InvalidResponse { + message: "Expected name field in server response, but there was none." + .to_string(), + } + .into(), + ), + }) + .try_collect() + .await +} + +#[action_impl] +impl<'a> Action for ListCollections<'a, ListNames, ImplicitSession> { + type Future = ListCollectionNamesFuture; + + async fn execute(self) -> Result> { + let list_collections = + op::ListCollections::new(self.db.name().to_string(), true, self.options); + let cursor: Cursor = self + .db + .client() + .execute_cursor_operation(list_collections) + .await?; + return list_collection_names_common(cursor).await; + } +} + +#[action_impl] +impl<'a> Action for ListCollections<'a, ListNames, ExplicitSession<'a>> { + type Future = ListCollectionNamesSessionFuture; + + async fn execute(self) -> Result> { + let list_collections = + op::ListCollections::new(self.db.name().to_string(), true, self.options); + let mut cursor: SessionCursor = self + .db + .client() + .execute_session_cursor_operation(list_collections, &mut *self.session.0) + .await?; + + list_collection_names_common(cursor.stream(self.session.0)).await + } +} diff --git a/src/action/list_databases.rs b/src/action/list_databases.rs new file mode 100644 index 000000000..2d2c59dd1 --- /dev/null +++ b/src/action/list_databases.rs @@ -0,0 +1,140 @@ +use std::marker::PhantomData; + +use crate::bson::{Bson, Document}; + +#[cfg(feature = "sync")] +use crate::sync::Client as SyncClient; +use crate::{ + db::options::ListDatabasesOptions, + error::{ErrorKind, Result}, + operation::list_databases as op, + results::DatabaseSpecification, + Client, + ClientSession, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + ListNames, + ListSpecifications, +}; + +impl Client { + /// Gets information about each database present in the cluster the Client is connected to. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_databases)] + pub fn list_databases(&self) -> ListDatabases { + ListDatabases { + client: self, + options: Default::default(), + session: None, + mode: PhantomData, + } + } + + /// Gets the names of the databases present in the cluster the Client is connected to. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_databases)] + pub fn list_database_names(&self) -> ListDatabases<'_, ListNames> { + ListDatabases { + client: self, + options: Default::default(), + session: None, + mode: PhantomData, + } + } +} + +#[cfg(feature = "sync")] +impl SyncClient { + /// Gets information about each database present in the cluster the Client is connected to. + /// + /// [run](ListDatabases::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(list_databases, sync)] + pub fn list_databases(&self) -> ListDatabases { + self.async_client.list_databases() + } + + /// Gets the names of the databases present in the cluster the Client is connected to. + /// + /// [run](ListDatabases::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(list_databases, sync)] + pub fn list_database_names(&self) -> ListDatabases<'_, ListNames> { + self.async_client.list_database_names() + } +} + +/// Gets information about each database present in the cluster the Client is connected to. Create +/// by calling [`Client::list_databases`] or [`Client::list_database_names`]. +#[must_use] +pub struct ListDatabases<'a, M = ListSpecifications> { + client: &'a Client, + options: Option, + session: Option<&'a mut ClientSession>, + mode: PhantomData, +} + +#[option_setters(crate::db::options::ListDatabasesOptions)] +#[export_doc(list_databases)] +impl<'a, M> ListDatabases<'a, M> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for ListDatabases<'a, ListSpecifications> { + type Future = ListDatabasesFuture; + + async fn execute(self) -> Result> { + let op = op::ListDatabases::new(false, self.options); + self.client + .execute_operation(op, self.session) + .await + .and_then(|dbs| { + dbs.into_iter() + .map(|db_spec| { + crate::bson_compat::deserialize_from_slice(db_spec.as_bytes()) + .map_err(crate::error::Error::from) + }) + .collect() + }) + } +} + +#[action_impl] +impl<'a> Action for ListDatabases<'a, ListNames> { + type Future = ListDatabaseNamesFuture; + + async fn execute(self) -> Result> { + let op = op::ListDatabases::new(true, self.options); + match self.client.execute_operation(op, self.session).await { + Ok(databases) => databases + .into_iter() + .map(|doc| { + let name = doc + .get_str("name") + .map_err(|_| ErrorKind::InvalidResponse { + message: "Expected \"name\" field in server response, but it was not \ + found" + .to_string(), + })?; + Ok(name.to_string()) + }) + .collect(), + Err(e) => Err(e), + } + } +} diff --git a/src/action/list_indexes.rs b/src/action/list_indexes.rs new file mode 100644 index 000000000..acbf4bbde --- /dev/null +++ b/src/action/list_indexes.rs @@ -0,0 +1,179 @@ +use std::{marker::PhantomData, time::Duration}; + +use crate::bson::Bson; +use futures_util::stream::TryStreamExt; + +use crate::{ + coll::options::ListIndexesOptions, + error::Result, + operation::ListIndexes as Op, + ClientSession, + Collection, + Cursor, + IndexModel, + SessionCursor, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + CollRef, + ExplicitSession, + ImplicitSession, + ListNames, + ListSpecifications, +}; + +impl Collection +where + T: Send + Sync, +{ + /// Lists all indexes on this collection. + /// + /// `await` will return d[`Result>`] (or + /// d[`Result>`] if a `ClientSession` is provided). + #[deeplink] + #[options_doc(list_indexes)] + pub fn list_indexes(&self) -> ListIndexes { + ListIndexes { + coll: CollRef::new(self), + options: None, + session: ImplicitSession, + _mode: PhantomData, + } + } + + /// Gets the names of all indexes on the collection. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_indexes)] + pub fn list_index_names(&self) -> ListIndexes { + ListIndexes { + coll: CollRef::new(self), + options: None, + session: ImplicitSession, + _mode: PhantomData, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Lists all indexes on this collection. + /// + /// [`run`](ListIndexes::run) will return d[`Result>`] (or + /// d[`Result>`] if a `ClientSession` is provided). + #[deeplink] + #[options_doc(list_indexes, sync)] + pub fn list_indexes(&self) -> ListIndexes { + self.async_collection.list_indexes() + } + + /// Gets the names of all indexes on the collection. + /// + /// [`run`](ListIndexes::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(list_indexes, sync)] + pub fn list_index_names(&self) -> ListIndexes { + self.async_collection.list_index_names() + } +} + +/// List indexes on a collection. Construct with [`Collection::list_indexes`] or +/// [`Collection::list_index_names`]. +#[must_use] +pub struct ListIndexes<'a, Mode = ListSpecifications, Session = ImplicitSession> { + coll: CollRef<'a>, + options: Option, + session: Session, + _mode: PhantomData, +} + +#[option_setters(crate::coll::options::ListIndexesOptions)] +#[export_doc(list_indexes, extra = [session])] +impl ListIndexes<'_, Mode, Session> {} + +impl<'a, Mode> ListIndexes<'a, Mode, ImplicitSession> { + /// Use the provided session when running the operation. + pub fn session( + self, + value: impl Into<&'a mut ClientSession>, + ) -> ListIndexes<'a, Mode, ExplicitSession<'a>> { + ListIndexes { + coll: self.coll, + options: self.options, + session: ExplicitSession(value.into()), + _mode: PhantomData, + } + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a> Action for ListIndexes<'a, ListSpecifications, ImplicitSession> { + type Future = ListIndexesFuture; + + async fn execute(self) -> Result> { + let op = Op::new(self.coll.namespace(), self.options); + self.coll.client().execute_cursor_operation(op).await + } +} + +#[action_impl(sync = crate::sync::SessionCursor)] +impl<'a> Action for ListIndexes<'a, ListSpecifications, ExplicitSession<'a>> { + type Future = ListIndexesSessionFuture; + + async fn execute(self) -> Result> { + let op = Op::new(self.coll.namespace(), self.options); + self.coll + .client() + .execute_session_cursor_operation(op, self.session.0) + .await + } +} + +#[action_impl] +impl<'a> Action for ListIndexes<'a, ListNames, ImplicitSession> { + type Future = ListIndexNamesFuture; + + async fn execute(self) -> Result> { + let inner = ListIndexes { + coll: self.coll, + options: self.options, + session: self.session, + _mode: PhantomData::, + }; + let cursor = inner.await?; + cursor + .try_filter_map(|index| futures_util::future::ok(index.get_name())) + .try_collect() + .await + } +} + +#[action_impl] +impl<'a> Action for ListIndexes<'a, ListNames, ExplicitSession<'a>> { + type Future = ListIndexNamesSessionFuture; + + async fn execute(self) -> Result> { + let session = self.session.0; + let inner = ListIndexes { + coll: self.coll, + options: self.options, + session: ExplicitSession(&mut *session), + _mode: PhantomData::, + }; + let mut cursor = inner.await?; + let stream = cursor.stream(session); + stream + .try_filter_map(|index| futures_util::future::ok(index.get_name())) + .try_collect() + .await + } +} diff --git a/src/action/perf.rs b/src/action/perf.rs new file mode 100644 index 000000000..5d33e5727 --- /dev/null +++ b/src/action/perf.rs @@ -0,0 +1,47 @@ +use crate::Client; + +impl Client { + /// Add connections to the connection pool up to `min_pool_size`. This is normally not needed - + /// the connection pool will be filled in the background, and new connections created as needed + /// up to `max_pool_size`. However, it can sometimes be preferable to pay the (larger) cost of + /// creating new connections up-front so that individual operations execute as quickly as + /// possible. + /// + /// Note that topology changes require rebuilding the connection pool, so this method cannot + /// guarantee that the pool will always be filled for the lifetime of the `Client`. + /// + /// Does nothing if `min_pool_size` is unset or zero. + /// + /// `await` will return `()`. + pub fn warm_connection_pool(&self) -> WarmConnectionPool { + WarmConnectionPool { client: self } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Client { + /// Add connections to the connection pool up to `min_pool_size`. This is normally not needed - + /// the connection pool will be filled in the background, and new connections created as needed + /// up to `max_pool_size`. However, it can sometimes be preferable to pay the (larger) cost of + /// creating new connections up-front so that individual operations execute as quickly as + /// possible. + /// + /// Note that topology changes require rebuilding the connection pool, so this method cannot + /// guarantee that the pool will always be filled for the lifetime of the `Client`. + /// + /// Does nothing if `min_pool_size` is unset or zero. + /// + /// [`run`](WarmConnectionPool::run) will return `()`. + pub fn warm_connection_pool(&self) -> WarmConnectionPool { + self.async_client.warm_connection_pool() + } +} + +/// Add connections to the connection pool up to `min_pool_size`. Construct with +/// [`Client::warm_connection_pool`]. +#[must_use] +pub struct WarmConnectionPool<'a> { + pub(crate) client: &'a Client, +} + +// Action impl in src/client/action/perf.rs diff --git a/src/action/replace_one.rs b/src/action/replace_one.rs new file mode 100644 index 000000000..af3d31392 --- /dev/null +++ b/src/action/replace_one.rs @@ -0,0 +1,98 @@ +use std::borrow::Borrow; + +use crate::bson::{Bson, Document, RawDocumentBuf}; +use serde::Serialize; + +use crate::{ + coll::options::{Hint, ReplaceOptions, UpdateOptions}, + collation::Collation, + error::Result, + operation::Update as Op, + options::WriteConcern, + results::UpdateResult, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection { + /// Replaces up to one document matching `query` in the collection with `replacement`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(replace_one)] + pub fn replace_one(&self, query: Document, replacement: impl Borrow) -> ReplaceOne { + ReplaceOne { + coll: CollRef::new(self), + query, + replacement: crate::bson_compat::serialize_to_raw_document_buf(replacement.borrow()) + .map_err(Into::into), + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection { + /// Replaces up to one document matching `query` in the collection with `replacement`. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](ReplaceOne::run) will return d[`Result`]. + #[deeplink] + #[options_doc(replace_one, sync)] + pub fn replace_one(&self, query: Document, replacement: impl Borrow) -> ReplaceOne { + self.async_collection.replace_one(query, replacement) + } +} + +/// Replace up to one document matching a query. Construct with [`Collection::replace_one`]. +#[must_use] +pub struct ReplaceOne<'a> { + coll: CollRef<'a>, + query: Document, + replacement: Result, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::ReplaceOptions)] +#[export_doc(replace_one)] +impl<'a> ReplaceOne<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for ReplaceOne<'a> { + type Future = ReplaceOneFuture; + + async fn execute(mut self) -> Result { + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let update = Op::with_replace_raw( + self.coll.namespace(), + self.query, + self.replacement?, + false, + self.options.map(UpdateOptions::from_replace_options), + )?; + self.coll + .client() + .execute_operation(update, self.session) + .await + } +} diff --git a/src/action/run_command.rs b/src/action/run_command.rs new file mode 100644 index 000000000..438c279e5 --- /dev/null +++ b/src/action/run_command.rs @@ -0,0 +1,289 @@ +use std::time::Duration; + +use crate::bson::{Bson, Document, RawDocumentBuf}; + +use crate::{ + bson_compat::RawResult, + client::session::TransactionState, + coll::options::CursorType, + db::options::{RunCommandOptions, RunCursorCommandOptions}, + error::{ErrorKind, Result}, + operation::{run_command, run_cursor_command}, + selection_criteria::SelectionCriteria, + ClientSession, + Cursor, + Database, + SessionCursor, +}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + ExplicitSession, + ImplicitSession, +}; + +impl Database { + /// Runs a database-level command. + /// + /// Note that no inspection is done on `doc`, so the command will not use the database's default + /// read concern or write concern. If specific read concern or write concern is desired, it must + /// be specified manually. + /// Please note that run_command doesn't validate WriteConcerns passed into the body of the + /// command document. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(run_command)] + pub fn run_command(&self, command: Document) -> RunCommand { + RunCommand { + db: self, + command: RawDocumentBuf::from_document(&command), + options: None, + session: None, + } + } + + /// Runs a database-level command. + /// + /// Note that no inspection is done on `doc`, so the command will not use the database's default + /// read concern or write concern. If specific read concern or write concern is desired, it must + /// be specified manually. + /// Please note that run_raw_command doesn't validate WriteConcerns passed into the body of the + /// command document. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(run_command)] + pub fn run_raw_command(&self, command: RawDocumentBuf) -> RunCommand { + RunCommand { + db: self, + command: Ok(command), + options: None, + session: None, + } + } + + /// Runs a database-level command and returns a cursor to the response. + /// + /// `await` will return d[`Result>`] or a + /// d[`Result>`] if a [`ClientSession`] is provided. + #[deeplink] + #[options_doc(run_cursor_command)] + pub fn run_cursor_command(&self, command: Document) -> RunCursorCommand { + RunCursorCommand { + db: self, + command: RawDocumentBuf::from_document(&command), + options: None, + session: ImplicitSession, + } + } + + /// Runs a database-level command and returns a cursor to the response. + /// + /// `await` will return d[`Result>`] or a + /// d[`Result>`] if a [`ClientSession`] is provided. + #[deeplink] + #[options_doc(run_cursor_command)] + pub fn run_raw_cursor_command(&self, command: RawDocumentBuf) -> RunCursorCommand { + RunCursorCommand { + db: self, + command: Ok(command), + options: None, + session: ImplicitSession, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Runs a database-level command. + /// + /// Note that no inspection is done on `doc`, so the command will not use the database's default + /// read concern or write concern. If specific read concern or write concern is desired, it must + /// be specified manually. + /// Please note that run_command doesn't validate WriteConcerns passed into the body of the + /// command document. + /// + /// [`run`](RunCommand::run) will return d[`Result`]. + #[deeplink] + #[options_doc(run_command, sync)] + pub fn run_command(&self, command: Document) -> RunCommand { + self.async_database.run_command(command) + } + + /// Runs a database-level command. + /// + /// Note that no inspection is done on `doc`, so the command will not use the database's default + /// read concern or write concern. If specific read concern or write concern is desired, it must + /// be specified manually. + /// Please note that run_raw_command doesn't validate WriteConcerns passed into the body of the + /// command document. + /// + /// [`run`](RunCommand::run) will return d[`Result`]. + #[deeplink] + #[options_doc(run_command, sync)] + pub fn run_raw_command(&self, command: RawDocumentBuf) -> RunCommand { + self.async_database.run_raw_command(command) + } + + /// Runs a database-level command and returns a cursor to the response. + /// + /// [`run`](RunCursorCommand::run) will return d[`Result>`] or a + /// d[`Result>`] if a [`ClientSession`] is provided. + #[deeplink] + #[options_doc(run_cursor_command, sync)] + pub fn run_cursor_command(&self, command: Document) -> RunCursorCommand { + self.async_database.run_cursor_command(command) + } + + /// Runs a database-level command and returns a cursor to the response. + /// + /// [`run`](RunCursorCommand::run) will return d[`Result>`] or a + /// d[`Result>`] if a [`ClientSession`] is provided. + #[deeplink] + #[options_doc(run_cursor_command, sync)] + pub fn run_raw_cursor_command(&self, command: RawDocumentBuf) -> RunCursorCommand { + self.async_database.run_raw_cursor_command(command) + } +} + +/// Run a database-level command. Create with [`Database::run_command`]. +#[must_use] +pub struct RunCommand<'a> { + db: &'a Database, + command: RawResult, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::db::options::RunCommandOptions)] +#[export_doc(run_command)] +impl<'a> RunCommand<'a> { + /// Run the command using the provided [`ClientSession`]. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for RunCommand<'a> { + type Future = RunCommandFuture; + + async fn execute(self) -> Result { + let mut selection_criteria = self.options.and_then(|o| o.selection_criteria); + let command = self.command?; + if let Some(session) = &self.session { + match session.transaction.state { + TransactionState::Starting | TransactionState::InProgress => { + if command.get("readConcern").is_ok_and(|rc| rc.is_some()) { + return Err(ErrorKind::InvalidArgument { + message: "Cannot set read concern after starting a transaction".into(), + } + .into()); + } + selection_criteria = match selection_criteria { + Some(selection_criteria) => Some(selection_criteria), + None => { + if let Some(ref options) = session.transaction.options { + options.selection_criteria.clone() + } else { + None + } + } + }; + } + _ => {} + } + } + + let operation = + run_command::RunCommand::new(self.db.name().into(), command, selection_criteria, None); + self.db + .client() + .execute_operation(operation, self.session) + .await + } +} + +/// Runs a database-level command and returns a cursor to the response. Create with +/// [`Database::run_cursor_command`]. +#[must_use] +pub struct RunCursorCommand<'a, Session = ImplicitSession> { + db: &'a Database, + command: RawResult, + options: Option, + session: Session, +} + +#[option_setters(crate::db::options::RunCursorCommandOptions)] +#[export_doc(run_cursor_command, extra = [session])] +impl RunCursorCommand<'_, Session> {} + +impl<'a> RunCursorCommand<'a, ImplicitSession> { + /// Run the command using the provided [`ClientSession`]. + pub fn session( + self, + value: impl Into<&'a mut ClientSession>, + ) -> RunCursorCommand<'a, ExplicitSession<'a>> { + RunCursorCommand { + db: self.db, + command: self.command, + options: self.options, + session: ExplicitSession(value.into()), + } + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a> Action for RunCursorCommand<'a, ImplicitSession> { + type Future = RunCursorCommandFuture; + + async fn execute(self) -> Result> { + let selection_criteria = self + .options + .as_ref() + .and_then(|options| options.selection_criteria.clone()); + let rcc = run_command::RunCommand::new( + self.db.name().to_string(), + self.command?, + selection_criteria, + None, + ); + let rc_command = run_cursor_command::RunCursorCommand::new(rcc, self.options)?; + let client = self.db.client(); + client.execute_cursor_operation(rc_command).await + } +} + +#[action_impl(sync = crate::sync::SessionCursor)] +impl<'a> Action for RunCursorCommand<'a, ExplicitSession<'a>> { + type Future = RunCursorCommandSessionFuture; + + async fn execute(mut self) -> Result> { + resolve_selection_criteria_with_session!( + self.db, + self.options, + Some(&mut *self.session.0) + )?; + let selection_criteria = self + .options + .as_ref() + .and_then(|options| options.selection_criteria.clone()); + let rcc = run_command::RunCommand::new( + self.db.name().to_string(), + self.command?, + selection_criteria, + None, + ); + let rc_command = run_cursor_command::RunCursorCommand::new(rcc, self.options)?; + let client = self.db.client(); + client + .execute_session_cursor_operation(rc_command, self.session.0) + .await + } +} diff --git a/src/action/search_index.rs b/src/action/search_index.rs new file mode 100644 index 000000000..b3ea37546 --- /dev/null +++ b/src/action/search_index.rs @@ -0,0 +1,301 @@ +use std::marker::PhantomData; + +use crate::bson::{doc, Document}; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + CollRef, + Multiple, + Single, +}; +use crate::{ + coll::options::AggregateOptions, + error::{Error, Result}, + operation, + search_index::options::{ + CreateSearchIndexOptions, + DropSearchIndexOptions, + ListSearchIndexOptions, + UpdateSearchIndexOptions, + }, + Collection, + Cursor, + SearchIndexModel, +}; + +impl Collection +where + T: Send + Sync, +{ + /// Creates multiple search indexes on the collection. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(create_search_index)] + pub fn create_search_indexes( + &self, + models: impl IntoIterator, + ) -> CreateSearchIndex { + CreateSearchIndex { + coll: CollRef::new(self), + models: models.into_iter().collect(), + options: None, + _mode: PhantomData, + } + } + + /// Convenience method for creating a single search index. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(create_search_index)] + pub fn create_search_index(&self, model: SearchIndexModel) -> CreateSearchIndex { + CreateSearchIndex { + coll: CollRef::new(self), + models: vec![model], + options: None, + _mode: PhantomData, + } + } + + /// Updates the search index with the given name to use the provided definition. + /// + /// `await` will return [`Result<()>`]. + #[options_doc(update_search_index)] + pub fn update_search_index( + &self, + name: impl Into, + definition: Document, + ) -> UpdateSearchIndex { + UpdateSearchIndex { + coll: CollRef::new(self), + name: name.into(), + definition, + options: None, + } + } + + /// Drops the search index with the given name. + /// + /// `await` will return [`Result<()>`]. + #[options_doc(drop_search_index)] + pub fn drop_search_index(&self, name: impl Into) -> DropSearchIndex { + DropSearchIndex { + coll: CollRef::new(self), + name: name.into(), + options: None, + } + } + + /// Gets index information for one or more search indexes in the collection. + /// + /// If name is not specified, information for all indexes on the specified collection will be + /// returned. + /// + /// `await` will return d[`Result>`]. + #[deeplink] + #[options_doc(list_search_indexes)] + pub fn list_search_indexes(&self) -> ListSearchIndexes { + ListSearchIndexes { + coll: CollRef::new(self), + name: None, + agg_options: None, + options: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Creates multiple search indexes on the collection. + /// + /// [`run`](CreateSearchIndex::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(create_search_index, sync)] + pub fn create_search_indexes( + &self, + models: impl IntoIterator, + ) -> CreateSearchIndex { + self.async_collection.create_search_indexes(models) + } + + /// Convenience method for creating a single search index. + /// + /// [`run`](CreateSearchIndex::run) will return d[`Result`]. + #[deeplink] + #[options_doc(create_search_index, sync)] + pub fn create_search_index(&self, model: SearchIndexModel) -> CreateSearchIndex { + self.async_collection.create_search_index(model) + } + + /// Updates the search index with the given name to use the provided definition. + /// + /// [`run`](UpdateSearchIndex::run) will return [`Result<()>`]. + #[options_doc(update_search_index, sync)] + pub fn update_search_index( + &self, + name: impl Into, + definition: Document, + ) -> UpdateSearchIndex { + self.async_collection.update_search_index(name, definition) + } + + /// Drops the search index with the given name. + /// + /// [`run`](DropSearchIndex::run) will return [`Result<()>`]. + #[options_doc(drop_search_index, sync)] + pub fn drop_search_index(&self, name: impl Into) -> DropSearchIndex { + self.async_collection.drop_search_index(name) + } + + /// Gets index information for one or more search indexes in the collection. + /// + /// If name is not specified, information for all indexes on the specified collection will be + /// returned. + /// + /// [`run`](ListSearchIndexes::run) will return d[`Result>`]. + #[deeplink] + #[options_doc(list_search_indexes, sync)] + pub fn list_search_indexes(&self) -> ListSearchIndexes { + self.async_collection.list_search_indexes() + } +} + +/// Create search indexes on a collection. Construct with [`Collection::create_search_index`] or +/// [`Collection::create_search_indexes`]. +#[must_use] +pub struct CreateSearchIndex<'a, Mode> { + coll: CollRef<'a>, + models: Vec, + options: Option, + _mode: PhantomData, +} + +#[option_setters(crate::search_index::options::CreateSearchIndexOptions)] +#[export_doc(create_search_index)] +impl CreateSearchIndex<'_, Mode> {} + +#[action_impl] +impl<'a> Action for CreateSearchIndex<'a, Multiple> { + type Future = CreateSearchIndexesFuture; + + async fn execute(self) -> Result> { + let op = operation::CreateSearchIndexes::new(self.coll.namespace(), self.models); + self.coll.client().execute_operation(op, None).await + } +} + +#[action_impl] +impl<'a> Action for CreateSearchIndex<'a, Single> { + type Future = CreateSearchIndexFuture; + + async fn execute(self) -> Result { + let mut names = self + .coll + .create_search_indexes(self.models) + .with_options(self.options) + .await?; + match names.len() { + 1 => Ok(names.pop().unwrap()), + n => Err(Error::internal(format!("expected 1 index name, got {}", n))), + } + } +} + +/// Updates a specific search index to use a new definition. Construct with +/// [`Collection::update_search_index`]. +#[must_use] +pub struct UpdateSearchIndex<'a> { + coll: CollRef<'a>, + name: String, + definition: Document, + options: Option, +} + +#[option_setters(crate::search_index::options::UpdateSearchIndexOptions)] +#[export_doc(update_search_index)] +impl UpdateSearchIndex<'_> {} + +#[action_impl] +impl<'a> Action for UpdateSearchIndex<'a> { + type Future = UpdateSearchIndexFuture; + + async fn execute(self) -> Result<()> { + let op = + operation::UpdateSearchIndex::new(self.coll.namespace(), self.name, self.definition); + self.coll.client().execute_operation(op, None).await + } +} + +/// Drops a specific search index. Construct with [`Collection::drop_search_index`]. +#[must_use] +pub struct DropSearchIndex<'a> { + coll: CollRef<'a>, + name: String, + options: Option, +} + +#[option_setters(crate::search_index::options::DropSearchIndexOptions)] +#[export_doc(drop_search_index)] +impl DropSearchIndex<'_> {} + +#[action_impl] +impl<'a> Action for DropSearchIndex<'a> { + type Future = DropSearchIndexFuture; + + async fn execute(self) -> Result<()> { + let op = operation::DropSearchIndex::new(self.coll.namespace(), self.name); + self.coll.client().execute_operation(op, None).await + } +} + +/// Gets index information for one or more search indexes in a collection. +#[must_use] +pub struct ListSearchIndexes<'a> { + coll: CollRef<'a>, + name: Option, + agg_options: Option, + options: Option, +} + +#[option_setters(crate::search_index::options::ListSearchIndexOptions)] +#[export_doc(list_search_indexes)] +impl ListSearchIndexes<'_> { + /// Get information for the named index. + pub fn name(mut self, name: impl Into) -> Self { + self.name = Some(name.into()); + self + } + + /// Set aggregation options. + pub fn aggregate_options(mut self, value: AggregateOptions) -> Self { + self.agg_options = Some(value); + self + } +} + +#[action_impl(sync = crate::sync::Cursor)] +impl<'a> Action for ListSearchIndexes<'a> { + type Future = ListSearchIndexesFuture; + + async fn execute(self) -> Result> { + let mut inner = doc! {}; + if let Some(name) = self.name { + inner.insert("name", name); + } + self.coll + .clone_unconcerned() + .aggregate(vec![doc! { + "$listSearchIndexes": inner, + }]) + .with_options(self.agg_options) + .await + } +} diff --git a/src/action/session.rs b/src/action/session.rs new file mode 100644 index 000000000..6b4d8fb91 --- /dev/null +++ b/src/action/session.rs @@ -0,0 +1,57 @@ +use crate::{ + client::options::{SessionOptions, TransactionOptions}, + error::Result, + Client, + ClientSession, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc}; + +impl Client { + /// Starts a new [`ClientSession`]. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(start_session)] + pub fn start_session(&self) -> StartSession { + StartSession { + client: self, + options: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Client { + /// Starts a new [`ClientSession`]. + /// + /// [run](StartSession::run) will return d[`Result`]. + #[deeplink] + #[options_doc(start_session, sync)] + pub fn start_session(&self) -> StartSession { + self.async_client.start_session() + } +} + +/// Starts a new [`ClientSession`]. Construct with [`Client::start_session`]. +#[must_use] +pub struct StartSession<'a> { + client: &'a Client, + options: Option, +} + +#[option_setters(crate::client::options::SessionOptions)] +#[export_doc(start_session)] +impl StartSession<'_> {} + +#[action_impl(sync = crate::sync::ClientSession)] +impl<'a> Action for StartSession<'a> { + type Future = StartSessionFuture; + + async fn execute(self) -> Result { + if let Some(options) = &self.options { + options.validate()?; + } + Ok(ClientSession::new(self.client.clone(), self.options, false).await) + } +} diff --git a/src/action/shutdown.rs b/src/action/shutdown.rs new file mode 100644 index 000000000..f8336c131 --- /dev/null +++ b/src/action/shutdown.rs @@ -0,0 +1,148 @@ +use crate::Client; + +use super::{export_doc, options_doc}; + +impl Client { + /// Shut down this `Client`, terminating background thread workers and closing connections. + /// Using this method is not required under most circumstances (resources will be cleaned up in + /// the background when dropped) but can be needed when creating `Client`s in a loop or precise + /// control of lifespan timing is required. This will wait for any live handles to + /// server-side resources (see below) to be dropped and any associated server-side + /// operations to finish. + /// + /// IMPORTANT: Any live resource handles that are not dropped will cause this method to wait + /// indefinitely. It's strongly recommended to structure your usage to avoid this, e.g. by + /// only using those types in shorter-lived scopes than the `Client`. If this is not possible, + /// see [`immediate`](Shutdown::immediate). For example: + /// + /// ```rust + /// # use mongodb::{Client, error::Result, gridfs::GridFsBucket}; + /// async fn upload_data(bucket: &GridFsBucket) -> Result<()> { + /// let stream = bucket.open_upload_stream("test").await?; + /// // .. write to the stream .. + /// # Ok(()) + /// } + /// + /// # async fn run() -> Result<()> { + /// let client = Client::with_uri_str("mongodb://example.com").await?; + /// let bucket = client.database("test").gridfs_bucket(None); + /// upload_data(&bucket).await; + /// client.shutdown().await; + /// // Background cleanup work from `upload_data` is guaranteed to have run. + /// # Ok(()) + /// # } + /// ``` + /// + /// If the handle is used in the same scope as `shutdown`, explicit `drop` may be needed: + /// + /// ```rust + /// # use mongodb::{Client, error::Result}; + /// # async fn run() -> Result<()> { + /// let client = Client::with_uri_str("mongodb://example.com").await?; + /// let bucket = client.database("test").gridfs_bucket(None); + /// let stream = bucket.open_upload_stream("test").await?; + /// // .. write to the stream .. + /// drop(stream); + /// client.shutdown().await; + /// // Background cleanup work for `stream` is guaranteed to have run. + /// # Ok(()) + /// # } + /// ``` + /// + /// Calling any methods on clones of this `Client` or derived handles after this will return + /// errors. + /// + /// Handles to server-side resources are `Cursor`, `SessionCursor`, `Session`, or + /// `GridFsUploadStream`. + /// + /// `await` will return `()`. + #[options_doc(shutdown)] + pub fn shutdown(self) -> Shutdown { + Shutdown { + client: self, + immediate: false, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Client { + /// Shut down this `Client`, terminating background thread workers and closing connections. + /// Using this method is not required under most circumstances (resources will be cleaned up in + /// the background when dropped) but can be needed when creating `Client`s in a loop or precise + /// control of lifespan timing is required. This will wait for any live handles to + /// server-side resources (see below) to be dropped and any associated server-side + /// operations to finish. + /// + /// IMPORTANT: Any live resource handles that are not dropped will cause this method to wait + /// indefinitely. It's strongly recommended to structure your usage to avoid this, e.g. by + /// only using those types in shorter-lived scopes than the `Client`. If this is not possible, + /// see [`immediate`](Shutdown::immediate). For example: + /// + /// ```rust + /// # use mongodb::{sync::{Client, gridfs::GridFsBucket}, error::Result}; + /// fn upload_data(bucket: &GridFsBucket) -> Result<()> { + /// let stream = bucket.open_upload_stream("test").run()?; + /// // .. write to the stream .. + /// # Ok(()) + /// } + /// + /// # fn run() -> Result<()> { + /// let client = Client::with_uri_str("mongodb://example.com")?; + /// let bucket = client.database("test").gridfs_bucket(None); + /// upload_data(&bucket)?; + /// client.shutdown(); + /// // Background cleanup work from `upload_data` is guaranteed to have run. + /// # Ok(()) + /// # } + /// ``` + /// + /// If the handle is used in the same scope as `shutdown`, explicit `drop` may be needed: + /// + /// ```rust + /// # use mongodb::{sync::Client, error::Result}; + /// # fn run() -> Result<()> { + /// let client = Client::with_uri_str("mongodb://example.com")?; + /// let bucket = client.database("test").gridfs_bucket(None); + /// let stream = bucket.open_upload_stream("test").run()?; + /// // .. write to the stream .. + /// drop(stream); + /// client.shutdown(); + /// // Background cleanup work for `stream` is guaranteed to have run. + /// # Ok(()) + /// # } + /// ``` + /// + /// Calling any methods on clones of this `Client` or derived handles after this will return + /// errors. + /// + /// Handles to server-side resources are `Cursor`, `SessionCursor`, `Session`, or + /// `GridFsUploadStream`. + /// + /// [`run`](Shutdown::run) will return `()`. + #[options_doc(shutdown, sync)] + pub fn shutdown(self) -> Shutdown { + self.async_client.shutdown() + } +} + +/// Shut down this `Client`, terminating background thread workers and closing connections. Create +/// by calling [`Client::shutdown`]. +#[must_use] +pub struct Shutdown { + pub(crate) client: Client, + pub(crate) immediate: bool, +} + +#[export_doc(shutdown)] +impl Shutdown { + /// If `true`, execution will not wait for pending resources to be cleaned up, + /// which may cause both client-side errors and server-side resource leaks. Defaults to + /// `false`. + pub fn immediate(mut self, value: bool) -> Self { + self.immediate = value; + self + } +} + +// IntoFuture impl in src/client/action/shutdown.rs diff --git a/src/action/transaction.rs b/src/action/transaction.rs new file mode 100644 index 000000000..c1de5ba71 --- /dev/null +++ b/src/action/transaction.rs @@ -0,0 +1,232 @@ +use std::time::Duration; + +use crate::{ + client::options::TransactionOptions, + options::{ReadConcern, WriteConcern}, + selection_criteria::SelectionCriteria, + ClientSession, +}; + +use super::{export_doc, option_setters, options_doc}; + +impl ClientSession { + /// Starts a new transaction on this session. If no options are set, the session's + /// `defaultTransactionOptions` will be used. This session must be passed into each operation + /// within the transaction; otherwise, the operation will be executed outside of the + /// transaction. + /// + /// Errors returned from operations executed within a transaction may include a + /// [`crate::error::TRANSIENT_TRANSACTION_ERROR`] label. This label indicates that the entire + /// transaction can be retried with a reasonable expectation that it will succeed. + /// + /// Transactions on replica sets are supported on MongoDB 4.0+. Transactions on sharded + /// clusters are supported on MongoDB 4.2+. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().await?; + /// session.start_transaction().await?; + /// let result = coll.insert_one(doc! { "x": 1 }).session(&mut session).await?; + /// session.commit_transaction().await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// `await` will return [`Result<()>`]. + #[options_doc(start_transaction)] + pub fn start_transaction(&mut self) -> StartTransaction<&mut Self> { + StartTransaction { + session: self, + options: None, + } + } + + /// Commits the transaction that is currently active on this session. + /// + /// This method may return an error with a [`crate::error::UNKNOWN_TRANSACTION_COMMIT_RESULT`] + /// label. This label indicates that it is unknown whether the commit has satisfied the write + /// concern associated with the transaction. If an error with this label is returned, it is + /// safe to retry the commit until the write concern is satisfied or an error without the label + /// is returned. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().await?; + /// session.start_transaction().await?; + /// let result = coll.insert_one(doc! { "x": 1 }).session(&mut session).await?; + /// session.commit_transaction().await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return [`Result<()>`]. + pub fn commit_transaction(&mut self) -> CommitTransaction { + CommitTransaction { session: self } + } + + /// Aborts the transaction that is currently active on this session. Any open transaction will + /// be aborted automatically in the `Drop` implementation of `ClientSession`. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession, Collection}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().await?; + /// session.start_transaction().await?; + /// match execute_transaction(&coll, &mut session).await { + /// Ok(_) => session.commit_transaction().await?, + /// Err(_) => session.abort_transaction().await?, + /// } + /// # Ok(()) + /// # } + /// + /// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> { + /// coll.insert_one(doc! { "x": 1 }).session(&mut *session).await?; + /// coll.delete_one(doc! { "y": 2 }).session(&mut *session).await?; + /// Ok(()) + /// } + /// ``` + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return [`Result<()>`]. + pub fn abort_transaction(&mut self) -> AbortTransaction { + AbortTransaction { session: self } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::ClientSession { + /// Starts a new transaction on this session with the given `TransactionOptions`. If no options + /// are provided, the session's `defaultTransactionOptions` will be used. This session must + /// be passed into each operation within the transaction; otherwise, the operation will be + /// executed outside of the transaction. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession}}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com")?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().run()?; + /// session.start_transaction().run()?; + /// let result = coll.insert_one(doc! { "x": 1 }).session(&mut session).run()?; + /// session.commit_transaction().run()?; + /// # Ok(()) + /// # } + /// ``` + /// + /// [`run`](StartTransaction::run) will return [`Result<()>`]. + #[options_doc(start_transaction, sync)] + pub fn start_transaction(&mut self) -> StartTransaction<&mut Self> { + StartTransaction { + session: self, + options: None, + } + } + + /// Commits the transaction that is currently active on this session. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession}}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com")?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().run()?; + /// session.start_transaction().run()?; + /// let result = coll.insert_one(doc! { "x": 1 }).session(&mut session).run()?; + /// session.commit_transaction().run()?; + /// # Ok(()) + /// # } + /// ``` + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](CommitTransaction::run) will return [`Result<()>`]. + pub fn commit_transaction(&mut self) -> CommitTransaction { + self.async_client_session.commit_transaction() + } + + /// Aborts the transaction that is currently active on this session. Any open transaction will + /// be aborted automatically in the `Drop` implementation of `ClientSession`. + /// + /// ```rust + /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession, Collection}}; + /// # + /// # async fn do_stuff() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com")?; + /// # let coll = client.database("foo").collection::("bar"); + /// # let mut session = client.start_session().run()?; + /// session.start_transaction().run()?; + /// match execute_transaction(coll, &mut session) { + /// Ok(_) => session.commit_transaction().run()?, + /// Err(_) => session.abort_transaction().run()?, + /// } + /// # Ok(()) + /// # } + /// + /// fn execute_transaction(coll: Collection, session: &mut ClientSession) -> Result<()> { + /// coll.insert_one(doc! { "x": 1 }).session(&mut *session).run()?; + /// coll.delete_one(doc! { "y": 2 }).session(&mut *session).run()?; + /// Ok(()) + /// } + /// ``` + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](AbortTransaction::run) will return [`Result<()>`]. + pub fn abort_transaction(&mut self) -> AbortTransaction { + self.async_client_session.abort_transaction() + } +} + +/// Start a new transaction. Construct with [`ClientSession::start_transaction`]. +#[must_use] +pub struct StartTransaction { + pub(crate) session: S, + pub(crate) options: Option, +} + +#[option_setters(crate::client::options::TransactionOptions)] +#[export_doc(start_transaction)] +impl StartTransaction {} + +/// Commits a currently-active transaction. Construct with [`ClientSession::commit_transaction`]. +#[must_use] +pub struct CommitTransaction<'a> { + pub(crate) session: &'a mut ClientSession, +} + +/// Abort the currently active transaction on a session. Construct with +/// [`ClientSession::abort_transaction`]. +#[must_use] +pub struct AbortTransaction<'a> { + pub(crate) session: &'a mut ClientSession, +} + +// Action impls at src/client/session/action.rs diff --git a/src/action/update.rs b/src/action/update.rs new file mode 100644 index 000000000..d53bdd590 --- /dev/null +++ b/src/action/update.rs @@ -0,0 +1,148 @@ +use crate::bson::{Bson, Document}; + +use crate::{ + coll::options::{Hint, UpdateModifications, UpdateOptions}, + collation::Collation, + error::Result, + operation::Update as Op, + options::WriteConcern, + results::UpdateResult, + ClientSession, + Collection, +}; + +use super::{action_impl, deeplink, export_doc, option_setters, options_doc, CollRef}; + +impl Collection +where + T: Send + Sync, +{ + /// Updates all documents matching `query` in the collection. + /// + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. See the official MongoDB + /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(update)] + pub fn update_many(&self, query: Document, update: impl Into) -> Update { + Update { + coll: CollRef::new(self), + query, + update: update.into(), + multi: true, + options: None, + session: None, + } + } + + /// Updates up to one document matching `query` in the collection. + /// + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. See the official MongoDB + /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// `await` will return d[`Result`]. + #[deeplink] + #[options_doc(update)] + pub fn update_one(&self, query: Document, update: impl Into) -> Update { + Update { + coll: CollRef::new(self), + query, + update: update.into(), + multi: false, + options: None, + session: None, + } + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Updates all documents matching `query` in the collection. + /// + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. See the official MongoDB + /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. + /// + /// [`run`](Update::run) will return d[`Result`]. + #[deeplink] + #[options_doc(update, sync)] + pub fn update_many(&self, query: Document, update: impl Into) -> Update { + self.async_collection.update_many(query, update) + } + + /// Updates up to one document matching `query` in the collection. + /// + /// Both `Document` and `Vec` implement `Into`, so either can be + /// passed in place of constructing the enum case. Note: pipeline updates are only supported + /// in MongoDB 4.2+. See the official MongoDB + /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. + /// + /// This operation will retry once upon failure if the connection and encountered error support + /// retryability. See the documentation + /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on + /// retryable writes. + /// + /// [`run`](Update::run) will return d[`Result`]. + #[deeplink] + #[options_doc(update, sync)] + pub fn update_one(&self, query: Document, update: impl Into) -> Update { + self.async_collection.update_one(query, update) + } +} + +/// Update documents matching a query. Construct with [`Collection::update_many`] or +/// [`Collection::update_one`]. +#[must_use] +pub struct Update<'a> { + coll: CollRef<'a>, + query: Document, + update: UpdateModifications, + multi: bool, + options: Option, + session: Option<&'a mut ClientSession>, +} + +#[option_setters(crate::coll::options::UpdateOptions)] +#[export_doc(update)] +impl<'a> Update<'a> { + /// Use the provided session when running the operation. + pub fn session(mut self, value: impl Into<&'a mut ClientSession>) -> Self { + self.session = Some(value.into()); + self + } +} + +#[action_impl] +impl<'a> Action for Update<'a> { + type Future = UpdateFuture; + + async fn execute(mut self) -> Result { + if let UpdateModifications::Document(d) = &self.update { + crate::bson_util::update_document_check(d)?; + } + resolve_write_concern_with_session!(self.coll, self.options, self.session.as_ref())?; + + let op = Op::with_update( + self.coll.namespace(), + self.query, + self.update, + self.multi, + self.options, + ); + self.coll.client().execute_operation(op, self.session).await + } +} diff --git a/src/action/watch.rs b/src/action/watch.rs new file mode 100644 index 000000000..fae772d53 --- /dev/null +++ b/src/action/watch.rs @@ -0,0 +1,310 @@ +use std::{marker::PhantomData, time::Duration}; + +use crate::bson::{Bson, Document, Timestamp}; +use serde::de::DeserializeOwned; + +use super::{ + action_impl, + deeplink, + export_doc, + option_setters, + options_doc, + ExplicitSession, + ImplicitSession, +}; +use crate::{ + change_stream::{ + event::{ChangeStreamEvent, ResumeToken}, + options::{ChangeStreamOptions, FullDocumentBeforeChangeType, FullDocumentType}, + session::SessionChangeStream, + ChangeStream, + }, + collation::Collation, + error::Result, + operation::aggregate::AggregateTarget, + options::ReadConcern, + selection_criteria::SelectionCriteria, + Client, + ClientSession, + Collection, + Database, +}; + +impl Client { + /// Starts a new [`ChangeStream`] that receives events for all changes in the cluster. The + /// stream does not observe changes from system collections or the "config", "local" or + /// "admin" databases. Note that this method (`watch` on a cluster) is only supported in + /// MongoDB 4.0 or greater. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read + /// concern. Anything else will cause a server error. + /// + /// Note that using a `$project` stage to remove any of the `_id` `operationType` or `ns` fields + /// will cause an error. The driver requires these fields to support resumability. For + /// more information on resumability, see the documentation for + /// [`ChangeStream`](change_stream/struct.ChangeStream.html) + /// + /// If the pipeline alters the structure of the returned events, the parsed type will need to be + /// changed via [`ChangeStream::with_type`]. + /// + /// `await` will return d[`Result>>`] or + /// d[`Result>>`] if a + /// [`ClientSession`] has been provided. + #[deeplink] + #[options_doc(watch)] + pub fn watch(&self) -> Watch { + Watch::new_cluster(self) + } +} + +impl Database { + /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events + /// for all changes in this database. The stream does not observe changes from system + /// collections and cannot be started on "config", "local" or "admin" databases. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read + /// concern. Anything else will cause a server error. + /// + /// Note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` + /// fields will cause an error. The driver requires these fields to support resumability. For + /// more information on resumability, see the documentation for + /// [`ChangeStream`](change_stream/struct.ChangeStream.html). + /// + /// If the pipeline alters the structure of the returned events, the parsed type will need to be + /// changed via [`ChangeStream::with_type`]. + /// + /// `await` will return d[`Result>>`] or + /// d[`Result>>`] if a + /// [`ClientSession`] has been provided. + #[deeplink] + #[options_doc(watch)] + pub fn watch(&self) -> Watch { + Watch::new( + self.client(), + AggregateTarget::Database(self.name().to_string()), + ) + } +} + +impl Collection +where + T: Send + Sync, +{ + /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events + /// for all changes in this collection. A + /// [`ChangeStream`](change_stream/struct.ChangeStream.html) cannot be started on system + /// collections. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read concern. Anything else + /// will cause a server error. + /// + /// `await` will return d[`Result>>`] or + /// d[`Result>>`] if a + /// [`ClientSession`] has been provided. + #[deeplink] + #[options_doc(watch)] + pub fn watch(&self) -> Watch { + Watch::new(self.client(), self.namespace().into()) + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Client { + /// Starts a new [`ChangeStream`] that receives events for all changes in the cluster. The + /// stream does not observe changes from system collections or the "config", "local" or + /// "admin" databases. Note that this method (`watch` on a cluster) is only supported in + /// MongoDB 4.0 or greater. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read + /// concern. Anything else will cause a server error. + #[options_doc(watch, sync)] + pub fn watch(&self) -> Watch { + self.async_client.watch() + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Database { + /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events + /// for all changes in this database. The stream does not observe changes from system + /// collections and cannot be started on "config", "local" or "admin" databases. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read + /// concern. Anything else will cause a server error. + #[options_doc(watch, sync)] + pub fn watch(&self) -> Watch { + self.async_database.watch() + } +} + +#[cfg(feature = "sync")] +impl crate::sync::Collection +where + T: Send + Sync, +{ + /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events + /// for all changes in this collection. A + /// [`ChangeStream`](change_stream/struct.ChangeStream.html) cannot be started on system + /// collections. + /// + /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change + /// streams. + /// + /// Change streams require either a "majority" read concern or no read concern. Anything else + /// will cause a server error. + #[options_doc(watch, sync)] + pub fn watch(&self) -> Watch { + self.async_collection.watch() + } +} + +/// Starts a new [`ChangeStream`] that receives events for all changes in a given scope. Create by +/// calling [`Client::watch`], [`Database::watch`], or [`Collection::watch`]. +#[must_use] +pub struct Watch<'a, T = Document, S = ImplicitSession> { + client: &'a Client, + target: AggregateTarget, + pipeline: Vec, + options: Option, + session: S, + cluster: bool, + phantom: PhantomData T>, +} + +impl<'a, T> Watch<'a, T, ImplicitSession> { + fn new(client: &'a Client, target: AggregateTarget) -> Self { + Self { + client, + target, + pipeline: vec![], + options: None, + session: ImplicitSession, + cluster: false, + phantom: PhantomData, + } + } + + fn new_cluster(client: &'a Client) -> Self { + Self { + client, + target: AggregateTarget::Database("admin".to_string()), + pipeline: vec![], + options: None, + session: ImplicitSession, + cluster: true, + phantom: PhantomData, + } + } +} + +#[option_setters(crate::change_stream::options::ChangeStreamOptions, skip = [resume_after, all_changes_for_cluster])] +#[export_doc(watch, extra = [session])] +impl Watch<'_, S> { + /// Apply an aggregation pipeline to the change stream. + /// + /// Note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` + /// fields will cause an error. The driver requires these fields to support resumability. For + /// more information on resumability, see the documentation for + /// [`ChangeStream`](change_stream/struct.ChangeStream.html) + /// + /// If the pipeline alters the structure of the returned events, the parsed type will need to be + /// changed via [`ChangeStream::with_type`]. + pub fn pipeline(mut self, value: impl IntoIterator) -> Self { + self.pipeline = value.into_iter().collect(); + self + } + + /// Specifies the logical starting point for the new change stream. Note that if a watched + /// collection is dropped and recreated or newly renamed, `start_after` should be set instead. + /// `resume_after` and `start_after` cannot be set simultaneously. + /// + /// For more information on resuming a change stream see the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/#change-stream-resume-after) + pub fn resume_after(mut self, value: impl Into>) -> Self { + // Implemented manually to accept `impl Into>` so the output of + // `ChangeStream::resume_token()` can be passed in directly. + self.options().resume_after = value.into(); + self + } +} + +impl<'a, T> Watch<'a, T, ImplicitSession> { + /// Use the provided ['ClientSession']. + pub fn session<'s>( + self, + session: impl Into<&'s mut ClientSession>, + ) -> Watch<'a, T, ExplicitSession<'s>> { + Watch { + client: self.client, + target: self.target, + pipeline: self.pipeline, + options: self.options, + session: ExplicitSession(session.into()), + cluster: self.cluster, + phantom: PhantomData, + } + } +} + +#[action_impl(sync = crate::sync::ChangeStream>)] +impl<'a, T: DeserializeOwned + Unpin + Send + Sync> Action for Watch<'a, T, ImplicitSession> { + type Future = WatchFuture; + + async fn execute(mut self) -> Result>> { + resolve_options!( + self.client, + self.options, + [read_concern, selection_criteria] + ); + if self.cluster { + self.options + .get_or_insert_with(Default::default) + .all_changes_for_cluster = Some(true); + } + self.client + .execute_watch(self.pipeline, self.options, self.target, None) + .await + } +} + +#[action_impl(sync = crate::sync::SessionChangeStream>)] +impl<'a, T: DeserializeOwned + Unpin + Send + Sync> Action for Watch<'a, T, ExplicitSession<'a>> { + type Future = WatchSessionFuture; + + async fn execute(mut self) -> Result>> { + resolve_read_concern_with_session!(self.client, self.options, Some(&mut *self.session.0))?; + resolve_selection_criteria_with_session!( + self.client, + self.options, + Some(&mut *self.session.0) + )?; + if self.cluster { + self.options + .get_or_insert_with(Default::default) + .all_changes_for_cluster = Some(true); + } + self.client + .execute_watch_with_session( + self.pipeline, + self.options, + self.target, + None, + self.session.0, + ) + .await + } +} diff --git a/src/bson_compat.rs b/src/bson_compat.rs new file mode 100644 index 000000000..01a175cc2 --- /dev/null +++ b/src/bson_compat.rs @@ -0,0 +1,122 @@ +#[cfg(feature = "bson-3")] +pub(crate) type CStr = crate::bson::raw::CStr; +#[cfg(feature = "bson-3")] +pub(crate) type CString = crate::bson::raw::CString; +#[cfg(feature = "bson-3")] +pub(crate) use crate::bson::raw::cstr; + +#[cfg(not(feature = "bson-3"))] +pub(crate) type CStr = str; +#[cfg(not(feature = "bson-3"))] +pub(crate) type CString = String; +#[cfg(not(feature = "bson-3"))] +macro_rules! cstr { + ($text:literal) => { + $text + }; +} +#[cfg(not(feature = "bson-3"))] +pub(crate) use cstr; + +pub(crate) fn cstr_to_str(cs: &CStr) -> &str { + #[cfg(feature = "bson-3")] + { + cs.as_str() + } + #[cfg(not(feature = "bson-3"))] + { + cs + } +} + +pub(crate) trait RawDocumentBufExt: Sized { + fn append_ref_compat<'a>( + &mut self, + key: impl AsRef, + value: impl Into> + 'a, + ); + + #[cfg(not(feature = "bson-3"))] + fn decode_from_bytes(data: Vec) -> RawResult; +} + +#[cfg(feature = "bson-3")] +impl RawDocumentBufExt for crate::bson::RawDocumentBuf { + fn append_ref_compat<'a>( + &mut self, + key: impl AsRef, + value: impl Into> + 'a, + ) { + self.append(key, value); + } +} + +#[cfg(not(feature = "bson-3"))] +impl RawDocumentBufExt for crate::bson::RawDocumentBuf { + fn append_ref_compat<'a>( + &mut self, + key: impl AsRef, + value: impl Into>, + ) { + self.append_ref(key, value) + } + + fn decode_from_bytes(data: Vec) -> RawResult { + Self::from_bytes(data) + } +} + +#[cfg(not(feature = "bson-3"))] +pub(crate) trait RawDocumentExt { + fn decode_from_bytes + ?Sized>(data: &D) -> RawResult<&Self>; +} + +#[cfg(not(feature = "bson-3"))] +impl RawDocumentExt for crate::bson::RawDocument { + fn decode_from_bytes + ?Sized>(data: &D) -> RawResult<&Self> { + Self::from_bytes(data) + } +} + +#[cfg(not(feature = "bson-3"))] +#[allow(dead_code)] +pub(crate) trait DocumentExt { + fn encode_to_vec(&self) -> crate::bson::ser::Result>; +} + +#[cfg(not(feature = "bson-3"))] +impl DocumentExt for crate::bson::Document { + fn encode_to_vec(&self) -> crate::bson::ser::Result> { + let mut out = vec![]; + self.to_writer(&mut out)?; + Ok(out) + } +} + +macro_rules! use_either { + ($($name:ident => $path3:path | $path2:path);+;) => { + $( + #[cfg(feature = "bson-3")] + #[allow(unused_imports)] + pub(crate) use crate::bson::{$path3 as $name}; + + #[cfg(not(feature = "bson-3"))] + #[allow(unused_imports)] + pub(crate) use crate::bson::{$path2 as $name}; + )+ + }; +} + +// Exported name => bson3 import | bson2 import +use_either! { + RawResult => error::Result | raw::Result; + RawError => error::Error | raw::Error; + DeError => error::Error | de::Error; + SerError => error::Error | ser::Error; + serialize_to_raw_document_buf => serialize_to_raw_document_buf | to_raw_document_buf; + serialize_to_document => serialize_to_document | to_document; + serialize_to_bson => serialize_to_bson | to_bson; + deserialize_from_slice => deserialize_from_slice | from_slice; + deserialize_from_document => deserialize_from_document | from_document; + deserialize_from_bson => deserialize_from_bson | from_bson; +} diff --git a/src/bson_util.rs b/src/bson_util.rs new file mode 100644 index 000000000..c5c7f9cfb --- /dev/null +++ b/src/bson_util.rs @@ -0,0 +1,264 @@ +use std::{ + collections::HashSet, + convert::TryFrom, + io::{Read, Write}, +}; + +use serde::Serialize; + +use crate::{ + bson::{ + oid::ObjectId, + rawdoc, + Bson, + Document, + RawArrayBuf, + RawBson, + RawBsonRef, + RawDocumentBuf, + }, + bson_compat::RawDocumentBufExt as _, + checked::Checked, + error::{Error, ErrorKind, Result}, + runtime::SyncLittleEndianRead, +}; + +/// Coerce numeric types into an `i64` if it would be lossless to do so. If this Bson is not numeric +/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn get_int(val: &Bson) -> Option { + match *val { + Bson::Int32(i) => Some(i64::from(i)), + Bson::Int64(i) => Some(i), + Bson::Double(f) if (f - (f as i64 as f64)).abs() <= f64::EPSILON => Some(f as i64), + _ => None, + } +} + +/// Coerce numeric types into an `f64` if it would be lossless to do so. If this Bson is not numeric +/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. +#[cfg(test)] +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn get_double(val: &Bson) -> Option { + match *val { + Bson::Int32(i) => Some(f64::from(i)), + Bson::Int64(i) if i == i as f64 as i64 => Some(i as f64), + Bson::Double(f) => Some(f), + _ => None, + } +} + +/// Coerce numeric types into an `i64` if it would be lossless to do so. If this Bson is not numeric +/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. +pub(crate) fn get_int_raw(val: RawBsonRef<'_>) -> Option { + match val { + RawBsonRef::Int32(i) => get_int(&Bson::Int32(i)), + RawBsonRef::Int64(i) => get_int(&Bson::Int64(i)), + RawBsonRef::Double(i) => get_int(&Bson::Double(i)), + _ => None, + } +} + +/// Coerce numeric types into an `u64` if it would be lossless to do so. If this Bson is not numeric +/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn get_u64(val: &Bson) -> Option { + match *val { + Bson::Int32(i) => u64::try_from(i).ok(), + Bson::Int64(i) => u64::try_from(i).ok(), + Bson::Double(f) if (f - (f as u64 as f64)).abs() <= f64::EPSILON => Some(f as u64), + _ => None, + } +} + +pub(crate) fn to_bson_array(docs: &[Document]) -> Bson { + Bson::Array(docs.iter().map(|doc| Bson::Document(doc.clone())).collect()) +} + +pub(crate) fn to_raw_bson_array(docs: &[Document]) -> Result { + let mut array = RawArrayBuf::new(); + for doc in docs { + array.push(RawDocumentBuf::from_document(doc)?); + } + Ok(RawBson::Array(array)) +} +pub(crate) fn to_raw_bson_array_ser(values: &[T]) -> Result { + let mut array = RawArrayBuf::new(); + for value in values { + array.push(crate::bson_compat::serialize_to_raw_document_buf(value)?); + } + Ok(RawBson::Array(array)) +} + +pub(crate) fn first_key(document: &Document) -> Option<&str> { + document.keys().next().map(String::as_str) +} + +pub(crate) fn update_document_check(update: &Document) -> Result<()> { + match first_key(update) { + Some(key) => { + if !key.starts_with('$') { + Err(ErrorKind::InvalidArgument { + message: "update document must only contain update modifiers".to_string(), + } + .into()) + } else { + Ok(()) + } + } + None => Err(ErrorKind::InvalidArgument { + message: "update document must not be empty".to_string(), + } + .into()), + } +} + +pub(crate) fn replacement_document_check(replacement: &Document) -> Result<()> { + if let Some(key) = first_key(replacement) { + if key.starts_with('$') { + return Err(ErrorKind::InvalidArgument { + message: "replacement document must not contain update modifiers".to_string(), + } + .into()); + } + } + Ok(()) +} + +pub(crate) fn replacement_raw_document_check(replacement: &RawDocumentBuf) -> Result<()> { + if let Some((key, _)) = replacement.iter().next().transpose()? { + if crate::bson_compat::cstr_to_str(key).starts_with('$') { + return Err(ErrorKind::InvalidArgument { + message: "replacement document must not contain update modifiers".to_string(), + } + .into()); + }; + } + Ok(()) +} + +/// The size in bytes of the provided document's entry in a BSON array at the given index. +pub(crate) fn array_entry_size_bytes(index: usize, doc_len: usize) -> Result { + // * type (1 byte) + // * number of decimal digits in key + // * null terminator for the key (1 byte) + // * size of value + + (Checked::new(1) + num_decimal_digits(index) + 1 + doc_len).get() +} + +pub(crate) fn vec_to_raw_array_buf(docs: Vec) -> RawArrayBuf { + let mut array = RawArrayBuf::new(); + for doc in docs { + array.push(doc); + } + array +} + +/// The number of digits in `n` in base 10. +/// Useful for calculating the size of an array entry in BSON. +fn num_decimal_digits(mut n: usize) -> usize { + let mut digits = 0; + + loop { + n /= 10; + digits += 1; + + if n == 0 { + return digits; + } + } +} + +/// Read a document's raw BSON bytes from the provided reader. +pub(crate) fn read_document_bytes(mut reader: R) -> Result> { + let length = Checked::new(reader.read_i32_sync()?); + + let mut bytes = Vec::with_capacity(length.try_into()?); + bytes.write_all(&length.try_into::()?.to_le_bytes())?; + + reader + .take((length - 4).try_into()?) + .read_to_end(&mut bytes)?; + + Ok(bytes) +} + +pub(crate) fn extend_raw_document_buf( + this: &mut RawDocumentBuf, + other: RawDocumentBuf, +) -> Result<()> { + let mut keys: HashSet = HashSet::new(); + for elem in this.iter_elements() { + keys.insert(elem?.key().to_owned()); + } + for result in other.iter() { + let (k, v) = result?; + if keys.contains(k) { + return Err(Error::internal(format!( + "duplicate raw document key {:?}", + k + ))); + } + this.append(k, v.to_raw_bson()); + } + Ok(()) +} + +pub(crate) fn append_ser( + this: &mut RawDocumentBuf, + key: impl AsRef, + value: impl Serialize, +) -> Result<()> { + #[derive(Serialize)] + struct Helper { + value: T, + } + let raw_doc = crate::bson_compat::serialize_to_raw_document_buf(&Helper { value })?; + this.append_ref_compat( + key, + raw_doc + .get("value")? + .ok_or_else(|| Error::internal("no value"))?, + ); + Ok(()) +} + +/// Returns the _id field of this document, prepending the field to the document if one is not +/// already present. +pub(crate) fn get_or_prepend_id_field(doc: &mut RawDocumentBuf) -> Result { + match doc.get("_id")? { + Some(id) => Ok(id.try_into()?), + None => { + let id = ObjectId::new(); + let mut new_bytes = rawdoc! { "_id": id }.into_bytes(); + + // Remove the trailing null byte (which will be replaced by the null byte in the given + // document) and append the document's elements + new_bytes.pop(); + new_bytes.extend(&doc.as_bytes()[4..]); + + let new_length: i32 = Checked::new(new_bytes.len()).try_into()?; + new_bytes[0..4].copy_from_slice(&new_length.to_le_bytes()); + + *doc = RawDocumentBuf::decode_from_bytes(new_bytes)?; + + Ok(id.into()) + } + } +} + +#[cfg(test)] +mod test { + use crate::bson_util::num_decimal_digits; + + #[test] + fn num_digits() { + assert_eq!(num_decimal_digits(0), 1); + assert_eq!(num_decimal_digits(1), 1); + assert_eq!(num_decimal_digits(10), 2); + assert_eq!(num_decimal_digits(15), 2); + assert_eq!(num_decimal_digits(100), 3); + assert_eq!(num_decimal_digits(125), 3); + } +} diff --git a/src/bson_util/mod.rs b/src/bson_util/mod.rs deleted file mode 100644 index a92969c78..000000000 --- a/src/bson_util/mod.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::{ - convert::TryFrom, - io::{Read, Write}, - time::Duration, -}; - -use bson::RawBsonRef; -use serde::{de::Error as SerdeDeError, ser, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::{ - bson::{doc, Bson, Document}, - error::{Error, ErrorKind, Result}, - runtime::SyncLittleEndianRead, -}; - -/// Coerce numeric types into an `i64` if it would be lossless to do so. If this Bson is not numeric -/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. -pub(crate) fn get_int(val: &Bson) -> Option { - match *val { - Bson::Int32(i) => Some(i64::from(i)), - Bson::Int64(i) => Some(i), - Bson::Double(f) if (f - (f as i64 as f64)).abs() <= f64::EPSILON => Some(f as i64), - _ => None, - } -} - -/// Coerce numeric types into an `i64` if it would be lossless to do so. If this Bson is not numeric -/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. -pub(crate) fn get_int_raw(val: RawBsonRef<'_>) -> Option { - match val { - RawBsonRef::Int32(i) => get_int(&Bson::Int32(i)), - RawBsonRef::Int64(i) => get_int(&Bson::Int64(i)), - RawBsonRef::Double(i) => get_int(&Bson::Double(i)), - _ => None, - } -} - -/// Coerce numeric types into an `u64` if it would be lossless to do so. If this Bson is not numeric -/// or the conversion would be lossy (e.g. 1.5 -> 1), this returns `None`. -pub(crate) fn get_u64(val: &Bson) -> Option { - match *val { - Bson::Int32(i) => u64::try_from(i).ok(), - Bson::Int64(i) => u64::try_from(i).ok(), - Bson::Double(f) if (f - (f as u64 as f64)).abs() <= f64::EPSILON => Some(f as u64), - _ => None, - } -} - -pub(crate) fn to_bson_array(docs: &[Document]) -> Bson { - Bson::Array(docs.iter().map(|doc| Bson::Document(doc.clone())).collect()) -} - -#[cfg(test)] -pub(crate) fn sort_document(document: &mut Document) { - let temp = std::mem::take(document); - - let mut elements: Vec<_> = temp.into_iter().collect(); - elements.sort_by(|e1, e2| e1.0.cmp(&e2.0)); - - document.extend(elements); -} - -pub(crate) fn first_key(document: &Document) -> Option<&str> { - document.keys().next().map(String::as_str) -} - -pub(crate) fn replacement_document_check(replacement: &Document) -> Result<()> { - match first_key(replacement) { - Some(s) if !s.starts_with('$') => Ok(()), - _ => Err(ErrorKind::InvalidArgument { - message: "replace document must have first key not starting with '$".to_string(), - } - .into()), - } -} - -pub(crate) fn update_document_check(update: &Document) -> Result<()> { - match first_key(update) { - Some(s) if s.starts_with('$') => Ok(()), - _ => Err(ErrorKind::InvalidArgument { - message: "update document must have first key starting with '$".to_string(), - } - .into()), - } -} - -pub(crate) fn serialize_duration_option_as_int_millis( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(duration) if duration.as_millis() > i32::MAX as u128 => { - serializer.serialize_i64(duration.as_millis() as i64) - } - Some(duration) => serializer.serialize_i32(duration.as_millis() as i32), - None => serializer.serialize_none(), - } -} - -pub(crate) fn serialize_duration_option_as_int_secs( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(duration) if duration.as_secs() > i32::MAX as u64 => { - serializer.serialize_i64(duration.as_secs() as i64) - } - Some(duration) => serializer.serialize_i32(duration.as_secs() as i32), - None => serializer.serialize_none(), - } -} - -pub(crate) fn deserialize_duration_option_from_u64_millis<'de, D>( - deserializer: D, -) -> std::result::Result, D::Error> -where - D: Deserializer<'de>, -{ - let millis = Option::::deserialize(deserializer)?; - Ok(millis.map(Duration::from_millis)) -} - -pub(crate) fn deserialize_duration_option_from_u64_seconds<'de, D>( - deserializer: D, -) -> std::result::Result, D::Error> -where - D: Deserializer<'de>, -{ - let millis = Option::::deserialize(deserializer)?; - Ok(millis.map(Duration::from_secs)) -} - -#[allow(clippy::trivially_copy_pass_by_ref)] -pub(crate) fn serialize_u32_option_as_i32( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(ref val) => bson::serde_helpers::serialize_u32_as_i32(val, serializer), - None => serializer.serialize_none(), - } -} - -#[allow(clippy::trivially_copy_pass_by_ref)] -pub(crate) fn serialize_u32_option_as_batch_size( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(val) if *val <= std::i32::MAX as u32 => (doc! { - "batchSize": (*val as i32) - }) - .serialize(serializer), - None => Document::new().serialize(serializer), - _ => Err(ser::Error::custom( - "batch size must be able to fit into a signed 32-bit integer", - )), - } -} - -pub(crate) fn serialize_u64_option_as_i64( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(ref v) => bson::serde_helpers::serialize_u64_as_i64(v, serializer), - None => serializer.serialize_none(), - } -} - -/// Deserialize an u64 from any BSON number type if it could be done losslessly. -pub(crate) fn deserialize_u64_from_bson_number<'de, D>( - deserializer: D, -) -> std::result::Result -where - D: Deserializer<'de>, -{ - let bson = Bson::deserialize(deserializer)?; - get_u64(&bson) - .ok_or_else(|| D::Error::custom(format!("could not deserialize u64 from {:?}", bson))) -} - -/// The size in bytes of the provided document's entry in a BSON array at the given index. -pub(crate) fn array_entry_size_bytes(index: usize, doc_len: usize) -> u64 { - // * type (1 byte) - // * number of decimal digits in key - // * null terminator for the key (1 byte) - // * size of value - - 1 + num_decimal_digits(index) + 1 + doc_len as u64 -} - -/// The number of digits in `n` in base 10. -/// Useful for calculating the size of an array entry in BSON. -fn num_decimal_digits(mut n: usize) -> u64 { - let mut digits = 0; - - loop { - n /= 10; - digits += 1; - - if n == 0 { - return digits; - } - } -} - -/// Read a document's raw BSON bytes from the provided reader. -pub(crate) fn read_document_bytes(mut reader: R) -> Result> { - let length = reader.read_i32_sync()?; - - let mut bytes = Vec::with_capacity(length as usize); - bytes.write_all(&length.to_le_bytes())?; - - reader.take(length as u64 - 4).read_to_end(&mut bytes)?; - - Ok(bytes) -} - -/// Serializes an Error as a string. -pub(crate) fn serialize_error_as_string( - val: &Error, - serializer: S, -) -> std::result::Result { - serializer.serialize_str(&val.to_string()) -} - -/// Serializes a Result, serializing the error value as a string if present. -pub(crate) fn serialize_result_error_as_string( - val: &Result, - serializer: S, -) -> std::result::Result { - val.as_ref() - .map_err(|e| e.to_string()) - .serialize(serializer) -} - -#[cfg(test)] -mod test { - use crate::bson_util::num_decimal_digits; - - #[test] - fn num_digits() { - assert_eq!(num_decimal_digits(0), 1); - assert_eq!(num_decimal_digits(1), 1); - assert_eq!(num_decimal_digits(10), 2); - assert_eq!(num_decimal_digits(15), 2); - assert_eq!(num_decimal_digits(100), 3); - assert_eq!(num_decimal_digits(125), 3); - } -} diff --git a/src/change_stream.rs b/src/change_stream.rs new file mode 100644 index 000000000..12ba1ae12 --- /dev/null +++ b/src/change_stream.rs @@ -0,0 +1,320 @@ +//! Contains the functionality for change streams. +pub mod event; +pub(crate) mod options; +pub mod session; + +#[cfg(test)] +use std::collections::VecDeque; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +#[cfg(test)] +use crate::bson::RawDocumentBuf; +use crate::bson::{Document, Timestamp}; +use derive_where::derive_where; +use futures_core::{future::BoxFuture, Stream}; +use serde::de::DeserializeOwned; +#[cfg(test)] +use tokio::sync::oneshot; + +use crate::{ + change_stream::event::{ChangeStreamEvent, ResumeToken}, + cursor::{stream_poll_next, BatchValue, CursorStream, NextInBatchFuture}, + error::{ErrorKind, Result}, + operation::aggregate::AggregateTarget, + ClientSession, + Cursor, +}; + +/// A `ChangeStream` streams the ongoing changes of its associated collection, database or +/// deployment. `ChangeStream` instances should be created with method `watch` against the relevant +/// target. +/// +/// `ChangeStream`s are "resumable", meaning that they can be restarted at a given place in the +/// stream of events. This is done automatically when the `ChangeStream` encounters certain +/// ["resumable"](https://specifications.readthedocs.io/en/latest/change-streams/change-streams/#resumable-error) +/// errors, such as transient network failures. It can also be done manually by passing +/// a [`ResumeToken`] retrieved from a past event into either the +/// [`resume_after`](crate::action::Watch::resume_after) or +/// [`start_after`](crate::action::Watch::start_after) (4.2+) options used to create the +/// `ChangeStream`. Issuing a raw change stream aggregation is discouraged unless users wish to +/// explicitly opt out of resumability. +/// +/// A `ChangeStream` can be iterated like any other [`Stream`]: +/// +/// ``` +/// # use futures::stream::StreamExt; +/// # use mongodb::{Client, error::Result, bson::doc, +/// # change_stream::event::ChangeStreamEvent}; +/// # use tokio::task; +/// # +/// # async fn func() -> Result<()> { +/// # let client = Client::with_uri_str("mongodb://example.com").await?; +/// # let coll = client.database("foo").collection("bar"); +/// let mut change_stream = coll.watch().await?; +/// let coll_ref = coll.clone(); +/// task::spawn(async move { +/// coll_ref.insert_one(doc! { "x": 1 }).await; +/// }); +/// while let Some(event) = change_stream.next().await.transpose()? { +/// println!("operation performed: {:?}, document: {:?}", event.operation_type, event.full_document); +/// // operation performed: Insert, document: Some(Document({"x": Int32(1)})) +/// } +/// # +/// # Ok(()) +/// # } +/// ``` +/// +/// If a [`ChangeStream`] is still open when it goes out of scope, it will automatically be closed +/// via an asynchronous [killCursors](https://www.mongodb.com/docs/manual/reference/command/killCursors/) command executed +/// from its [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation. +/// +/// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams) for more +/// details. Also see the documentation on [usage recommendations](https://www.mongodb.com/docs/manual/administration/change-streams-production-recommendations/). +#[derive_where(Debug)] +pub struct ChangeStream +where + T: DeserializeOwned, +{ + /// The cursor to iterate over event instances. + cursor: Cursor, + + /// Arguments to `watch` that created this change stream. + args: WatchArgs, + + /// Dynamic information associated with this change stream. + data: ChangeStreamData, + + /// A pending future for a resume. + #[derive_where(skip)] + pending_resume: Option>>>, +} + +impl ChangeStream +where + T: DeserializeOwned, +{ + pub(crate) fn new(cursor: Cursor, args: WatchArgs, data: ChangeStreamData) -> Self { + let pending_resume: Option>>> = None; + Self { + cursor, + args, + data, + pending_resume, + } + } + + /// Returns the cached resume token that can be used to resume after the most recently returned + /// change. + /// + /// See the documentation + /// [here](https://www.mongodb.com/docs/manual/changeStreams/#change-stream-resume-token) for more + /// information on change stream resume tokens. + pub fn resume_token(&self) -> Option { + self.data.resume_token.clone() + } + + /// Update the type streamed values will be parsed as. + pub fn with_type(self) -> ChangeStream { + ChangeStream { + cursor: self.cursor.with_type(), + args: self.args, + data: self.data, + pending_resume: None, + } + } + + /// Returns whether the change stream will continue to receive events. + pub fn is_alive(&self) -> bool { + !self.cursor.is_exhausted() + } + + /// Retrieves the next result from the change stream, if any. + /// + /// Where calling `Stream::next` will internally loop until a change document is received, + /// this will make at most one request and return `None` if the returned document batch is + /// empty. This method should be used when storing the resume token in order to ensure the + /// most up to date token is received, e.g. + /// + /// ``` + /// # use mongodb::{Client, Collection, bson::Document, error::Result}; + /// # async fn func() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let coll: Collection = client.database("foo").collection("bar"); + /// let mut change_stream = coll.watch().await?; + /// let mut resume_token = None; + /// while change_stream.is_alive() { + /// if let Some(event) = change_stream.next_if_any().await? { + /// // process event + /// } + /// resume_token = change_stream.resume_token(); + /// } + /// # + /// # Ok(()) + /// # } + /// ``` + pub async fn next_if_any(&mut self) -> Result> { + Ok(match NextInBatchFuture::new(self).await? { + BatchValue::Some { doc, .. } => { + Some(crate::bson_compat::deserialize_from_slice(doc.as_bytes())?) + } + BatchValue::Empty | BatchValue::Exhausted => None, + }) + } + + #[cfg(test)] + pub(crate) fn set_kill_watcher(&mut self, tx: oneshot::Sender<()>) { + self.cursor.set_kill_watcher(tx); + } + + #[cfg(test)] + pub(crate) fn current_batch(&self) -> &VecDeque { + self.cursor.current_batch() + } + + #[cfg(test)] + pub(crate) fn client(&self) -> &crate::Client { + self.cursor.client() + } +} + +/// Arguments passed to a `watch` method, captured to allow resume. +#[derive(Debug, Clone)] +pub(crate) struct WatchArgs { + /// The pipeline of stages to append to an initial `$changeStream` stage. + pub(crate) pipeline: Vec, + + /// The original target of the change stream. + pub(crate) target: AggregateTarget, + + /// The options provided to the initial `$changeStream` stage. + pub(crate) options: Option, +} + +/// Dynamic change stream data needed for resume. +#[derive(Debug, Default)] +pub(crate) struct ChangeStreamData { + /// The `operationTime` returned by the initial `aggregate` command. + pub(crate) initial_operation_time: Option, + + /// The cached resume token. + pub(crate) resume_token: Option, + + /// Whether or not the change stream has attempted a resume, used to attempt a resume only + /// once. + pub(crate) resume_attempted: bool, + + /// Whether or not the change stream has returned a document, used to update resume token + /// during an automatic resume. + pub(crate) document_returned: bool, + + /// The implicit session used to create the original cursor. + pub(crate) implicit_session: Option, +} + +impl ChangeStreamData { + fn take(&mut self) -> Self { + Self { + initial_operation_time: self.initial_operation_time, + resume_token: self.resume_token.clone(), + resume_attempted: self.resume_attempted, + document_returned: self.document_returned, + implicit_session: self.implicit_session.take(), + } + } +} + +fn get_resume_token( + batch_value: &BatchValue, + batch_token: Option<&ResumeToken>, +) -> Result> { + Ok(match batch_value { + BatchValue::Some { doc, is_last } => { + let doc_token = match doc.get("_id")? { + Some(val) => ResumeToken(val.to_raw_bson()), + None => return Err(ErrorKind::MissingResumeToken.into()), + }; + if *is_last && batch_token.is_some() { + batch_token.cloned() + } else { + Some(doc_token) + } + } + BatchValue::Empty => batch_token.cloned(), + _ => None, + }) +} + +impl CursorStream for ChangeStream +where + T: DeserializeOwned, +{ + fn poll_next_in_batch(&mut self, cx: &mut Context<'_>) -> Poll> { + loop { + if let Some(mut pending) = self.pending_resume.take() { + match Pin::new(&mut pending).poll(cx) { + Poll::Pending => { + self.pending_resume = Some(pending); + return Poll::Pending; + } + Poll::Ready(Ok(new_stream)) => { + // Ensure that the old cursor is killed on the server selected for the new + // one. + self.cursor + .set_drop_address(new_stream.cursor.address().clone()); + self.cursor = new_stream.cursor; + self.args = new_stream.args; + // After a successful resume, another resume must be allowed. + self.data.resume_attempted = false; + continue; + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + } + } + let out = self.cursor.poll_next_in_batch(cx); + match &out { + Poll::Ready(Ok(bv)) => { + if let Some(token) = + get_resume_token(bv, self.cursor.post_batch_resume_token())? + { + self.data.resume_token = Some(token); + } + if matches!(bv, BatchValue::Some { .. }) { + self.data.document_returned = true; + } + } + Poll::Ready(Err(e)) if e.is_resumable() && !self.data.resume_attempted => { + self.data.resume_attempted = true; + let client = self.cursor.client().clone(); + let args = self.args.clone(); + let mut data = self.data.take(); + data.implicit_session = self.cursor.take_implicit_session(); + self.pending_resume = Some(Box::pin(async move { + let new_stream: Result>> = client + .execute_watch(args.pipeline, args.options, args.target, Some(data)) + .await; + new_stream.map(|cs| cs.with_type::()) + })); + // Iterate the loop so the new future gets polled and can register wakers. + continue; + } + _ => {} + } + return out; + } + } +} + +impl Stream for ChangeStream +where + T: DeserializeOwned, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + stream_poll_next(Pin::into_inner(self), cx) + } +} diff --git a/src/change_stream/event.rs b/src/change_stream/event.rs index d1587dcce..e0b510dd4 100644 --- a/src/change_stream/event.rs +++ b/src/change_stream/event.rs @@ -4,17 +4,17 @@ use std::convert::TryInto; use crate::{cursor::CursorSpecification, options::ChangeStreamOptions}; +use crate::bson::{DateTime, Document, RawBson, RawDocumentBuf, Timestamp}; #[cfg(test)] -use bson::Bson; -use bson::{DateTime, Document, RawBson, RawDocumentBuf, Timestamp}; +use crate::{bson::Bson, bson_compat::RawError}; use serde::{Deserialize, Serialize}; /// An opaque token used for resuming an interrupted /// [`ChangeStream`](crate::change_stream::ChangeStream). /// /// When starting a new change stream, -/// [`crate::options::ChangeStreamOptions::start_after`] and -/// [`crate::options::ChangeStreamOptions::resume_after`] fields can be specified +/// [`crate::action::Watch::start_after`] and +/// [`crate::action::Watch::resume_after`] fields can be specified /// with instances of `ResumeToken`. /// /// See the documentation @@ -43,7 +43,7 @@ impl ResumeToken { } #[cfg(test)] - pub fn parsed(self) -> std::result::Result { + pub(crate) fn parsed(self) -> std::result::Result { self.0.try_into() } } @@ -71,9 +71,20 @@ pub struct ChangeStreamEvent { /// Identifies the collection or database on which the event occurred. pub ns: Option, + /// The type of the newly created object. Only included for `OperationType::Create`. + pub ns_type: Option, + /// The new name for the `ns` collection. Only included for `OperationType::Rename`. pub to: Option, + /// The identifier for the session associated with the transaction. + /// Only present if the operation is part of a multi-document transaction. + pub lsid: Option, + + /// Together with the lsid, a number that helps uniquely identify a transaction. + /// Only present if the operation is part of a multi-document transaction. + pub txn_number: Option, + /// A `Document` that contains the `_id` of the document created or modified by the `insert`, /// `replace`, `delete`, `update` operations (i.e. CRUD operations). For sharded collections, /// also displays the full shard key for the document. The `_id` field is not repeated if it is @@ -97,7 +108,7 @@ pub struct ChangeStreamEvent { /// operation. For `delete` operations, this field is `None`. /// /// For `update` operations, this field only appears if you configured the change stream with - /// [`full_document`](crate::options::ChangeStreamOptions::full_document) set to + /// [`full_document`](crate::action::Watch::full_document) set to /// [`UpdateLookup`](crate::options::FullDocumentType::UpdateLookup). This field then /// represents the most current majority-committed version of the document modified by the /// update operation. @@ -106,7 +117,7 @@ pub struct ChangeStreamEvent { /// Contains the pre-image of the modified or deleted document if the pre-image is available /// for the change event and either `Required` or `WhenAvailable` was specified for the /// [`full_document_before_change`]( - /// crate::options::ChangeStreamOptions::full_document_before_change) option when creating the + /// crate::action::Watch::full_document_before_change) option when creating the /// change stream. If `WhenAvailable` was specified but the pre-image is unavailable, this /// will be explicitly set to `None`. pub full_document_before_change: Option, @@ -126,6 +137,12 @@ pub struct UpdateDescription { /// Arrays that were truncated in the `Document`. pub truncated_arrays: Option>, + + /// When an update event reports changes involving ambiguous fields, the disambiguatedPaths + /// document provides the path key with an array listing each path component. + /// Note: The disambiguatedPaths field is only available on change streams started with the + /// showExpandedEvents option + pub disambiguated_paths: Option, } /// Describes an array that has been truncated. @@ -208,7 +225,7 @@ impl<'a> From<&'a OperationType> for OperationTypeWrapper<'a> { } } -impl<'a> From> for OperationType { +impl From> for OperationType { fn from(src: OperationTypeWrapper) -> Self { match src { OperationTypeWrapper::Known(h) => match h { @@ -254,3 +271,18 @@ pub struct ChangeNamespace { /// The name of the collection in which the change occurred. pub coll: Option, } + +/// Identifies the type of object for a `create` event. +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum ChangeNamespaceType { + /// A collection with no special options set. + Collection, + /// A timeseries collection. + Timeseries, + /// A view collection. + View, + /// Forward compatibility fallthrough. + #[serde(untagged)] + Other(String), +} diff --git a/src/change_stream/mod.rs b/src/change_stream/mod.rs deleted file mode 100644 index 74d7d9c38..000000000 --- a/src/change_stream/mod.rs +++ /dev/null @@ -1,326 +0,0 @@ -//! Contains the functionality for change streams. -pub mod event; -pub(crate) mod options; -pub mod session; - -#[cfg(test)] -use std::collections::VecDeque; -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - -#[cfg(test)] -use bson::RawDocumentBuf; -use bson::{Document, Timestamp}; -use derivative::Derivative; -use futures_core::{future::BoxFuture, Stream}; -use serde::de::DeserializeOwned; -#[cfg(test)] -use tokio::sync::oneshot; - -use crate::{ - change_stream::{ - event::{ChangeStreamEvent, ResumeToken}, - options::ChangeStreamOptions, - }, - cursor::{stream_poll_next, BatchValue, CursorStream, NextInBatchFuture}, - error::{ErrorKind, Result}, - operation::AggregateTarget, - ClientSession, - Cursor, -}; - -/// A `ChangeStream` streams the ongoing changes of its associated collection, database or -/// deployment. `ChangeStream` instances should be created with method `watch` against the relevant -/// target. -/// -/// `ChangeStream`s are "resumable", meaning that they can be restarted at a given place in the -/// stream of events. This is done automatically when the `ChangeStream` encounters certain -/// ["resumable"](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error) -/// errors, such as transient network failures. It can also be done manually by passing -/// a [`ResumeToken`] retrieved from a past event into either the -/// [`resume_after`](ChangeStreamOptions::resume_after) or -/// [`start_after`](ChangeStreamOptions::start_after) (4.2+) options used to create the -/// `ChangeStream`. Issuing a raw change stream aggregation is discouraged unless users wish to -/// explicitly opt out of resumability. -/// -/// A `ChangeStream` can be iterated like any other [`Stream`]: -/// -/// ``` -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # use futures::stream::StreamExt; -/// # use mongodb::{Client, error::Result, bson::doc, -/// # change_stream::event::ChangeStreamEvent}; -/// # #[cfg(feature = "async-std-runtime")] -/// # use async_std::task; -/// # #[cfg(feature = "tokio-runtime")] -/// # use tokio::task; -/// # -/// # async fn func() -> Result<()> { -/// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// # let coll = client.database("foo").collection("bar"); -/// let mut change_stream = coll.watch(None, None).await?; -/// let coll_ref = coll.clone(); -/// task::spawn(async move { -/// coll_ref.insert_one(doc! { "x": 1 }, None).await; -/// }); -/// while let Some(event) = change_stream.next().await.transpose()? { -/// println!("operation performed: {:?}, document: {:?}", event.operation_type, event.full_document); -/// // operation performed: Insert, document: Some(Document({"x": Int32(1)})) -/// } -/// # -/// # Ok(()) -/// # } -/// ``` -/// -/// If a [`ChangeStream`] is still open when it goes out of scope, it will automatically be closed -/// via an asynchronous [killCursors](https://www.mongodb.com/docs/manual/reference/command/killCursors/) command executed -/// from its [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation. -/// -/// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams) for more -/// details. Also see the documentation on [usage recommendations](https://www.mongodb.com/docs/manual/administration/change-streams-production-recommendations/). -#[derive(Derivative)] -#[derivative(Debug)] -pub struct ChangeStream -where - T: DeserializeOwned, -{ - /// The cursor to iterate over event instances. - cursor: Cursor, - - /// Arguments to `watch` that created this change stream. - args: WatchArgs, - - /// Dynamic information associated with this change stream. - data: ChangeStreamData, - - /// A pending future for a resume. - #[derivative(Debug = "ignore")] - pending_resume: Option>>>, -} - -impl ChangeStream -where - T: DeserializeOwned, -{ - pub(crate) fn new(cursor: Cursor, args: WatchArgs, data: ChangeStreamData) -> Self { - let pending_resume: Option>>> = None; - Self { - cursor, - args, - data, - pending_resume, - } - } - - /// Returns the cached resume token that can be used to resume after the most recently returned - /// change. - /// - /// See the documentation - /// [here](https://www.mongodb.com/docs/manual/changeStreams/#change-stream-resume-token) for more - /// information on change stream resume tokens. - pub fn resume_token(&self) -> Option { - self.data.resume_token.clone() - } - - /// Update the type streamed values will be parsed as. - pub fn with_type(self) -> ChangeStream { - ChangeStream { - cursor: self.cursor.with_type(), - args: self.args, - data: self.data, - pending_resume: None, - } - } - - /// Returns whether the change stream will continue to receive events. - pub fn is_alive(&self) -> bool { - !self.cursor.is_exhausted() - } - - /// Retrieves the next result from the change stream, if any. - /// - /// Where calling `Stream::next` will internally loop until a change document is received, - /// this will make at most one request and return `None` if the returned document batch is - /// empty. This method should be used when storing the resume token in order to ensure the - /// most up to date token is received, e.g. - /// - /// ``` - /// # use mongodb::{Client, Collection, bson::Document, error::Result}; - /// # async fn func() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com").await?; - /// # let coll: Collection = client.database("foo").collection("bar"); - /// let mut change_stream = coll.watch(None, None).await?; - /// let mut resume_token = None; - /// while change_stream.is_alive() { - /// if let Some(event) = change_stream.next_if_any().await? { - /// // process event - /// } - /// resume_token = change_stream.resume_token(); - /// } - /// # - /// # Ok(()) - /// # } - /// ``` - pub async fn next_if_any(&mut self) -> Result> { - Ok(match NextInBatchFuture::new(self).await? { - BatchValue::Some { doc, .. } => Some(bson::from_slice(doc.as_bytes())?), - BatchValue::Empty | BatchValue::Exhausted => None, - }) - } - - #[cfg(test)] - pub(crate) fn set_kill_watcher(&mut self, tx: oneshot::Sender<()>) { - self.cursor.set_kill_watcher(tx); - } - - #[cfg(test)] - pub(crate) fn current_batch(&self) -> &VecDeque { - self.cursor.current_batch() - } - - #[cfg(test)] - pub(crate) fn client(&self) -> &crate::Client { - self.cursor.client() - } -} - -/// Arguments passed to a `watch` method, captured to allow resume. -#[derive(Debug, Clone)] -pub(crate) struct WatchArgs { - /// The pipeline of stages to append to an initial `$changeStream` stage. - pub(crate) pipeline: Vec, - - /// The original target of the change stream. - pub(crate) target: AggregateTarget, - - /// The options provided to the initial `$changeStream` stage. - pub(crate) options: Option, -} - -/// Dynamic change stream data needed for resume. -#[derive(Debug, Default)] -pub(crate) struct ChangeStreamData { - /// The `operationTime` returned by the initial `aggregate` command. - pub(crate) initial_operation_time: Option, - - /// The cached resume token. - pub(crate) resume_token: Option, - - /// Whether or not the change stream has attempted a resume, used to attempt a resume only - /// once. - pub(crate) resume_attempted: bool, - - /// Whether or not the change stream has returned a document, used to update resume token - /// during an automatic resume. - pub(crate) document_returned: bool, - - /// The implicit session used to create the original cursor. - pub(crate) implicit_session: Option, -} - -impl ChangeStreamData { - fn take(&mut self) -> Self { - Self { - initial_operation_time: self.initial_operation_time, - resume_token: self.resume_token.clone(), - resume_attempted: self.resume_attempted, - document_returned: self.document_returned, - implicit_session: self.implicit_session.take(), - } - } -} - -fn get_resume_token( - batch_value: &BatchValue, - batch_token: Option<&ResumeToken>, -) -> Result> { - Ok(match batch_value { - BatchValue::Some { doc, is_last } => { - let doc_token = match doc.get("_id")? { - Some(val) => ResumeToken(val.to_raw_bson()), - None => return Err(ErrorKind::MissingResumeToken.into()), - }; - if *is_last && batch_token.is_some() { - batch_token.cloned() - } else { - Some(doc_token) - } - } - BatchValue::Empty => batch_token.cloned(), - _ => None, - }) -} - -impl CursorStream for ChangeStream -where - T: DeserializeOwned, -{ - fn poll_next_in_batch(&mut self, cx: &mut Context<'_>) -> Poll> { - loop { - if let Some(mut pending) = self.pending_resume.take() { - match Pin::new(&mut pending).poll(cx) { - Poll::Pending => { - self.pending_resume = Some(pending); - return Poll::Pending; - } - Poll::Ready(Ok(new_stream)) => { - // Ensure that the old cursor is killed on the server selected for the new - // one. - self.cursor - .set_drop_address(new_stream.cursor.address().clone()); - self.cursor = new_stream.cursor; - self.args = new_stream.args; - // After a successful resume, another resume must be allowed. - self.data.resume_attempted = false; - continue; - } - Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), - } - } - let out = self.cursor.poll_next_in_batch(cx); - match &out { - Poll::Ready(Ok(bv)) => { - if let Some(token) = - get_resume_token(bv, self.cursor.post_batch_resume_token())? - { - self.data.resume_token = Some(token); - } - if matches!(bv, BatchValue::Some { .. }) { - self.data.document_returned = true; - } - } - Poll::Ready(Err(e)) if e.is_resumable() && !self.data.resume_attempted => { - self.data.resume_attempted = true; - let client = self.cursor.client().clone(); - let args = self.args.clone(); - let mut data = self.data.take(); - data.implicit_session = self.cursor.take_implicit_session(); - self.pending_resume = Some(Box::pin(async move { - let new_stream: Result>> = client - .execute_watch(args.pipeline, args.options, args.target, Some(data)) - .await; - new_stream.map(|cs| cs.with_type::()) - })); - // Iterate the loop so the new future gets polled and can register wakers. - continue; - } - _ => {} - } - return out; - } - } -} - -impl Stream for ChangeStream -where - T: DeserializeOwned, -{ - type Item = Result; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - stream_poll_next(Pin::into_inner(self), cx) - } -} diff --git a/src/change_stream/options.rs b/src/change_stream/options.rs index 22e58c29e..802795624 100644 --- a/src/change_stream/options.rs +++ b/src/change_stream/options.rs @@ -1,4 +1,5 @@ //! Contains options for ChangeStreams. +use macro_magic::export_tokens; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use std::time::Duration; @@ -17,21 +18,21 @@ use crate::{ /// [`ChangeStream`](crate::change_stream::ChangeStream). #[skip_serializing_none] #[derive(Clone, Debug, Default, Deserialize, Serialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] +#[export_tokens] pub struct ChangeStreamOptions { #[rustfmt::skip] /// Configures how the /// [`ChangeStreamEvent::full_document`](crate::change_stream::event::ChangeStreamEvent::full_document) /// field will be populated. By default, the field will be empty for updates. - #[builder(default)] pub full_document: Option, /// Configures how the /// [`ChangeStreamEvent::full_document_before_change`]( /// crate::change_stream::event::ChangeStreamEvent::full_document_before_change) field will be /// populated. By default, the field will be empty for updates. - #[builder(default)] pub full_document_before_change: Option, /// Specifies the logical starting point for the new change stream. Note that if a watched @@ -39,13 +40,11 @@ pub struct ChangeStreamOptions { /// `resume_after` and `start_after` cannot be set simultaneously. /// /// For more information on resuming a change stream see the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/#change-stream-resume-after) - #[builder(default)] pub resume_after: Option, /// The change stream will only provide changes that occurred at or after the specified /// timestamp. Any command run against the server will return an operation time that can be /// used here. - #[builder(default)] pub start_at_operation_time: Option, /// Takes a resume token and starts a new change stream returning the first notification after @@ -56,26 +55,22 @@ pub struct ChangeStreamOptions { /// /// See the documentation [here](https://www.mongodb.com/docs/master/changeStreams/#change-stream-start-after) for more /// information. - #[builder(default)] pub start_after: Option, /// If `true`, the change stream will monitor all changes for the given cluster. - #[builder(default, setter(skip))] + #[builder(setter(skip))] pub(crate) all_changes_for_cluster: Option, /// The maximum amount of time for the server to wait on new documents to satisfy a change /// stream query. - #[builder(default)] #[serde(skip_serializing)] pub max_await_time: Option, /// The number of documents to return per batch. - #[builder(default)] #[serde(skip_serializing)] pub batch_size: Option, /// Specifies a collation. - #[builder(default)] #[serde(skip_serializing)] pub collation: Option, @@ -83,7 +78,6 @@ pub struct ChangeStreamOptions { /// /// If none is specified, the read concern defined on the object executing this operation will /// be used. - #[builder(default)] #[serde(skip_serializing)] pub read_concern: Option, @@ -91,16 +85,17 @@ pub struct ChangeStreamOptions { /// /// If none is specified, the selection criteria defined on the object executing this operation /// will be used. - #[builder(default)] #[serde(skip_serializing)] pub selection_criteria: Option, + /// Enables the server to send the 'expanded' list of change stream events. + pub show_expanded_events: Option, + /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the /// database profiler, currentOp and logs. /// /// The comment can be any [`Bson`] value on server versions 4.4+. On lower server versions, /// the comment must be a [`Bson::String`] value. - #[builder(default)] pub comment: Option, } @@ -112,7 +107,7 @@ impl ChangeStreamOptions { .max_await_time(self.max_await_time) .read_concern(self.read_concern.clone()) .selection_criteria(self.selection_criteria.clone()) - .comment_bson(self.comment.clone()) + .comment(self.comment.clone()) .build() } } diff --git a/src/change_stream/session.rs b/src/change_stream/session.rs index 81c5b2fe9..f741a559d 100644 --- a/src/change_stream/session.rs +++ b/src/change_stream/session.rs @@ -23,10 +23,10 @@ use super::{ /// # /// # async fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// # let mut session = client.start_session(None).await?; +/// # let mut session = client.start_session().await?; /// # let coll = client.database("foo").collection::("bar"); /// # -/// let mut cs = coll.watch_with_session(None, None, &mut session).await?; +/// let mut cs = coll.watch().session(&mut session).await?; /// while let Some(event) = cs.next(&mut session).await? { /// println!("{:?}", event) /// } @@ -74,18 +74,17 @@ where /// The session provided must be the same session used to create the change stream. /// /// ``` - /// # use bson::{doc, Document}; - /// # use mongodb::Client; + /// # use mongodb::{Client, bson::{self, doc, Document}}; /// # fn main() { /// # async { /// # let client = Client::with_uri_str("foo").await?; /// # let coll = client.database("foo").collection::("bar"); /// # let other_coll = coll.clone(); - /// # let mut session = client.start_session(None).await?; - /// let mut cs = coll.watch_with_session(None, None, &mut session).await?; + /// # let mut session = client.start_session().await?; + /// let mut cs = coll.watch().session(&mut session).await?; /// while let Some(event) = cs.next(&mut session).await? { /// let id = bson::to_bson(&event.id)?; - /// other_coll.insert_one_with_session(doc! { "id": id }, None, &mut session).await?; + /// other_coll.insert_one(doc! { "id": id }).session(&mut session).await?; /// } /// # Ok::<(), mongodb::error::Error>(()) /// # }; @@ -119,8 +118,8 @@ where /// # async fn func() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com").await?; /// # let coll: Collection = client.database("foo").collection("bar"); - /// # let mut session = client.start_session(None).await?; - /// let mut change_stream = coll.watch_with_session(None, None, &mut session).await?; + /// # let mut session = client.start_session().await?; + /// let mut change_stream = coll.watch().session(&mut session).await?; /// let mut resume_token = None; /// while change_stream.is_alive() { /// if let Some(event) = change_stream.next_if_any(&mut session).await? { @@ -149,7 +148,9 @@ where match bv { BatchValue::Some { doc, .. } => { self.data.document_returned = true; - return Ok(Some(bson::from_slice(doc.as_bytes())?)); + return Ok(Some(crate::bson_compat::deserialize_from_slice( + doc.as_bytes(), + )?)); } BatchValue::Empty | BatchValue::Exhausted => return Ok(None), } diff --git a/src/checked.rs b/src/checked.rs new file mode 100644 index 000000000..ece4d2a59 --- /dev/null +++ b/src/checked.rs @@ -0,0 +1,502 @@ +// Modified from https://github.com/zeta12ti/Checked/blob/master/src/num.rs +// Original license: +// MIT License +// +// Copyright (c) 2017 zeta12ti +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +use std::{cmp::Ordering, convert::TryFrom, fmt, ops::*}; + +/// The Checked type. See the [module level documentation for more.](index.html) +#[derive(PartialEq, Eq, Clone, Copy, Hash)] +pub struct Checked(pub Option); + +impl Checked { + /// Creates a new Checked instance from some sort of integer. + #[inline] + pub fn new(x: T) -> Checked { + Checked(Some(x)) + } + + pub fn try_from(value: F) -> crate::error::Result + where + T: TryFrom, + T::Error: std::fmt::Display, + { + value + .try_into() + .map(|v| Self(Some(v))) + .map_err(|e| crate::error::Error::invalid_argument(format! {"{}", e})) + } + + pub fn get(self) -> crate::error::Result { + self.0 + .ok_or_else(|| crate::error::Error::invalid_argument("checked arithmetic failure")) + } + + pub fn try_into(self) -> crate::error::Result + where + T: TryInto, + T::Error: std::fmt::Display, + { + self.get().and_then(|v| { + v.try_into() + .map_err(|e| crate::error::Error::invalid_argument(format!("{}", e))) + }) + } +} + +// The derived Default only works if T has Default +// Even though this is what it would be anyway +// May change this to T's default (if it has one) +impl Default for Checked { + #[inline] + fn default() -> Checked { + Checked(None) + } +} + +impl fmt::Debug for Checked { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match **self { + Some(ref x) => x.fmt(f), + None => "overflow".fmt(f), + } + } +} + +impl fmt::Display for Checked { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match **self { + Some(ref x) => x.fmt(f), + None => "overflow".fmt(f), + } + } +} + +// I'd like to do +// `impl From where T: From for Checked`` +// in the obvious way, but that "conflicts" with the default `impl From for T`. +// This would subsume both the below Froms since Option has the right From impl. +impl From for Checked { + #[inline] + fn from(x: T) -> Checked { + Checked(Some(x)) + } +} + +impl From> for Checked { + #[inline] + fn from(x: Option) -> Checked { + Checked(x) + } +} + +impl Deref for Checked { + type Target = Option; + + #[inline] + fn deref(&self) -> &Option { + &self.0 + } +} + +impl DerefMut for Checked { + #[inline] + fn deref_mut(&mut self) -> &mut Option { + &mut self.0 + } +} + +impl PartialOrd for Checked { + fn partial_cmp(&self, other: &Checked) -> Option { + // I'm not really sure why we can't match **self etc. here. + // Even with refs everywhere it complains + // Note what happens in this implementation: + // we take the reference self, and call deref (the method) on it + // By Deref coercion, self gets derefed to a Checked + // Now Checked's deref gets called, returning a &Option + // That's what gets matched + match (self.deref(), other.deref()) { + (Some(x), Some(y)) => PartialOrd::partial_cmp(x, y), + _ => None, + } + } +} + +// implements the unary operator `op &T` +// based on `op T` where `T` is expected to be `Copy`able +macro_rules! forward_ref_unop { + (impl $imp:ident, $method:ident for $t:ty {}) => { + impl<'a> $imp for &'a $t { + type Output = <$t as $imp>::Output; + + #[inline] + fn $method(self) -> <$t as $imp>::Output { + $imp::$method(*self) + } + } + }; +} + +// implements binary operators "&T op U", "T op &U", "&T op &U" +// based on "T op U" where T and U are expected to be `Copy`able +macro_rules! forward_ref_binop { + (impl $imp:ident, $method:ident for $t:ty, $u:ty {}) => { + impl<'a> $imp<$u> for &'a $t { + type Output = <$t as $imp<$u>>::Output; + + #[inline] + fn $method(self, other: $u) -> <$t as $imp<$u>>::Output { + $imp::$method(*self, other) + } + } + + impl<'a> $imp<&'a $u> for $t { + type Output = <$t as $imp<$u>>::Output; + + #[inline] + fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { + $imp::$method(self, *other) + } + } + + impl<'a, 'b> $imp<&'a $u> for &'b $t { + type Output = <$t as $imp<$u>>::Output; + + #[inline] + fn $method(self, other: &'a $u) -> <$t as $imp<$u>>::Output { + $imp::$method(*self, *other) + } + } + }; +} + +macro_rules! impl_sh { + ($t:ident, $f:ident) => { + impl Shl> for Checked<$t> { + type Output = Checked<$t>; + + fn shl(self, other: Checked<$f>) -> Checked<$t> { + match (*self, *other) { + (Some(x), Some(y)) => Checked(x.checked_shl(y)), + _ => Checked(None), + } + } + } + + impl Shl<$f> for Checked<$t> { + type Output = Checked<$t>; + + fn shl(self, other: $f) -> Checked<$t> { + match *self { + Some(x) => Checked(x.checked_shl(other)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl Shl, shl for Checked<$t>, Checked<$f> {} } + forward_ref_binop! { impl Shl, shl for Checked<$t>, $f {} } + + impl ShlAssign<$f> for Checked<$t> { + #[inline] + fn shl_assign(&mut self, other: $f) { + *self = *self << other; + } + } + + impl ShlAssign> for Checked<$t> { + #[inline] + fn shl_assign(&mut self, other: Checked<$f>) { + *self = *self << other; + } + } + + impl Shr> for Checked<$t> { + type Output = Checked<$t>; + + fn shr(self, other: Checked<$f>) -> Checked<$t> { + match (*self, *other) { + (Some(x), Some(y)) => Checked(x.checked_shr(y)), + _ => Checked(None), + } + } + } + + impl Shr<$f> for Checked<$t> { + type Output = Checked<$t>; + + fn shr(self, other: $f) -> Checked<$t> { + match *self { + Some(x) => Checked(x.checked_shr(other)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl Shr, shr for Checked<$t>, Checked<$f> {} } + forward_ref_binop! { impl Shr, shr for Checked<$t>, $f {} } + + impl ShrAssign<$f> for Checked<$t> { + #[inline] + fn shr_assign(&mut self, other: $f) { + *self = *self >> other; + } + } + + impl ShrAssign> for Checked<$t> { + #[inline] + fn shr_assign(&mut self, other: Checked<$f>) { + *self = *self >> other; + } + } + }; +} + +macro_rules! impl_sh_reverse { + ($t:ident, $f:ident) => { + impl Shl> for $f { + type Output = Checked<$f>; + + fn shl(self, other: Checked<$t>) -> Checked<$f> { + match *other { + Some(x) => Checked(self.checked_shl(x)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl Shl, shl for $f, Checked<$t> {} } + + impl Shr> for $f { + type Output = Checked<$f>; + + fn shr(self, other: Checked<$t>) -> Checked<$f> { + match *other { + Some(x) => Checked(self.checked_shr(x)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl Shr, shr for $f, Checked<$t> {} } + }; +} + +macro_rules! impl_sh_all { + ($($t:ident)*) => ($( + // When checked_shX is added for other shift sizes, uncomment some of these. + // impl_sh! { $t, u8 } + // impl_sh! { $t, u16 } + impl_sh! { $t, u32 } + //impl_sh! { $t, u64 } + //impl_sh! { $t, usize } + + //impl_sh! { $t, i8 } + //impl_sh! { $t, i16 } + //impl_sh! { $t, i32 } + //impl_sh! { $t, i64 } + //impl_sh! { $t, isize } + + // impl_sh_reverse! { u8, $t } + // impl_sh_reverse! { u16, $t } + impl_sh_reverse! { u32, $t } + //impl_sh_reverse! { u64, $t } + //impl_sh_reverse! { usize, $t } + + //impl_sh_reverse! { i8, $t } + //impl_sh_reverse! { i16, $t } + //impl_sh_reverse! { i32, $t } + //impl_sh_reverse! { i64, $t } + //impl_sh_reverse! { isize, $t } + )*) +} + +impl_sh_all! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } + +// implements unary operators for checked types +macro_rules! impl_unop { + (impl $imp:ident, $method:ident, $checked_method:ident for $t:ty {}) => { + impl $imp for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self) -> Checked<$t> { + match *self { + Some(x) => Checked(x.$checked_method()), + None => Checked(None), + } + } + } + + forward_ref_unop! { impl $imp, $method for Checked<$t> {} } + }; +} + +// implements unary operators for checked types (with no checked method) +macro_rules! impl_unop_unchecked { + (impl $imp:ident, $method:ident for $t:ty {$op:tt}) => { + impl $imp for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self) -> Checked<$t> { + match *self { + Some(x) => Checked(Some($op x)), + None => Checked(None) + } + } + } + + forward_ref_unop! { impl $imp, $method for Checked<$t> {} } + } +} + +// implements binary operators for checked types +macro_rules! impl_binop { + (impl $imp:ident, $method:ident, $checked_method:ident for $t:ty {}) => { + impl $imp for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self, other: Checked<$t>) -> Checked<$t> { + match (*self, *other) { + (Some(x), Some(y)) => Checked(x.$checked_method(y)), + _ => Checked(None), + } + } + } + + impl $imp<$t> for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self, other: $t) -> Checked<$t> { + match *self { + Some(x) => Checked(x.$checked_method(other)), + _ => Checked(None), + } + } + } + + impl $imp> for $t { + type Output = Checked<$t>; + + fn $method(self, other: Checked<$t>) -> Checked<$t> { + match *other { + Some(x) => Checked(self.$checked_method(x)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl $imp, $method for Checked<$t>, Checked<$t> {} } + forward_ref_binop! { impl $imp, $method for Checked<$t>, $t {} } + forward_ref_binop! { impl $imp, $method for $t, Checked<$t> {} } + }; +} + +// implements binary operators for checked types (no checked method) +macro_rules! impl_binop_unchecked { + (impl $imp:ident, $method:ident for $t:ty {$op:tt}) => { + impl $imp for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self, other: Checked<$t>) -> Checked<$t> { + match (*self, *other) { + (Some(x), Some(y)) => Checked(Some(x $op y)), + _ => Checked(None), + } + } + } + + impl $imp<$t> for Checked<$t> { + type Output = Checked<$t>; + + fn $method(self, other: $t) -> Checked<$t> { + match *self { + Some(x) => Checked(Some(x $op other)), + _ => Checked(None), + } + } + } + + impl $imp> for $t { + type Output = Checked<$t>; + + fn $method(self, other: Checked<$t>) -> Checked<$t> { + match *other { + Some(x) => Checked(Some(self $op x)), + None => Checked(None), + } + } + } + + forward_ref_binop! { impl $imp, $method for Checked<$t>, Checked<$t> {} } + forward_ref_binop! { impl $imp, $method for Checked<$t>, $t {} } + forward_ref_binop! { impl $imp, $method for $t, Checked<$t> {} } + } +} + +// implements assignment operators for checked types +macro_rules! impl_binop_assign { + (impl $imp:ident, $method:ident for $t:ty {$op:tt}) => { + impl $imp for Checked<$t> { + #[inline] + fn $method(&mut self, other: Checked<$t>) { + *self = *self $op other; + } + } + + impl $imp<$t> for Checked<$t> { + #[inline] + fn $method(&mut self, other: $t) { + *self = *self $op other; + } + } + }; +} + +macro_rules! checked_impl { + ($($t:ty)*) => { + $( + impl_binop! { impl Add, add, checked_add for $t {} } + impl_binop_assign! { impl AddAssign, add_assign for $t {+} } + impl_binop! { impl Sub, sub, checked_sub for $t {} } + impl_binop_assign! { impl SubAssign, sub_assign for $t {-} } + impl_binop! { impl Mul, mul, checked_mul for $t {} } + impl_binop_assign! { impl MulAssign, mul_assign for $t {*} } + impl_binop! { impl Div, div, checked_div for $t {} } + impl_binop_assign! { impl DivAssign, div_assign for $t {/} } + impl_binop! { impl Rem, rem, checked_rem for $t {} } + impl_binop_assign! { impl RemAssign, rem_assign for $t {%} } + impl_unop_unchecked! { impl Not, not for $t {!} } + impl_binop_unchecked! { impl BitXor, bitxor for $t {^} } + impl_binop_assign! { impl BitXorAssign, bitxor_assign for $t {^} } + impl_binop_unchecked! { impl BitOr, bitor for $t {|} } + impl_binop_assign! { impl BitOrAssign, bitor_assign for $t {|} } + impl_binop_unchecked! { impl BitAnd, bitand for $t {&} } + impl_binop_assign! { impl BitAndAssign, bitand_assign for $t {&} } + impl_unop! { impl Neg, neg, checked_neg for $t {} } + + )* + }; +} + +checked_impl! { u8 u16 u32 u64 usize i8 i16 i32 i64 isize } diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 000000000..7f5defc63 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,716 @@ +pub mod action; +pub mod auth; +#[cfg(feature = "in-use-encryption")] +pub(crate) mod csfle; +mod executor; +pub mod options; +pub mod session; + +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex as SyncMutex, + }, + time::{Duration, Instant}, +}; + +#[cfg(feature = "in-use-encryption")] +pub use self::csfle::client_builder::*; +use derive_where::derive_where; +use futures_core::Future; +use futures_util::FutureExt; + +#[cfg(feature = "tracing-unstable")] +use crate::trace::{ + command::CommandTracingEventEmitter, + server_selection::ServerSelectionTracingEventEmitter, + trace_or_log_enabled, + TracingOrLogLevel, + COMMAND_TRACING_EVENT_TARGET, +}; +use crate::{ + bson::doc, + concern::{ReadConcern, WriteConcern}, + db::Database, + error::{Error, ErrorKind, Result}, + event::command::CommandEvent, + id_set::IdSet, + operation::OverrideCriteriaFn, + options::{ClientOptions, DatabaseOptions, ReadPreference, SelectionCriteria, ServerAddress}, + sdam::{ + server_selection::{self, attempt_to_select_server}, + SelectedServer, + Topology, + }, + tracking_arc::TrackingArc, + BoxFuture, + ClientSession, +}; + +pub(crate) use executor::{HELLO_COMMAND_NAMES, REDACTED_COMMANDS}; +pub(crate) use session::{ClusterTime, SESSIONS_UNSUPPORTED_COMMANDS}; + +use session::{ServerSession, ServerSessionPool}; + +const DEFAULT_SERVER_SELECTION_TIMEOUT: Duration = Duration::from_secs(30); + +/// This is the main entry point for the API. A `Client` is used to connect to a MongoDB cluster. +/// By default, it will monitor the topology of the cluster, keeping track of any changes, such +/// as servers being added or removed. +/// +/// `Client` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, +/// so it can safely be shared across threads or async tasks. For example: +/// +/// ```rust +/// # use mongodb::{bson::Document, Client, error::Result}; +/// # +/// # async fn start_workers() -> Result<()> { +/// let client = Client::with_uri_str("mongodb://example.com").await?; +/// +/// for i in 0..5 { +/// let client_ref = client.clone(); +/// +/// tokio::task::spawn(async move { +/// let collection = client_ref.database("items").collection::(&format!("coll{}", i)); +/// +/// // Do something with the collection +/// }); +/// } +/// # +/// # Ok(()) +/// # } +/// ``` +/// ## Notes on performance +/// Spawning many asynchronous tasks that use the driver concurrently like this is often the best +/// way to achieve maximum performance, as the driver is designed to work well in such situations. +/// +/// Additionally, using a custom Rust type that implements `Serialize` and `Deserialize` as the +/// generic parameter of [`Collection`](../struct.Collection.html) instead of +/// [`Document`](crate::bson::Document) can reduce the amount of time the driver and your +/// application spends serializing and deserializing BSON, which can also lead to increased +/// performance. +/// +/// ## TCP Keepalive +/// TCP keepalive is enabled by default with ``tcp_keepalive_time`` set to 120 seconds. The +/// driver does not set ``tcp_keepalive_intvl``. See the +/// [MongoDB Diagnostics FAQ keepalive section](https://www.mongodb.com/docs/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments) +/// for instructions on setting these values at the system level. +/// +/// ## Clean shutdown +/// Because Rust has no async equivalent of `Drop`, values that require server-side cleanup when +/// dropped spawn a new async task to perform that cleanup. This can cause two potential issues: +/// +/// * Drop tasks pending or in progress when the async runtime shuts down may not complete, causing +/// server-side resources to not be freed. +/// * Drop tasks may run at an arbitrary time even after no `Client` values exist, making it hard to +/// reason about associated resources (e.g. event handlers). +/// +/// To address these issues, we highly recommend you use [`Client::shutdown`] in the termination +/// path of your application. This will ensure that outstanding resources have been cleaned up and +/// terminate internal worker tasks before returning. Please note that `shutdown` will wait for +/// _all_ outstanding resource handles to be dropped, so they must either have been dropped before +/// calling `shutdown` or in a concurrent task; see the documentation of `shutdown` for more +/// details. +#[derive(Debug, Clone)] +pub struct Client { + inner: TrackingArc, +} + +#[allow(dead_code, unreachable_code, clippy::diverging_sub_expression)] +const _: fn() = || { + fn assert_send(_t: T) {} + fn assert_sync(_t: T) {} + + let _c: super::Client = todo!(); + assert_send(_c); + assert_sync(_c); +}; + +#[derive(Debug)] +struct ClientInner { + topology: Topology, + options: ClientOptions, + session_pool: ServerSessionPool, + shutdown: Shutdown, + dropped: AtomicBool, + end_sessions_token: std::sync::Mutex, + #[cfg(feature = "in-use-encryption")] + csfle: tokio::sync::RwLock>, + #[cfg(test)] + disable_command_events: AtomicBool, +} + +#[derive(Debug)] +struct Shutdown { + pending_drops: SyncMutex>>, + executed: AtomicBool, +} + +impl Client { + /// Creates a new `Client` connected to the cluster specified by `uri`. `uri` must be a valid + /// MongoDB connection string. + /// + /// See the documentation on + /// [`ClientOptions::parse`](options/struct.ClientOptions.html#method.parse) for more details. + pub async fn with_uri_str(uri: impl AsRef) -> Result { + let options = ClientOptions::parse(uri.as_ref()).await?; + + Client::with_options(options) + } + + /// Creates a new `Client` connected to the cluster specified by `options`. + pub fn with_options(options: ClientOptions) -> Result { + options.validate()?; + + // Spawn a cleanup task, similar to register_async_drop + let (cleanup_tx, cleanup_rx) = tokio::sync::oneshot::channel::>(); + crate::runtime::spawn(async move { + // If the cleanup channel is closed, that task was dropped. + if let Ok(cleanup) = cleanup_rx.await { + cleanup.await; + } + }); + let end_sessions_token = std::sync::Mutex::new(AsyncDropToken { + tx: Some(cleanup_tx), + }); + + let inner = TrackingArc::new(ClientInner { + topology: Topology::new(options.clone())?, + session_pool: ServerSessionPool::new(), + options, + shutdown: Shutdown { + pending_drops: SyncMutex::new(IdSet::new()), + executed: AtomicBool::new(false), + }, + dropped: AtomicBool::new(false), + end_sessions_token, + #[cfg(feature = "in-use-encryption")] + csfle: Default::default(), + #[cfg(test)] + disable_command_events: AtomicBool::new(false), + }); + Ok(Self { inner }) + } + + /// Return an `EncryptedClientBuilder` for constructing a `Client` with auto-encryption enabled. + /// + /// ```no_run + /// # use mongocrypt::ctx::KmsProvider; + /// # use mongodb::{Client, bson::{self, doc}, error::Result}; + /// # async fn func() -> Result<()> { + /// # let client_options = todo!(); + /// # let key_vault_namespace = todo!(); + /// # let key_vault_client: Client = todo!(); + /// # let local_key: bson::Binary = todo!(); + /// let encrypted_client = Client::encrypted_builder( + /// client_options, + /// key_vault_namespace, + /// [(KmsProvider::local(), doc! { "key": local_key }, None)], + /// )? + /// .key_vault_client(key_vault_client) + /// .build() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "in-use-encryption")] + pub fn encrypted_builder( + client_options: ClientOptions, + key_vault_namespace: crate::Namespace, + kms_providers: impl IntoIterator< + Item = ( + mongocrypt::ctx::KmsProvider, + crate::bson::Document, + Option, + ), + >, + ) -> Result { + Ok(EncryptedClientBuilder::new( + client_options, + csfle::options::AutoEncryptionOptions::new( + key_vault_namespace, + csfle::options::KmsProviders::new(kms_providers)?, + ), + )) + } + + /// Whether commands sent via this client should be auto-encrypted. + pub(crate) async fn should_auto_encrypt(&self) -> bool { + #[cfg(feature = "in-use-encryption")] + { + let csfle = self.inner.csfle.read().await; + match *csfle { + Some(ref csfle) => csfle + .opts() + .bypass_auto_encryption + .map(|b| !b) + .unwrap_or(true), + None => false, + } + } + #[cfg(not(feature = "in-use-encryption"))] + { + false + } + } + + #[cfg(all(test, feature = "in-use-encryption"))] + pub(crate) async fn mongocryptd_spawned(&self) -> bool { + self.inner + .csfle + .read() + .await + .as_ref() + .is_some_and(|cs| cs.exec().mongocryptd_spawned()) + } + + #[cfg(all(test, feature = "in-use-encryption"))] + pub(crate) async fn has_mongocryptd_client(&self) -> bool { + self.inner + .csfle + .read() + .await + .as_ref() + .is_some_and(|cs| cs.exec().has_mongocryptd_client()) + } + + fn test_command_event_channel(&self) -> Option<&options::TestEventSender> { + #[cfg(test)] + { + self.inner + .options + .test_options + .as_ref() + .and_then(|t| t.async_event_listener.as_ref()) + } + #[cfg(not(test))] + { + None + } + } + + pub(crate) async fn emit_command_event(&self, generate_event: impl FnOnce() -> CommandEvent) { + #[cfg(test)] + if self + .inner + .disable_command_events + .load(std::sync::atomic::Ordering::SeqCst) + { + return; + } + #[cfg(feature = "tracing-unstable")] + let tracing_emitter = if trace_or_log_enabled!( + target: COMMAND_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + Some(CommandTracingEventEmitter::new( + self.inner.options.tracing_max_document_length_bytes, + self.inner.topology.id, + )) + } else { + None + }; + let test_channel = self.test_command_event_channel(); + let should_send = test_channel.is_some() || self.options().command_event_handler.is_some(); + #[cfg(feature = "tracing-unstable")] + let should_send = should_send || tracing_emitter.is_some(); + if !should_send { + return; + } + + let event = generate_event(); + if let Some(tx) = test_channel { + let (msg, ack) = crate::runtime::AcknowledgedMessage::package(event.clone()); + let _ = tx.send(msg).await; + ack.wait_for_acknowledgment().await; + } + #[cfg(feature = "tracing-unstable")] + if let Some(ref tracing_emitter) = tracing_emitter { + tracing_emitter.handle(event.clone()); + } + if let Some(handler) = &self.options().command_event_handler { + handler.handle(event); + } + } + + /// Gets the default selection criteria the `Client` uses for operations.. + pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.inner.options.selection_criteria.as_ref() + } + + /// Gets the default read concern the `Client` uses for operations. + pub fn read_concern(&self) -> Option<&ReadConcern> { + self.inner.options.read_concern.as_ref() + } + + /// Gets the default write concern the `Client` uses for operations. + pub fn write_concern(&self) -> Option<&WriteConcern> { + self.inner.options.write_concern.as_ref() + } + + /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. + /// The `Database` options (e.g. read preference and write concern) will default to those of the + /// `Client`. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn database(&self, name: &str) -> Database { + Database::new(self.clone(), name, None) + } + + /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. + /// Operations done with this `Database` will use the options specified by `options` by default + /// and will otherwise default to those of the `Client`. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn database_with_options(&self, name: &str, options: DatabaseOptions) -> Database { + Database::new(self.clone(), name, Some(options)) + } + + /// Gets a handle to the default database specified in the `ClientOptions` or MongoDB connection + /// string used to construct this `Client`. + /// + /// If no default database was specified, `None` will be returned. + pub fn default_database(&self) -> Option { + self.inner + .options + .default_database + .as_ref() + .map(|db_name| self.database(db_name)) + } + + pub(crate) fn register_async_drop(&self) -> AsyncDropToken { + let (cleanup_tx, cleanup_rx) = tokio::sync::oneshot::channel::>(); + let (id_tx, id_rx) = tokio::sync::oneshot::channel::(); + let weak = self.weak(); + let handle = crate::runtime::spawn(async move { + // Unwrap safety: the id is sent immediately after task creation, with no + // await points in between. + let id = id_rx.await.unwrap(); + // If the cleanup channel is closed, that task was dropped. + if let Ok(cleanup) = cleanup_rx.await { + cleanup.await; + } + if let Some(client) = weak.upgrade() { + client + .inner + .shutdown + .pending_drops + .lock() + .unwrap() + .remove(&id); + } + }); + let id = self + .inner + .shutdown + .pending_drops + .lock() + .unwrap() + .insert(handle); + let _ = id_tx.send(id); + AsyncDropToken { + tx: Some(cleanup_tx), + } + } + + /// Check in a server session to the server session pool. The session will be discarded if it is + /// expired or dirty. + pub(crate) async fn check_in_server_session(&self, session: ServerSession) { + let timeout = self.inner.topology.logical_session_timeout(); + self.inner.session_pool.check_in(session, timeout).await; + } + + #[cfg(test)] + pub(crate) async fn clear_session_pool(&self) { + self.inner.session_pool.clear().await; + } + + #[cfg(test)] + pub(crate) async fn is_session_checked_in(&self, id: &crate::bson::Document) -> bool { + self.inner.session_pool.contains(id).await + } + + #[cfg(test)] + pub(crate) fn disable_command_events(&self, disable: bool) { + self.inner + .disable_command_events + .store(disable, std::sync::atomic::Ordering::SeqCst); + } + + /// Get the address of the server selected according to the given criteria. + /// This method is only used in tests. + #[cfg(test)] + pub(crate) async fn test_select_server( + &self, + criteria: Option<&SelectionCriteria>, + ) -> Result { + let (server, _) = self + .select_server(criteria, "Test select server", None, |_, _| None) + .await?; + Ok(server.address.clone()) + } + + /// Select a server using the provided criteria. If none is provided, a primary read preference + /// will be used instead. + async fn select_server( + &self, + criteria: Option<&SelectionCriteria>, + #[allow(unused_variables)] // we only use the operation_name for tracing. + operation_name: &str, + deprioritized: Option<&ServerAddress>, + override_criteria: OverrideCriteriaFn, + ) -> Result<(SelectedServer, SelectionCriteria)> { + let criteria = + criteria.unwrap_or(&SelectionCriteria::ReadPreference(ReadPreference::Primary)); + + let start_time = Instant::now(); + let timeout = self + .inner + .options + .server_selection_timeout + .unwrap_or(DEFAULT_SERVER_SELECTION_TIMEOUT); + + #[cfg(feature = "tracing-unstable")] + let event_emitter = ServerSelectionTracingEventEmitter::new( + self.inner.topology.id, + criteria, + operation_name, + start_time, + timeout, + ); + #[cfg(feature = "tracing-unstable")] + event_emitter.emit_started_event(self.inner.topology.watch().observe_latest().description); + // We only want to emit this message once per operation at most. + #[cfg(feature = "tracing-unstable")] + let mut emitted_waiting_message = false; + + let mut watcher = self.inner.topology.watch(); + loop { + let state = watcher.observe_latest(); + let override_slot; + let effective_criteria = + if let Some(oc) = override_criteria(criteria, &state.description) { + override_slot = oc; + &override_slot + } else { + criteria + }; + let result = server_selection::attempt_to_select_server( + effective_criteria, + &state.description, + &state.servers(), + deprioritized, + ); + match result { + Err(error) => { + #[cfg(feature = "tracing-unstable")] + event_emitter.emit_failed_event(&state.description, &error); + + return Err(error); + } + Ok(result) => { + if let Some(server) = result { + #[cfg(feature = "tracing-unstable")] + event_emitter.emit_succeeded_event(&state.description, &server); + + return Ok((server, effective_criteria.clone())); + } else { + #[cfg(feature = "tracing-unstable")] + if !emitted_waiting_message { + event_emitter.emit_waiting_event(&state.description); + emitted_waiting_message = true; + } + + watcher.request_immediate_check(); + + let change_occurred = start_time.elapsed() < timeout + && watcher + .wait_for_update(timeout - start_time.elapsed()) + .await; + if !change_occurred { + let error: Error = ErrorKind::ServerSelection { + message: state + .description + .server_selection_timeout_error_message(criteria), + } + .into(); + + #[cfg(feature = "tracing-unstable")] + event_emitter.emit_failed_event(&state.description, &error); + + return Err(error); + } + } + } + } + } + } + + #[cfg(all(test, feature = "dns-resolver"))] + pub(crate) fn get_hosts(&self) -> Vec { + let watcher = self.inner.topology.watch(); + let state = watcher.peek_latest(); + + state + .servers() + .keys() + .map(|stream_address| format!("{}", stream_address)) + .collect() + } + + #[cfg(test)] + pub(crate) async fn sync_workers(&self) { + self.inner.topology.sync_workers().await; + } + + #[cfg(test)] + pub(crate) fn topology_description(&self) -> crate::sdam::TopologyDescription { + self.inner + .topology + .watch() + .peek_latest() + .description + .clone() + } + + #[cfg(test)] + pub(crate) fn topology(&self) -> &Topology { + &self.inner.topology + } + + #[cfg(feature = "in-use-encryption")] + pub(crate) async fn primary_description(&self) -> Option { + let start_time = Instant::now(); + let timeout = self + .inner + .options + .server_selection_timeout + .unwrap_or(DEFAULT_SERVER_SELECTION_TIMEOUT); + let mut watcher = self.inner.topology.watch(); + loop { + let topology = watcher.observe_latest(); + if let Some(desc) = topology.description.primary() { + return Some(desc.clone()); + } + if !watcher + .wait_for_update(timeout - start_time.elapsed()) + .await + { + return None; + } + } + } + + pub(crate) fn weak(&self) -> WeakClient { + WeakClient { + inner: TrackingArc::downgrade(&self.inner), + } + } + + #[cfg(feature = "in-use-encryption")] + pub(crate) async fn auto_encryption_opts( + &self, + ) -> Option> { + tokio::sync::RwLockReadGuard::try_map(self.inner.csfle.read().await, |csfle| { + csfle.as_ref().map(|cs| cs.opts()) + }) + .ok() + } + + pub(crate) fn options(&self) -> &ClientOptions { + &self.inner.options + } + + /// Ends all sessions contained in this client's session pool on the server. + pub(crate) async fn end_all_sessions(&self) { + // The maximum number of session IDs that should be sent in a single endSessions command. + const MAX_END_SESSIONS_BATCH_SIZE: usize = 10_000; + + let mut watcher = self.inner.topology.watch(); + let selection_criteria = + SelectionCriteria::from(ReadPreference::PrimaryPreferred { options: None }); + + let session_ids = self.inner.session_pool.get_session_ids().await; + for chunk in session_ids.chunks(MAX_END_SESSIONS_BATCH_SIZE) { + let state = watcher.observe_latest(); + let Ok(Some(_)) = attempt_to_select_server( + &selection_criteria, + &state.description, + &state.servers(), + None, + ) else { + // If a suitable server is not available, do not proceed with the operation to avoid + // spinning for server_selection_timeout. + return; + }; + + let end_sessions = doc! { + "endSessions": chunk, + }; + let _ = self + .database("admin") + .run_command(end_sessions) + .selection_criteria(selection_criteria.clone()) + .await; + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct WeakClient { + inner: crate::tracking_arc::Weak, +} + +impl WeakClient { + pub(crate) fn upgrade(&self) -> Option { + self.inner.upgrade().map(|inner| Client { inner }) + } +} + +#[derive_where(Debug)] +pub(crate) struct AsyncDropToken { + #[derive_where(skip)] + tx: Option>>, +} + +impl AsyncDropToken { + pub(crate) fn spawn(&mut self, fut: impl Future + Send + 'static) { + if let Some(tx) = self.tx.take() { + let _ = tx.send(fut.boxed()); + } else { + #[cfg(debug_assertions)] + panic!("exhausted AsyncDropToken"); + } + } + + pub(crate) fn take(&mut self) -> Self { + Self { tx: self.tx.take() } + } +} + +impl Drop for Client { + fn drop(&mut self) { + if !self.inner.shutdown.executed.load(Ordering::SeqCst) + && !self.inner.dropped.load(Ordering::SeqCst) + && TrackingArc::strong_count(&self.inner) == 1 + { + // We need an owned copy of the client to move into the spawned future. However, if this + // call to drop completes before the spawned future completes, the number of strong + // references to the inner client will again be 1 when the cloned client drops, and thus + // end_all_sessions will be called continuously until the runtime shuts down. Storing a + // flag indicating whether end_all_sessions has already been called breaks + // this cycle. + self.inner.dropped.store(true, Ordering::SeqCst); + let client = self.clone(); + self.inner + .end_sessions_token + .lock() + .unwrap() + .spawn(async move { + client.end_all_sessions().await; + }); + } + } +} diff --git a/src/client/action.rs b/src/client/action.rs new file mode 100644 index 000000000..622eb321a --- /dev/null +++ b/src/client/action.rs @@ -0,0 +1,2 @@ +pub(crate) mod perf; +pub(crate) mod shutdown; diff --git a/src/client/action/perf.rs b/src/client/action/perf.rs new file mode 100644 index 000000000..eda5de0a0 --- /dev/null +++ b/src/client/action/perf.rs @@ -0,0 +1,20 @@ +use crate::action::action_impl; + +#[action_impl] +impl<'a> Action for crate::action::WarmConnectionPool<'a> { + type Future = WarmConnectionPoolFuture; + + async fn execute(self) -> () { + if self + .client + .inner + .options + .min_pool_size + .is_some_and(|size| size == 0) + { + // No-op when min_pool_size is zero. + return; + } + self.client.inner.topology.warm_pool().await; + } +} diff --git a/src/client/action/shutdown.rs b/src/client/action/shutdown.rs new file mode 100644 index 000000000..7944342ac --- /dev/null +++ b/src/client/action/shutdown.rs @@ -0,0 +1,39 @@ +use std::sync::atomic::Ordering; + +use futures_util::future::join_all; + +use crate::action::action_impl; + +#[action_impl] +impl Action for crate::action::Shutdown { + type Future = ShutdownFuture; + + async fn execute(self) -> () { + if !self.immediate { + // Subtle bug: if this is inlined into the `join_all(..)` call, Rust will extend the + // lifetime of the temporary unnamed `MutexLock` until the end of the *statement*, + // causing the lock to be held for the duration of the join, which deadlocks. + let pending = self + .client + .inner + .shutdown + .pending_drops + .lock() + .unwrap() + .extract(); + join_all(pending).await; + } + // If shutdown has already been called on a different copy of the client, don't call + // end_all_sessions again. + if !self.client.inner.shutdown.executed.load(Ordering::SeqCst) { + self.client.end_all_sessions().await; + } + self.client.inner.topology.shutdown().await; + // This has to happen last to allow pending cleanup to execute commands. + self.client + .inner + .shutdown + .executed + .store(true, Ordering::SeqCst); + } +} diff --git a/src/client/auth.rs b/src/client/auth.rs new file mode 100644 index 000000000..d4c747b5c --- /dev/null +++ b/src/client/auth.rs @@ -0,0 +1,655 @@ +//! Contains the types needed to specify the auth configuration for a +//! [`Client`](struct.Client.html). + +#[cfg(feature = "aws-auth")] +pub(crate) mod aws; +#[cfg(feature = "gssapi-auth")] +mod gssapi; +/// Contains the functionality for [`OIDC`](https://openid.net/developers/how-connect-works/) authorization and authentication. +pub mod oidc; +mod plain; +mod sasl; +mod scram; +#[cfg(test)] +mod test; +mod x509; + +use std::{borrow::Cow, fmt::Debug, str::FromStr}; + +use crate::{bson::RawDocumentBuf, bson_compat::cstr, options::ClientOptions}; +use derive_where::derive_where; +use hmac::{digest::KeyInit, Mac}; +use rand::Rng; +use serde::Deserialize; +use typed_builder::TypedBuilder; + +use self::scram::ScramVersion; +#[cfg(feature = "gssapi-auth")] +use crate::options::ResolverConfig; +use crate::{ + bson::Document, + client::options::ServerApi, + cmap::{Command, Connection, StreamDescription}, + error::{Error, ErrorKind, Result}, +}; + +const SCRAM_SHA_1_STR: &str = "SCRAM-SHA-1"; +const SCRAM_SHA_256_STR: &str = "SCRAM-SHA-256"; +const MONGODB_CR_STR: &str = "MONGODB-CR"; +const GSSAPI_STR: &str = "GSSAPI"; +const MONGODB_AWS_STR: &str = "MONGODB-AWS"; +const MONGODB_X509_STR: &str = "MONGODB-X509"; +const PLAIN_STR: &str = "PLAIN"; +const MONGODB_OIDC_STR: &str = "MONGODB-OIDC"; + +/// The authentication mechanisms supported by MongoDB. +/// +/// Note: not all of these mechanisms are currently supported by the driver. +#[derive(Clone, Deserialize, PartialEq, Debug)] +#[non_exhaustive] +pub enum AuthMechanism { + /// MongoDB Challenge Response nonce and MD5 based authentication system. It is currently + /// deprecated and will never be supported by this driver. + MongoDbCr, + + /// The SCRAM-SHA-1 mechanism as defined in [RFC 5802](http://tools.ietf.org/html/rfc5802). + /// + /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-scram/) for more information. + ScramSha1, + + /// The SCRAM-SHA-256 mechanism which extends [RFC 5802](http://tools.ietf.org/html/rfc5802) and is formally defined in [RFC 7677](https://tools.ietf.org/html/rfc7677). + /// + /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-scram/) for more information. + ScramSha256, + + /// The MONGODB-X509 mechanism based on the usage of X.509 certificates to validate a client + /// where the distinguished subject name of the client certificate acts as the username. + /// + /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-x.509/) for more information. + MongoDbX509, + + /// Kerberos authentication mechanism as defined in [RFC 4752](http://tools.ietf.org/html/rfc4752). + /// + /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/kerberos/) for more information. + #[cfg(feature = "gssapi-auth")] + Gssapi, + + /// The SASL PLAIN mechanism, as defined in [RFC 4616](), is used in MongoDB to perform LDAP + /// authentication and cannot be used for any other type of authentication. + /// Since the credentials are stored outside of MongoDB, the "$external" database must be used + /// for authentication. + /// + /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-ldap/#ldap-proxy-authentication) for more information on LDAP authentication. + Plain, + + /// MONGODB-AWS authenticates using AWS IAM credentials (an access key ID and a secret access + /// key), temporary AWS IAM credentials obtained from an AWS Security Token Service (STS) + /// Assume Role request, or temporary AWS IAM credentials assigned to an EC2 instance or ECS + /// task. + /// + /// Note: Only server versions 4.4+ support AWS authentication. Additionally, the driver only + /// supports AWS authentication with the tokio runtime. + #[cfg(feature = "aws-auth")] + MongoDbAws, + + /// MONGODB-OIDC authenticates using [OpenID Connect](https://openid.net/developers/specs/) access tokens. + #[serde(alias = "MONGODB-OIDC")] + MongoDbOidc, +} + +impl AuthMechanism { + fn from_scram_version(scram: &ScramVersion) -> Self { + match scram { + ScramVersion::Sha1 => Self::ScramSha1, + ScramVersion::Sha256 => Self::ScramSha256, + } + } + + pub(crate) fn from_stream_description(description: &StreamDescription) -> AuthMechanism { + let scram_sha_256_found = description + .sasl_supported_mechs + .as_ref() + .map(|ms| ms.iter().any(|m| m == AuthMechanism::ScramSha256.as_str())) + .unwrap_or(false); + + if scram_sha_256_found { + AuthMechanism::ScramSha256 + } else { + AuthMechanism::ScramSha1 + } + } + + /// Determines if the provided credentials have the required information to perform + /// authentication. + pub fn validate_credential(&self, credential: &Credential) -> Result<()> { + match self { + AuthMechanism::ScramSha1 | AuthMechanism::ScramSha256 => { + if credential.username.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No username provided for SCRAM authentication".to_string(), + } + .into()); + }; + Ok(()) + } + AuthMechanism::MongoDbX509 => { + if credential.password.is_some() { + return Err(ErrorKind::InvalidArgument { + message: "A password cannot be specified with MONGODB-X509".to_string(), + } + .into()); + } + + if credential.source.as_deref().unwrap_or("$external") != "$external" { + return Err(ErrorKind::InvalidArgument { + message: "only $external may be specified as an auth source for \ + MONGODB-X509" + .to_string(), + } + .into()); + } + + Ok(()) + } + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + if credential.username.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No username provided for GSSAPI authentication".to_string(), + } + .into()); + } + + if credential.source.as_deref().unwrap_or("$external") != "$external" { + return Err(ErrorKind::InvalidArgument { + message: "only $external may be specified as an auth source for GSSAPI" + .to_string(), + } + .into()); + } + + Ok(()) + } + AuthMechanism::Plain => { + if credential.username.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No username provided for PLAIN authentication".to_string(), + } + .into()); + } + + if credential.username.as_deref() == Some("") { + return Err(ErrorKind::InvalidArgument { + message: "Username for PLAIN authentication must be non-empty".to_string(), + } + .into()); + } + + if credential.password.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "No password provided for PLAIN authentication".to_string(), + } + .into()); + } + + Ok(()) + } + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => { + if credential.username.is_some() && credential.password.is_none() { + return Err(ErrorKind::InvalidArgument { + message: "Username cannot be provided without password for MONGODB-AWS \ + authentication" + .to_string(), + } + .into()); + } + + Ok(()) + } + AuthMechanism::MongoDbOidc => oidc::validate_credential(credential), + _ => Ok(()), + } + } + + /// Returns this `AuthMechanism` as a string. + pub fn as_str(&self) -> &'static str { + match self { + AuthMechanism::ScramSha1 => SCRAM_SHA_1_STR, + AuthMechanism::ScramSha256 => SCRAM_SHA_256_STR, + AuthMechanism::MongoDbCr => MONGODB_CR_STR, + AuthMechanism::MongoDbX509 => MONGODB_X509_STR, + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => GSSAPI_STR, + AuthMechanism::Plain => PLAIN_STR, + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => MONGODB_AWS_STR, + AuthMechanism::MongoDbOidc => MONGODB_OIDC_STR, + } + } + + /// Get the default authSource for a given mechanism depending on the database provided in the + /// connection string. + pub(crate) fn default_source<'a>(&'a self, uri_db: Option<&'a str>) -> &'a str { + match self { + AuthMechanism::ScramSha1 | AuthMechanism::ScramSha256 | AuthMechanism::MongoDbCr => { + uri_db.unwrap_or("admin") + } + AuthMechanism::MongoDbX509 => "$external", + AuthMechanism::Plain => uri_db.unwrap_or("$external"), + AuthMechanism::MongoDbOidc => "$external", + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => "$external", + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => "$external", + } + } + + /// Constructs the first message to be sent to the server as part of the authentication + /// handshake, which can be used for speculative authentication. + pub(crate) async fn build_speculative_client_first( + &self, + credential: &Credential, + ) -> Result> { + match self { + Self::ScramSha1 => { + let client_first = ScramVersion::Sha1.build_speculative_client_first(credential)?; + + Ok(Some(ClientFirst::Scram(ScramVersion::Sha1, client_first))) + } + Self::ScramSha256 => { + let client_first = + ScramVersion::Sha256.build_speculative_client_first(credential)?; + + Ok(Some(ClientFirst::Scram(ScramVersion::Sha256, client_first))) + } + Self::MongoDbX509 => Ok(Some(ClientFirst::X509(Box::new( + x509::build_speculative_client_first(credential)?, + )))), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => Ok(None), + Self::Plain => Ok(None), + Self::MongoDbOidc => Ok(oidc::build_speculative_client_first(credential) + .await + .map(|comm| ClientFirst::Oidc(Box::new(comm)))), + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => Ok(None), + AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { + message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ + for password-based authentication instead" + .into(), + } + .into()), + } + } + + pub(crate) async fn authenticate_stream( + &self, + stream: &mut Connection, + credential: &Credential, + opts: &AuthOptions, + ) -> Result<()> { + self.validate_credential(credential)?; + + let server_api = opts.server_api.as_ref(); + match self { + AuthMechanism::ScramSha1 => { + ScramVersion::Sha1 + .authenticate_stream(stream, credential, server_api, None) + .await + } + AuthMechanism::ScramSha256 => { + ScramVersion::Sha256 + .authenticate_stream(stream, credential, server_api, None) + .await + } + AuthMechanism::MongoDbX509 => { + x509::authenticate_stream(stream, credential, server_api, None).await + } + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => { + gssapi::authenticate_stream( + stream, + credential, + server_api, + opts.resolver_config.as_ref(), + ) + .await + } + AuthMechanism::Plain => { + plain::authenticate_stream(stream, credential, server_api).await + } + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => { + aws::authenticate_stream(stream, credential, server_api, &opts.http_client).await + } + AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { + message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ + for password-based authentication instead" + .into(), + } + .into()), + AuthMechanism::MongoDbOidc => { + oidc::authenticate_stream(stream, credential, server_api, None).await + } + } + } + + pub(crate) async fn reauthenticate_stream( + &self, + stream: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + ) -> Result<()> { + self.validate_credential(credential)?; + + match self { + AuthMechanism::ScramSha1 + | AuthMechanism::ScramSha256 + | AuthMechanism::MongoDbX509 + | AuthMechanism::Plain + | AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { + message: format!( + "Reauthentication for authentication mechanism {:?} is not supported.", + self + ), + } + .into()), + #[cfg(feature = "gssapi-auth")] + AuthMechanism::Gssapi => Err(ErrorKind::Authentication { + message: format!( + "Reauthentication for authentication mechanism {:?} is not supported.", + self + ), + } + .into()), + #[cfg(feature = "aws-auth")] + AuthMechanism::MongoDbAws => Err(ErrorKind::Authentication { + message: format!( + "Reauthentication for authentication mechanism {:?} is not supported.", + self + ), + } + .into()), + AuthMechanism::MongoDbOidc => { + oidc::reauthenticate_stream(stream, credential, server_api).await + } + } + } +} + +impl FromStr for AuthMechanism { + type Err = Error; + + fn from_str(str: &str) -> Result { + match str { + SCRAM_SHA_1_STR => Ok(AuthMechanism::ScramSha1), + SCRAM_SHA_256_STR => Ok(AuthMechanism::ScramSha256), + MONGODB_CR_STR => Ok(AuthMechanism::MongoDbCr), + MONGODB_X509_STR => Ok(AuthMechanism::MongoDbX509), + #[cfg(feature = "gssapi-auth")] + GSSAPI_STR => Ok(AuthMechanism::Gssapi), + #[cfg(not(feature = "gssapi-auth"))] + GSSAPI_STR => Err(ErrorKind::InvalidArgument { + message: "GSSAPI auth is only supported with the gssapi-auth feature flag".into(), + } + .into()), + PLAIN_STR => Ok(AuthMechanism::Plain), + MONGODB_OIDC_STR => Ok(AuthMechanism::MongoDbOidc), + #[cfg(feature = "aws-auth")] + MONGODB_AWS_STR => Ok(AuthMechanism::MongoDbAws), + #[cfg(not(feature = "aws-auth"))] + MONGODB_AWS_STR => Err(ErrorKind::InvalidArgument { + message: "MONGODB-AWS auth is only supported with the aws-auth feature flag and \ + the tokio runtime" + .into(), + } + .into()), + + _ => Err(ErrorKind::InvalidArgument { + message: format!("invalid mechanism string: {}", str), + } + .into()), + } + } +} + +#[derive(Clone, Debug, Default)] +// Auxiliary information needed by authentication mechanisms. +pub(crate) struct AuthOptions { + server_api: Option, + #[cfg(feature = "aws-auth")] + http_client: crate::runtime::HttpClient, + #[cfg(feature = "gssapi-auth")] + resolver_config: Option, +} + +impl From<&ClientOptions> for AuthOptions { + fn from(opts: &ClientOptions) -> Self { + Self { + server_api: opts.server_api.clone(), + #[cfg(feature = "aws-auth")] + http_client: crate::runtime::HttpClient::default(), + #[cfg(feature = "gssapi-auth")] + resolver_config: opts.resolver_config.clone(), + } + } +} + +/// A struct containing authentication information. +/// +/// Some fields (mechanism and source) may be omitted and will either be negotiated or assigned a +/// default value, depending on the values of other fields in the credential. +#[derive(Clone, Default, Deserialize, TypedBuilder)] +#[derive_where(PartialEq)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct Credential { + /// The username to authenticate with. This applies to all mechanisms but may be omitted when + /// authenticating via MONGODB-X509. + pub username: Option, + + /// The database used to authenticate. This applies to all mechanisms and defaults to "admin" + /// in SCRAM authentication mechanisms, "$external" for GSSAPI and MONGODB-X509, and the + /// database name or "$external" for PLAIN. + pub source: Option, + + /// The password to authenticate with. This does not apply to all mechanisms. + pub password: Option, + + /// Which authentication mechanism to use. If not provided, one will be negotiated with the + /// server. + pub mechanism: Option, + + /// Additional properties for the given mechanism. + /// + /// If any value in the properties contains a comma, this field must be set directly on + /// [`ClientOptions`](crate::options::ClientOptions) and cannot be parsed from a connection + /// string. + pub mechanism_properties: Option, + + /// The token callback for OIDC authentication. + /// ``` + /// use mongodb::{error::Error, Client, options::{ClientOptions, oidc::{Callback, CallbackContext, IdpServerResponse}}}; + /// use std::time::{Duration, Instant}; + /// use futures::future::FutureExt; + /// async fn do_human_flow(c: CallbackContext) -> Result<(String, Option, Option), Error> { + /// // Do the human flow here see: https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow + /// Ok(("some_access_token".to_string(), Some(Instant::now() + Duration::from_secs(60 * 60 * 12)), Some("some_refresh_token".to_string()))) + /// } + /// + /// async fn setup_client() -> Result { + /// let mut opts = + /// ClientOptions::parse("mongodb://localhost:27017,localhost:27018/admin?authSource=admin&authMechanism=MONGODB-OIDC").await?; + /// opts.credential.as_mut().unwrap().oidc_callback = + /// Callback::human(move |c: CallbackContext| { + /// async move { + /// let (access_token, expires, refresh_token) = do_human_flow(c).await?; + /// Ok(IdpServerResponse::builder().access_token(access_token).expires(expires).refresh_token(refresh_token).build()) + /// }.boxed() + /// }); + /// Client::with_options(opts) + /// } + /// ``` + #[serde(skip)] + #[derive_where(skip)] + #[builder(default)] + pub oidc_callback: oidc::Callback, +} + +impl Credential { + pub(crate) fn resolved_source(&self) -> &str { + self.mechanism + .as_ref() + .map(|m| m.default_source(None)) + .unwrap_or("admin") + } + + /// If the mechanism is missing, append the appropriate mechanism negotiation key-value-pair to + /// the provided hello or legacy hello command document. + pub(crate) fn append_needed_mechanism_negotiation(&self, command: &mut RawDocumentBuf) { + if let (Some(username), None) = (self.username.as_ref(), self.mechanism.as_ref()) { + command.append( + cstr!("saslSupportedMechs"), + format!("{}.{}", self.resolved_source(), username), + ); + } + } + + /// Attempts to authenticate a stream according to this credential, returning an error + /// result on failure. A mechanism may be negotiated if one is not provided as part of the + /// credential. + pub(crate) async fn authenticate_stream( + &self, + conn: &mut Connection, + first_round: Option, + opts: &AuthOptions, + ) -> Result<()> { + let stream_description = conn.stream_description()?; + + // Verify server can authenticate. + if !stream_description.initial_server_type.can_auth() { + return Ok(()); + }; + + // If speculative authentication returned a response, then short-circuit the authentication + // logic and use the first round from the handshake. + if let Some(first_round) = first_round { + let server_api = opts.server_api.as_ref(); + return match first_round { + FirstRound::Scram(version, first_round) => { + version + .authenticate_stream(conn, self, server_api, first_round) + .await + } + FirstRound::X509(server_first) => { + x509::authenticate_stream(conn, self, server_api, server_first).await + } + FirstRound::Oidc(server_first) => { + oidc::authenticate_stream(conn, self, server_api, server_first).await + } + }; + } + + let mechanism = match self.mechanism { + None => Cow::Owned(AuthMechanism::from_stream_description(stream_description)), + Some(ref m) => Cow::Borrowed(m), + }; + // Authenticate according to the chosen mechanism. + mechanism.authenticate_stream(conn, self, opts).await + } + + #[cfg(test)] + pub(crate) fn serialize_for_client_options( + credential: &Option, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + use serde::ser::Serialize; + + #[derive(serde::Serialize)] + struct CredentialHelper<'a> { + authsource: Option<&'a String>, + authmechanism: Option<&'a str>, + authmechanismproperties: Option<&'a Document>, + } + + let state = credential.as_ref().map(|c| CredentialHelper { + authsource: c.source.as_ref(), + authmechanism: c.mechanism.as_ref().map(|s| s.as_str()), + authmechanismproperties: c.mechanism_properties.as_ref(), + }); + state.serialize(serializer) + } +} + +impl Debug for Credential { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("Credential") + .field(&"REDACTED".to_string()) + .finish() + } +} + +/// Contains the first client message sent as part of the authentication handshake. +#[derive(Debug)] +pub(crate) enum ClientFirst { + Scram(ScramVersion, scram::ClientFirst), + X509(Box), + Oidc(Box), +} + +impl ClientFirst { + pub(crate) fn to_document(&self) -> Result { + Ok(match self { + Self::Scram(version, client_first) => client_first.to_command(version)?.body, + Self::X509(command) => command.body.clone(), + Self::Oidc(command) => command.body.clone(), + }) + } + + pub(crate) fn into_first_round(self, server_first: Document) -> FirstRound { + match self { + Self::Scram(version, client_first) => FirstRound::Scram( + version, + scram::FirstRound { + client_first, + server_first, + }, + ), + Self::X509(..) => FirstRound::X509(server_first), + Self::Oidc(..) => FirstRound::Oidc(server_first), + } + } +} + +/// Contains the complete first round of the authentication handshake, including the client message +/// and the server response. +#[derive(Debug)] +pub(crate) enum FirstRound { + Scram(ScramVersion, scram::FirstRound), + X509(Document), + Oidc(Document), +} + +pub(crate) fn generate_nonce_bytes() -> [u8; 32] { + rand::thread_rng().gen() +} + +pub(crate) fn generate_nonce() -> String { + let result = generate_nonce_bytes(); + base64::encode(result) +} + +fn mac( + key: &[u8], + input: &[u8], + auth_mechanism: &str, +) -> Result> { + let mut mac = ::new_from_slice(key) + .map_err(|_| Error::unknown_authentication_error(auth_mechanism))?; + mac.update(input); + Ok(mac.finalize().into_bytes()) +} diff --git a/src/client/auth/aws.rs b/src/client/auth/aws.rs index c4730bf6a..cb9a938b5 100644 --- a/src/client/auth/aws.rs +++ b/src/client/auth/aws.rs @@ -1,10 +1,12 @@ -use std::{fs::File, io::Read}; +use std::{fs::File, io::Read, time::Duration}; use chrono::{offset::Utc, DateTime}; use hmac::Hmac; +use once_cell::sync::Lazy; use rand::distributions::{Alphanumeric, DistString}; use serde::Deserialize; use sha2::{Digest, Sha256}; +use tokio::sync::Mutex; use crate::{ bson::{doc, rawdoc, spec::BinarySubtype, Binary, Bson, Document}, @@ -20,19 +22,40 @@ use crate::{ cmap::Connection, error::{Error, Result}, runtime::HttpClient, + serde_util, }; +#[cfg(not(feature = "bson-3"))] +use crate::bson_compat::DocumentExt as _; + const AWS_ECS_IP: &str = "169.254.170.2"; const AWS_EC2_IP: &str = "169.254.169.254"; const AWS_LONG_DATE_FMT: &str = "%Y%m%dT%H%M%SZ"; const MECH_NAME: &str = "MONGODB-AWS"; +static CACHED_CREDENTIAL: Lazy>> = Lazy::new(|| Mutex::new(None)); + /// Performs MONGODB-AWS authentication for a given stream. pub(super) async fn authenticate_stream( conn: &mut Connection, credential: &Credential, server_api: Option<&ServerApi>, http_client: &HttpClient, +) -> Result<()> { + match authenticate_stream_inner(conn, credential, server_api, http_client).await { + Ok(()) => Ok(()), + Err(error) => { + *CACHED_CREDENTIAL.lock().await = None; + Err(error) + } + } +} + +async fn authenticate_stream_inner( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + http_client: &HttpClient, ) -> Result<()> { let source = match credential.source.as_deref() { Some("$external") | None => "$external", @@ -52,8 +75,7 @@ pub(super) async fn authenticate_stream( // channel binding is not supported. "p": 110i32, }; - let mut client_first_payload_bytes = Vec::new(); - client_first_payload.to_writer(&mut client_first_payload_bytes)?; + let client_first_payload_bytes = client_first_payload.encode_to_vec()?; let sasl_start = SaslStart::new( source.into(), @@ -61,14 +83,30 @@ pub(super) async fn authenticate_stream( client_first_payload_bytes, server_api.cloned(), ); - let client_first = sasl_start.into_command(); + let client_first = sasl_start.into_command()?; - let server_first_response = conn.send_command(client_first, None).await?; + let server_first_response = conn.send_message(client_first).await?; let server_first = ServerFirst::parse(server_first_response.auth_response_body(MECH_NAME)?)?; server_first.validate(&nonce)?; - let aws_credential = AwsCredential::get(credential, http_client).await?; + let aws_credential = { + // Limit scope of this variable to avoid holding onto the lock for the duration of + // authenticate_stream. + let cached_credential = CACHED_CREDENTIAL.lock().await; + match *cached_credential { + Some(ref aws_credential) if !aws_credential.is_expired() => aws_credential.clone(), + _ => { + // From the spec: the driver MUST not place a lock on making a request. + drop(cached_credential); + let aws_credential = AwsCredential::get(credential, http_client).await?; + if aws_credential.expiration.is_some() { + *CACHED_CREDENTIAL.lock().await = Some(aws_credential.clone()); + } + aws_credential + } + } + }; let date = Utc::now(); @@ -87,8 +125,7 @@ pub(super) async fn authenticate_stream( client_second_payload.insert("t", security_token); } - let mut client_second_payload_bytes = Vec::new(); - client_second_payload.to_writer(&mut client_second_payload_bytes)?; + let client_second_payload_bytes = client_second_payload.encode_to_vec()?; let sasl_continue = SaslContinue::new( source.into(), @@ -99,7 +136,7 @@ pub(super) async fn authenticate_stream( let client_second = sasl_continue.into_command(); - let server_second_response = conn.send_command(client_second, None).await?; + let server_second_response = conn.send_message(client_second).await?; let server_second = SaslResponse::parse( MECH_NAME, server_second_response.auth_response_body(MECH_NAME)?, @@ -117,7 +154,7 @@ pub(super) async fn authenticate_stream( } /// Contains the credentials for MONGODB-AWS authentication. -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "PascalCase")] pub(crate) struct AwsCredential { access_key_id: String, @@ -126,6 +163,20 @@ pub(crate) struct AwsCredential { #[serde(alias = "Token")] session_token: Option, + + #[serde( + default, + deserialize_with = "serde_util::deserialize_datetime_option_from_double_or_string" + )] + expiration: Option, +} + +fn non_empty(s: Option) -> Option { + match s { + None => None, + Some(s) if s.is_empty() => None, + Some(s) => Some(s), + } } impl AwsCredential { @@ -135,17 +186,17 @@ impl AwsCredential { let access_key = credential .username .clone() - .or_else(|| std::env::var("AWS_ACCESS_KEY_ID").ok()); + .or_else(|| non_empty(std::env::var("AWS_ACCESS_KEY_ID").ok())); let secret_key = credential .password .clone() - .or_else(|| std::env::var("AWS_SECRET_ACCESS_KEY").ok()); + .or_else(|| non_empty(std::env::var("AWS_SECRET_ACCESS_KEY").ok())); let session_token = credential .mechanism_properties .as_ref() .and_then(|d| d.get_str("AWS_SESSION_TOKEN").ok()) .map(|s| s.to_string()) - .or_else(|| std::env::var("AWS_SESSION_TOKEN").ok()); + .or_else(|| non_empty(std::env::var("AWS_SESSION_TOKEN").ok())); let found_access_key = access_key.is_some(); let found_secret_key = secret_key.is_some(); @@ -157,6 +208,7 @@ impl AwsCredential { access_key_id: access_key, secret_access_key: secret_key, session_token, + expiration: None, }); } @@ -236,7 +288,7 @@ impl AwsCredential { .map_err(|_| Error::unknown_authentication_error(MECH_NAME))? .to_owned(); - Ok(bson::from_document(credential)?) + Ok(crate::bson_compat::deserialize_from_document(credential)?) } /// Obtains credentials from the ECS endpoint. @@ -254,7 +306,7 @@ impl AwsCredential { /// Obtains temporary credentials for an EC2 instance to use for authentication. async fn get_from_ec2(http_client: &HttpClient) -> Result { let temporary_token = http_client - .put(&format!("http://{}/latest/api/token", AWS_EC2_IP)) + .put(format!("http://{}/latest/api/token", AWS_EC2_IP)) .headers(&[("X-aws-ec2-metadata-token-ttl-seconds", "30")]) .send_and_get_string() .await @@ -405,20 +457,30 @@ impl AwsCredential { Ok(auth_header) } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) fn access_key(&self) -> &str { &self.access_key_id } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) fn secret_key(&self) -> &str { &self.secret_access_key } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) fn session_token(&self) -> Option<&str> { self.session_token.as_deref() } + + fn is_expired(&self) -> bool { + match self.expiration { + Some(expiration) => { + expiration.saturating_duration_since(crate::bson::DateTime::now()) + < Duration::from_secs(5 * 60) + } + None => true, + } + } } /// The response from the server to the `saslStart` command in a MONGODB-AWS authentication attempt. @@ -451,7 +513,7 @@ impl ServerFirst { let ServerFirstPayload { server_nonce, sts_host, - } = bson::from_slice(payload.as_slice()) + } = crate::bson_compat::deserialize_from_slice(payload.as_slice()) .map_err(|_| Error::invalid_authentication_response(MECH_NAME))?; Ok(Self { @@ -481,7 +543,7 @@ impl ServerFirst { MECH_NAME, "sts host must be non-empty", )) - } else if self.sts_host.as_bytes().len() > 255 { + } else if self.sts_host.len() > 255 { Err(Error::authentication_error( MECH_NAME, "sts host cannot be more than 255 bytes", @@ -496,3 +558,45 @@ impl ServerFirst { } } } + +#[cfg(test)] +pub(crate) mod test_utils { + use super::{AwsCredential, CACHED_CREDENTIAL}; + + pub(crate) async fn cached_credential() -> Option { + CACHED_CREDENTIAL.lock().await.clone() + } + + pub(crate) async fn clear_cached_credential() { + *CACHED_CREDENTIAL.lock().await = None; + } + + pub(crate) async fn poison_cached_credential() { + CACHED_CREDENTIAL + .lock() + .await + .as_mut() + .unwrap() + .access_key_id = "bad".into(); + } + + pub(crate) async fn cached_access_key_id() -> String { + cached_credential().await.unwrap().access_key_id + } + + pub(crate) async fn cached_secret_access_key() -> String { + cached_credential().await.unwrap().secret_access_key + } + + pub(crate) async fn cached_session_token() -> Option { + cached_credential().await.unwrap().session_token + } + + pub(crate) async fn cached_expiration() -> crate::bson::DateTime { + cached_credential().await.unwrap().expiration.unwrap() + } + + pub(crate) async fn set_cached_expiration(expiration: crate::bson::DateTime) { + CACHED_CREDENTIAL.lock().await.as_mut().unwrap().expiration = Some(expiration); + } +} diff --git a/src/client/auth/gssapi.rs b/src/client/auth/gssapi.rs new file mode 100644 index 000000000..b554dd353 --- /dev/null +++ b/src/client/auth/gssapi.rs @@ -0,0 +1,369 @@ +use cross_krb5::{ClientCtx, InitiateFlags, K5Ctx, PendingClientCtx, Step}; +use hickory_resolver::proto::rr::RData; + +use crate::{ + bson::Bson, + client::{ + auth::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + Credential, + GSSAPI_STR, + }, + options::ServerApi, + }, + cmap::Connection, + error::{Error, Result}, + options::ResolverConfig, +}; + +const SERVICE_NAME: &str = "SERVICE_NAME"; +const CANONICALIZE_HOST_NAME: &str = "CANONICALIZE_HOST_NAME"; +const SERVICE_REALM: &str = "SERVICE_REALM"; +const SERVICE_HOST: &str = "SERVICE_HOST"; + +#[derive(Debug, Clone)] +pub(crate) struct GssapiProperties { + pub service_name: String, + pub canonicalize_host_name: CanonicalizeHostName, + pub service_realm: Option, + pub service_host: Option, +} + +#[derive(Debug, Default, Clone, PartialEq)] +pub(crate) enum CanonicalizeHostName { + #[default] + None, + Forward, + ForwardAndReverse, +} + +pub(crate) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + resolver_config: Option<&ResolverConfig>, +) -> Result<()> { + let properties = GssapiProperties::from_credential(credential)?; + + let conn_host = conn.address.host().to_string(); + let hostname = properties.service_host.as_ref().unwrap_or(&conn_host); + let hostname = canonicalize_hostname( + hostname, + &properties.canonicalize_host_name, + resolver_config, + ) + .await?; + + let user_principal = credential.username.clone(); + let (mut authenticator, initial_token) = + GssapiAuthenticator::init(user_principal, properties.clone(), &hostname).await?; + + let source = credential.source.as_deref().unwrap_or("$external"); + + let command = SaslStart::new( + source.to_string(), + crate::client::auth::AuthMechanism::Gssapi, + initial_token, + server_api.cloned(), + ) + .into_command()?; + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + let mut conversation_id = Some(sasl_response.conversation_id); + let mut payload = sasl_response.payload; + + // Limit number of auth challenge steps (typically, only one step is needed, however + // different configurations may require more). + for _ in 0..10 { + let challenge = payload.as_slice(); + let output_token = authenticator.step(challenge).await?; + + // The step may return None, which is a valid final step. We still need to + // send a saslContinue command, so we send an empty payload if there is no + // token. + let token = output_token.unwrap_or(vec![]); + let command = SaslContinue::new( + source.to_string(), + conversation_id.clone().unwrap(), + token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + conversation_id = Some(sasl_response.conversation_id); + payload = sasl_response.payload; + + // Although unlikely, there are cases where authentication can be done + // at this point. + if sasl_response.done { + return Ok(()); + } + + // The authenticator is considered "complete" when the Kerberos auth + // process is done. However, this is not the end of the full auth flow. + // We no longer need to issue challenges to the authenticator, so we + // break the loop and continue with the rest of the flow. + if authenticator.is_complete() { + break; + } + } + + let output_token = authenticator.do_unwrap_wrap(payload.as_slice())?; + let command = SaslContinue::new( + source.to_string(), + conversation_id.unwrap(), + output_token, + server_api.cloned(), + ) + .into_command(); + + let response_doc = conn.send_message(command).await?; + let sasl_response = + SaslResponse::parse(GSSAPI_STR, response_doc.auth_response_body(GSSAPI_STR)?)?; + + if sasl_response.done { + Ok(()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "GSSAPI authentication failed after 10 attempts", + )) + } +} + +impl GssapiProperties { + pub fn from_credential(credential: &Credential) -> Result { + let mut properties = GssapiProperties { + service_name: "mongodb".to_string(), + canonicalize_host_name: CanonicalizeHostName::None, + service_realm: None, + service_host: None, + }; + + if let Some(mechanism_properties) = &credential.mechanism_properties { + if let Some(Bson::String(name)) = mechanism_properties.get(SERVICE_NAME) { + properties.service_name = name.clone(); + } + + if let Some(canonicalize) = mechanism_properties.get(CANONICALIZE_HOST_NAME) { + properties.canonicalize_host_name = match canonicalize { + Bson::String(s) => match s.as_str() { + "none" => CanonicalizeHostName::None, + "forward" => CanonicalizeHostName::Forward, + "forwardAndReverse" => CanonicalizeHostName::ForwardAndReverse, + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + format!( + "Invalid CANONICALIZE_HOST_NAME value: {s}. Valid values are \ + 'none', 'forward', 'forwardAndReverse'", + ) + .as_str(), + )) + } + }, + Bson::Boolean(true) => CanonicalizeHostName::ForwardAndReverse, + Bson::Boolean(false) => CanonicalizeHostName::None, + _ => { + return Err(Error::authentication_error( + GSSAPI_STR, + "CANONICALIZE_HOST_NAME must be a string or boolean", + )) + } + }; + } + + if let Some(Bson::String(realm)) = mechanism_properties.get(SERVICE_REALM) { + properties.service_realm = Some(realm.clone()); + } + + if let Some(Bson::String(host)) = mechanism_properties.get(SERVICE_HOST) { + properties.service_host = Some(host.clone()); + } + } + + Ok(properties) + } +} + +struct GssapiAuthenticator { + pending_ctx: Option, + established_ctx: Option, + user_principal: Option, + is_complete: bool, +} + +impl GssapiAuthenticator { + // Initialize the GssapiAuthenticator by creating a PendingClientCtx and + // getting an initial token to send to the server. + async fn init( + user_principal: Option, + properties: GssapiProperties, + hostname: &str, + ) -> Result<(Self, Vec)> { + let service_name: &str = properties.service_name.as_ref(); + let mut service_principal = format!("{service_name}/{hostname}"); + if let Some(service_realm) = properties.service_realm.as_ref() { + service_principal = format!("{service_principal}@{service_realm}"); + } else if let Some(user_principal) = user_principal.as_ref() { + if let Some(idx) = user_principal.find('@') { + // If no SERVICE_REALM was specified, use realm specified in the + // username. Note that `realm` starts with '@'. + let (_, realm) = user_principal.split_at(idx); + service_principal = format!("{service_principal}{realm}"); + } + } + + let (pending_ctx, initial_token) = ClientCtx::new( + InitiateFlags::empty(), + user_principal.as_deref(), + &service_principal, + None, // No channel bindings + ) + .map_err(|e| { + Error::authentication_error( + GSSAPI_STR, + &format!("Failed to initialize GSSAPI context: {e}"), + ) + })?; + + Ok(( + Self { + pending_ctx: Some(pending_ctx), + established_ctx: None, + user_principal, + is_complete: false, + }, + initial_token.to_vec(), + )) + } + + // Issue the server provided token to the client context. If the ClientCtx + // is established, an optional final token that must be sent to the server + // may be returned; otherwise another token to pass to the server is + // returned and the client context remains in the pending state. + async fn step(&mut self, challenge: &[u8]) -> Result>> { + if challenge.is_empty() { + Err(Error::authentication_error( + GSSAPI_STR, + "Expected challenge data for GSSAPI continuation", + )) + } else if let Some(pending_ctx) = self.pending_ctx.take() { + match pending_ctx.step(challenge).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI step failed: {e}")) + })? { + Step::Finished((ctx, token)) => { + self.is_complete = true; + self.established_ctx = Some(ctx); + Ok(token.map(|t| t.to_vec())) + } + Step::Continue((ctx, token)) => { + self.pending_ctx = Some(ctx); + Ok(Some(token.to_vec())) + } + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "Authentication context not initialized", + )) + } + } + + // Perform the final step of Kerberos authentication by gss_unwrap-ing the + // final server challenge, then wrapping the protocol bytes + user principal. + // The resulting token must be sent to the server. + fn do_unwrap_wrap(&mut self, payload: &[u8]) -> Result> { + if let Some(mut established_ctx) = self.established_ctx.take() { + let _ = established_ctx.unwrap(payload).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI unwrap failed: {e}")) + })?; + + if let Some(user_principal) = self.user_principal.take() { + let bytes: &[u8] = &[0x1, 0x0, 0x0, 0x0]; + let bytes = [bytes, user_principal.as_bytes()].concat(); + let output_token = established_ctx.wrap(false, bytes.as_slice()).map_err(|e| { + Error::authentication_error(GSSAPI_STR, &format!("GSSAPI wrap failed: {e}")) + })?; + Ok(output_token.to_vec()) + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "User principal not specified", + )) + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + "Authentication context not established", + )) + } + } + + fn is_complete(&self) -> bool { + self.is_complete + } +} + +async fn canonicalize_hostname( + hostname: &str, + mode: &CanonicalizeHostName, + resolver_config: Option<&ResolverConfig>, +) -> Result { + if mode == &CanonicalizeHostName::None { + return Ok(hostname.to_string()); + } + + let resolver = + crate::runtime::AsyncResolver::new(resolver_config.map(|c| c.inner.clone())).await?; + + match mode { + CanonicalizeHostName::Forward => { + let lookup_records = resolver.cname_lookup(hostname).await?; + + if let Some(first_record) = lookup_records.records().first() { + if let Some(RData::CNAME(cname)) = first_record.data() { + Ok(cname.to_lowercase().to_string()) + } else { + Ok(hostname.to_string()) + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )) + } + } + CanonicalizeHostName::ForwardAndReverse => { + // forward lookup + let ips = resolver.ip_lookup(hostname).await?; + + if let Some(first_address) = ips.iter().next() { + // reverse lookup + match resolver.reverse_lookup(first_address).await { + Ok(reverse_lookup) => { + if let Some(name) = reverse_lookup.iter().next() { + Ok(name.to_lowercase().to_string()) + } else { + Ok(hostname.to_lowercase()) + } + } + Err(_) => Ok(hostname.to_lowercase()), + } + } else { + Err(Error::authentication_error( + GSSAPI_STR, + &format!("No addresses found for hostname: {hostname}"), + )) + } + } + CanonicalizeHostName::None => unreachable!(), + } +} diff --git a/src/client/auth/mod.rs b/src/client/auth/mod.rs deleted file mode 100644 index edf9a0959..000000000 --- a/src/client/auth/mod.rs +++ /dev/null @@ -1,531 +0,0 @@ -//! Contains the types needed to specify the auth configuration for a -//! [`Client`](struct.Client.html). - -#[cfg(feature = "aws-auth")] -pub(crate) mod aws; -mod plain; -mod sasl; -mod scram; -#[cfg(test)] -mod test; -mod x509; - -use std::{borrow::Cow, fmt::Debug, str::FromStr}; - -use hmac::{digest::KeyInit, Mac}; -use rand::Rng; -use serde::Deserialize; -use typed_builder::TypedBuilder; - -use self::scram::ScramVersion; -use crate::{ - bson::Document, - client::options::ServerApi, - cmap::{Command, Connection, StreamDescription}, - error::{Error, ErrorKind, Result}, -}; - -const SCRAM_SHA_1_STR: &str = "SCRAM-SHA-1"; -const SCRAM_SHA_256_STR: &str = "SCRAM-SHA-256"; -const MONGODB_CR_STR: &str = "MONGODB-CR"; -const GSSAPI_STR: &str = "GSSAPI"; -const MONGODB_AWS_STR: &str = "MONGODB-AWS"; -const MONGODB_X509_STR: &str = "MONGODB-X509"; -const PLAIN_STR: &str = "PLAIN"; - -/// The authentication mechanisms supported by MongoDB. -/// -/// Note: not all of these mechanisms are currently supported by the driver. -#[derive(Clone, Deserialize, PartialEq, Debug)] -#[non_exhaustive] -pub enum AuthMechanism { - /// MongoDB Challenge Response nonce and MD5 based authentication system. It is currently - /// deprecated and will never be supported by this driver. - MongoDbCr, - - /// The SCRAM-SHA-1 mechanism as defined in [RFC 5802](http://tools.ietf.org/html/rfc5802). - /// - /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-scram/) for more information. - ScramSha1, - - /// The SCRAM-SHA-256 mechanism which extends [RFC 5802](http://tools.ietf.org/html/rfc5802) and is formally defined in [RFC 7677](https://tools.ietf.org/html/rfc7677). - /// - /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-scram/) for more information. - ScramSha256, - - /// The MONGODB-X509 mechanism based on the usage of X.509 certificates to validate a client - /// where the distinguished subject name of the client certificate acts as the username. - /// - /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-x.509/) for more information. - MongoDbX509, - - /// Kerberos authentication mechanism as defined in [RFC 4752](http://tools.ietf.org/html/rfc4752). - /// - /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/kerberos/) for more information. - /// - /// Note: This mechanism is not currently supported by this driver but will be in the future. - Gssapi, - - /// The SASL PLAIN mechanism, as defined in [RFC 4616](), is used in MongoDB to perform LDAP - /// authentication and cannot be used for any other type of authentication. - /// Since the credentials are stored outside of MongoDB, the "$external" database must be used - /// for authentication. - /// - /// See the [MongoDB documentation](https://www.mongodb.com/docs/manual/core/security-ldap/#ldap-proxy-authentication) for more information on LDAP authentication. - Plain, - - /// MONGODB-AWS authenticates using AWS IAM credentials (an access key ID and a secret access - /// key), temporary AWS IAM credentials obtained from an AWS Security Token Service (STS) - /// Assume Role request, or temporary AWS IAM credentials assigned to an EC2 instance or ECS - /// task. - /// - /// Note: Only server versions 4.4+ support AWS authentication. Additionally, the driver only - /// supports AWS authentication with the tokio runtime. - #[cfg(feature = "aws-auth")] - MongoDbAws, -} - -impl AuthMechanism { - fn from_scram_version(scram: &ScramVersion) -> Self { - match scram { - ScramVersion::Sha1 => Self::ScramSha1, - ScramVersion::Sha256 => Self::ScramSha256, - } - } - - pub(crate) fn from_stream_description(description: &StreamDescription) -> AuthMechanism { - let scram_sha_256_found = description - .sasl_supported_mechs - .as_ref() - .map(|ms| ms.iter().any(|m| m == AuthMechanism::ScramSha256.as_str())) - .unwrap_or(false); - - if scram_sha_256_found { - AuthMechanism::ScramSha256 - } else { - AuthMechanism::ScramSha1 - } - } - - /// Determines if the provided credentials have the required information to perform - /// authentication. - pub fn validate_credential(&self, credential: &Credential) -> Result<()> { - match self { - AuthMechanism::ScramSha1 | AuthMechanism::ScramSha256 => { - if credential.username.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "No username provided for SCRAM authentication".to_string(), - } - .into()); - }; - Ok(()) - } - AuthMechanism::MongoDbX509 => { - if credential.password.is_some() { - return Err(ErrorKind::InvalidArgument { - message: "A password cannot be specified with MONGODB-X509".to_string(), - } - .into()); - } - - if credential.source.as_deref().unwrap_or("$external") != "$external" { - return Err(ErrorKind::InvalidArgument { - message: "only $external may be specified as an auth source for \ - MONGODB-X509" - .to_string(), - } - .into()); - } - - Ok(()) - } - AuthMechanism::Plain => { - if credential.username.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "No username provided for PLAIN authentication".to_string(), - } - .into()); - } - - if credential.username.as_deref() == Some("") { - return Err(ErrorKind::InvalidArgument { - message: "Username for PLAIN authentication must be non-empty".to_string(), - } - .into()); - } - - if credential.password.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "No password provided for PLAIN authentication".to_string(), - } - .into()); - } - - Ok(()) - } - #[cfg(feature = "aws-auth")] - AuthMechanism::MongoDbAws => { - if credential.username.is_some() && credential.password.is_none() { - return Err(ErrorKind::InvalidArgument { - message: "Username cannot be provided without password for MONGODB-AWS \ - authentication" - .to_string(), - } - .into()); - } - - Ok(()) - } - _ => Ok(()), - } - } - - /// Returns this `AuthMechanism` as a string. - pub fn as_str(&self) -> &'static str { - match self { - AuthMechanism::ScramSha1 => SCRAM_SHA_1_STR, - AuthMechanism::ScramSha256 => SCRAM_SHA_256_STR, - AuthMechanism::MongoDbCr => MONGODB_CR_STR, - AuthMechanism::MongoDbX509 => MONGODB_X509_STR, - AuthMechanism::Gssapi => GSSAPI_STR, - AuthMechanism::Plain => PLAIN_STR, - #[cfg(feature = "aws-auth")] - AuthMechanism::MongoDbAws => MONGODB_AWS_STR, - } - } - - /// Get the default authSource for a given mechanism depending on the database provided in the - /// connection string. - pub(crate) fn default_source<'a>(&'a self, uri_db: Option<&'a str>) -> &'a str { - // TODO: fill in others as they're implemented - match self { - AuthMechanism::ScramSha1 | AuthMechanism::ScramSha256 | AuthMechanism::MongoDbCr => { - uri_db.unwrap_or("admin") - } - AuthMechanism::MongoDbX509 => "$external", - AuthMechanism::Plain => uri_db.unwrap_or("$external"), - #[cfg(feature = "aws-auth")] - AuthMechanism::MongoDbAws => "$external", - _ => "", - } - } - - /// Constructs the first message to be sent to the server as part of the authentication - /// handshake, which can be used for speculative authentication. - pub(crate) fn build_speculative_client_first( - &self, - credential: &Credential, - ) -> Result> { - match self { - Self::ScramSha1 => { - let client_first = ScramVersion::Sha1.build_speculative_client_first(credential)?; - - Ok(Some(ClientFirst::Scram(ScramVersion::Sha1, client_first))) - } - Self::ScramSha256 => { - let client_first = - ScramVersion::Sha256.build_speculative_client_first(credential)?; - - Ok(Some(ClientFirst::Scram(ScramVersion::Sha256, client_first))) - } - Self::MongoDbX509 => Ok(Some(ClientFirst::X509(Box::new( - x509::build_speculative_client_first(credential), - )))), - Self::Plain => Ok(None), - #[cfg(feature = "aws-auth")] - AuthMechanism::MongoDbAws => Ok(None), - AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { - message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ - for password-based authentication instead" - .into(), - } - .into()), - _ => Err(ErrorKind::Authentication { - message: format!("Authentication mechanism {:?} not yet implemented.", self), - } - .into()), - } - } - - pub(crate) async fn authenticate_stream( - &self, - stream: &mut Connection, - credential: &Credential, - server_api: Option<&ServerApi>, - #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, - ) -> Result<()> { - self.validate_credential(credential)?; - - match self { - AuthMechanism::ScramSha1 => { - ScramVersion::Sha1 - .authenticate_stream(stream, credential, server_api, None) - .await - } - AuthMechanism::ScramSha256 => { - ScramVersion::Sha256 - .authenticate_stream(stream, credential, server_api, None) - .await - } - AuthMechanism::MongoDbX509 => { - x509::authenticate_stream(stream, credential, server_api, None).await - } - AuthMechanism::Plain => { - plain::authenticate_stream(stream, credential, server_api).await - } - #[cfg(feature = "aws-auth")] - AuthMechanism::MongoDbAws => { - aws::authenticate_stream(stream, credential, server_api, http_client).await - } - AuthMechanism::MongoDbCr => Err(ErrorKind::Authentication { - message: "MONGODB-CR is deprecated and not supported by this driver. Use SCRAM \ - for password-based authentication instead" - .into(), - } - .into()), - _ => Err(ErrorKind::Authentication { - message: format!("Authentication mechanism {:?} not yet implemented.", self), - } - .into()), - } - } -} - -impl FromStr for AuthMechanism { - type Err = Error; - - fn from_str(str: &str) -> Result { - match str { - SCRAM_SHA_1_STR => Ok(AuthMechanism::ScramSha1), - SCRAM_SHA_256_STR => Ok(AuthMechanism::ScramSha256), - MONGODB_CR_STR => Ok(AuthMechanism::MongoDbCr), - MONGODB_X509_STR => Ok(AuthMechanism::MongoDbX509), - GSSAPI_STR => Ok(AuthMechanism::Gssapi), - PLAIN_STR => Ok(AuthMechanism::Plain), - - #[cfg(feature = "aws-auth")] - MONGODB_AWS_STR => Ok(AuthMechanism::MongoDbAws), - #[cfg(not(feature = "aws-auth"))] - MONGODB_AWS_STR => Err(ErrorKind::InvalidArgument { - message: "MONGODB-AWS auth is only supported with the aws-auth feature flag and \ - the tokio runtime" - .into(), - } - .into()), - - _ => Err(ErrorKind::InvalidArgument { - message: format!("invalid mechanism string: {}", str), - } - .into()), - } - } -} - -/// A struct containing authentication information. -/// -/// Some fields (mechanism and source) may be omitted and will either be negotiated or assigned a -/// default value, depending on the values of other fields in the credential. -#[derive(Clone, Default, Deserialize, TypedBuilder, PartialEq)] -#[builder(field_defaults(default, setter(into)))] -#[non_exhaustive] -pub struct Credential { - /// The username to authenticate with. This applies to all mechanisms but may be omitted when - /// authenticating via MONGODB-X509. - pub username: Option, - - /// The database used to authenticate. This applies to all mechanisms and defaults to "admin" - /// in SCRAM authentication mechanisms, "$external" for GSSAPI and MONGODB-X509, and the - /// database name or "$external" for PLAIN. - pub source: Option, - - /// The password to authenticate with. This does not apply to all mechanisms. - pub password: Option, - - /// Which authentication mechanism to use. If not provided, one will be negotiated with the - /// server. - pub mechanism: Option, - - /// Additional properties for the given mechanism. - pub mechanism_properties: Option, -} - -impl Credential { - #[cfg(all(test, not(feature = "sync"), not(feature = "tokio-sync")))] - pub(crate) fn into_document(mut self) -> Document { - use crate::bson::Bson; - - let mut doc = Document::new(); - - if let Some(s) = self.username.take() { - doc.insert("username", s); - } - - if let Some(s) = self.password.take() { - doc.insert("password", s); - } else { - doc.insert("password", Bson::Null); - } - - if let Some(s) = self.source.take() { - doc.insert("db", s); - } - - doc - } - - pub(crate) fn resolved_source(&self) -> &str { - self.mechanism - .as_ref() - .map(|m| m.default_source(None)) - .unwrap_or("admin") - } - - /// If the mechanism is missing, append the appropriate mechanism negotiation key-value-pair to - /// the provided hello or legacy hello command document. - pub(crate) fn append_needed_mechanism_negotiation(&self, command: &mut Document) { - if let (Some(username), None) = (self.username.as_ref(), self.mechanism.as_ref()) { - command.insert( - "saslSupportedMechs", - format!("{}.{}", self.resolved_source(), username), - ); - } - } - - /// Attempts to authenticate a stream according this credential, returning an error - /// result on failure. A mechanism may be negotiated if one is not provided as part of the - /// credential. - pub(crate) async fn authenticate_stream( - &self, - conn: &mut Connection, - server_api: Option<&ServerApi>, - first_round: Option, - #[cfg(feature = "aws-auth")] http_client: &crate::runtime::HttpClient, - ) -> Result<()> { - let stream_description = conn.stream_description()?; - - // Verify server can authenticate. - if !stream_description.initial_server_type.can_auth() { - return Ok(()); - }; - - // If speculative authentication returned a response, then short-circuit the authentication - // logic and use the first round from the handshake. - if let Some(first_round) = first_round { - return match first_round { - FirstRound::Scram(version, first_round) => { - version - .authenticate_stream(conn, self, server_api, first_round) - .await - } - FirstRound::X509(server_first) => { - x509::authenticate_stream(conn, self, server_api, server_first).await - } - }; - } - - let mechanism = match self.mechanism { - None => Cow::Owned(AuthMechanism::from_stream_description(stream_description)), - Some(ref m) => Cow::Borrowed(m), - }; - - // Authenticate according to the chosen mechanism. - mechanism - .authenticate_stream( - conn, - self, - server_api, - #[cfg(feature = "aws-auth")] - http_client, - ) - .await - } - - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - credential: &Option, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - use serde::ser::Serialize; - - #[derive(serde::Serialize)] - struct CredentialHelper<'a> { - authsource: Option<&'a String>, - authmechanism: Option<&'a str>, - authmechanismproperties: Option<&'a Document>, - } - - let state = credential.as_ref().map(|c| CredentialHelper { - authsource: c.source.as_ref(), - authmechanism: c.mechanism.as_ref().map(|s| s.as_str()), - authmechanismproperties: c.mechanism_properties.as_ref(), - }); - state.serialize(serializer) - } -} - -impl Debug for Credential { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Credential") - .field(&"REDACTED".to_string()) - .finish() - } -} - -/// Contains the first client message sent as part of the authentication handshake. -pub(crate) enum ClientFirst { - Scram(ScramVersion, scram::ClientFirst), - X509(Box), -} - -impl ClientFirst { - pub(crate) fn to_document(&self) -> Document { - match self { - Self::Scram(version, client_first) => client_first.to_command(version).body, - Self::X509(command) => command.body.clone(), - } - } - - pub(crate) fn into_first_round(self, server_first: Document) -> FirstRound { - match self { - Self::Scram(version, client_first) => FirstRound::Scram( - version, - scram::FirstRound { - client_first, - server_first, - }, - ), - Self::X509(..) => FirstRound::X509(server_first), - } - } -} - -/// Contains the complete first round of the authentication handshake, including the client message -/// and the server response. -#[derive(Debug)] -pub(crate) enum FirstRound { - Scram(ScramVersion, scram::FirstRound), - X509(Document), -} - -pub(crate) fn generate_nonce_bytes() -> [u8; 32] { - rand::thread_rng().gen() -} - -pub(crate) fn generate_nonce() -> String { - let result = generate_nonce_bytes(); - base64::encode(result) -} - -fn mac( - key: &[u8], - input: &[u8], - auth_mechanism: &str, -) -> Result> { - let mut mac = ::new_from_slice(key) - .map_err(|_| Error::unknown_authentication_error(auth_mechanism))?; - mac.update(input); - Ok(mac.finalize().into_bytes()) -} diff --git a/src/client/auth/oidc.rs b/src/client/auth/oidc.rs new file mode 100644 index 000000000..17d069ad1 --- /dev/null +++ b/src/client/auth/oidc.rs @@ -0,0 +1,1010 @@ +//! Contains the functionality for [`OIDC`](https://openid.net/developers/how-connect-works/) authorization and authentication. +use serde::Deserialize; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::sync::Mutex; +use typed_builder::TypedBuilder; + +use crate::{ + bson::{doc, rawdoc, spec::BinarySubtype, Binary, Document}, + bson_compat::cstr, + client::options::{ServerAddress, ServerApi}, + cmap::{Command, Connection}, + error::{Error, Result}, + BoxFuture, +}; + +use super::{ + sasl::{SaslContinue, SaslResponse, SaslStart}, + AuthMechanism, + Credential, + MONGODB_OIDC_STR, +}; + +pub(crate) const TOKEN_RESOURCE_PROP_STR: &str = "TOKEN_RESOURCE"; +pub(crate) const ENVIRONMENT_PROP_STR: &str = "ENVIRONMENT"; +pub(crate) const ALLOWED_HOSTS_PROP_STR: &str = "ALLOWED_HOSTS"; +const VALID_PROPERTIES: &[&str] = &[ + TOKEN_RESOURCE_PROP_STR, + ENVIRONMENT_PROP_STR, + ALLOWED_HOSTS_PROP_STR, +]; + +pub(crate) const AZURE_ENVIRONMENT_VALUE_STR: &str = "azure"; +pub(crate) const GCP_ENVIRONMENT_VALUE_STR: &str = "gcp"; +const K8S_ENVIRONMENT_VALUE_STR: &str = "k8s"; +#[cfg(test)] +const TEST_ENVIRONMENT_VALUE_STR: &str = "test"; +const VALID_ENVIRONMENTS: &[&str] = &[ + AZURE_ENVIRONMENT_VALUE_STR, + GCP_ENVIRONMENT_VALUE_STR, + K8S_ENVIRONMENT_VALUE_STR, + #[cfg(test)] + TEST_ENVIRONMENT_VALUE_STR, +]; + +const HUMAN_CALLBACK_TIMEOUT: Duration = Duration::from_secs(5 * 60); +const MACHINE_CALLBACK_TIMEOUT: Duration = Duration::from_secs(60); +const MACHINE_INVALIDATE_SLEEP_TIMEOUT: Duration = Duration::from_millis(100); +const API_VERSION: u32 = 1; +const DEFAULT_ALLOWED_HOSTS: &[&str] = &[ + "*.mongodb.net", + "*.mongodb-qa.net", + "*.mongodb-dev.net", + "*.mongodbgov.net", + "localhost", + "127.0.0.1", + "::1", +]; + +/// The callback to use for OIDC authentication. +#[derive(Clone)] +#[non_exhaustive] +pub struct Callback { + inner: Arc>>, + is_user_provided: bool, +} + +impl Default for Callback { + fn default() -> Self { + Self::new() + } +} + +impl Callback { + pub(crate) fn is_user_provided(&self) -> bool { + self.is_user_provided + } + + #[cfg(test)] + pub(crate) async fn set_access_token(&self, access_token: Option) { + self.inner.lock().await.as_mut().unwrap().cache.access_token = access_token; + } + + #[cfg(test)] + pub(crate) async fn set_refresh_token(&self, refresh_token: Option) { + self.inner + .lock() + .await + .as_mut() + .unwrap() + .cache + .refresh_token = refresh_token; + } + + pub(crate) fn new() -> Self { + Self { + inner: Arc::new(Mutex::new(None)), + is_user_provided: false, + } + } + + fn new_function(func: F, kind: CallbackKind) -> Function + where + F: Fn(CallbackContext) -> BoxFuture<'static, Result> + + Send + + Sync + + 'static, + { + Function { + inner: Box::new(FunctionInner { f: Box::new(func) }), + kind, + } + } + + /// Create a new human token request function for OIDC. + /// The return type is purposefully opaque to users and should only be created using this + /// function or Callback::machine. + pub fn human(function: F) -> Callback + where + F: Fn(CallbackContext) -> BoxFuture<'static, Result> + + Send + + Sync + + 'static, + { + Self::create_callback(function, CallbackKind::Human) + } + + /// Create a new machine token request function for OIDC. + /// The return type is purposefully opaque to users and should only be created using this + /// function or Callback::human. + pub fn machine(function: F) -> Callback + where + F: Fn(CallbackContext) -> BoxFuture<'static, Result> + + Send + + Sync + + 'static, + { + Self::create_callback(function, CallbackKind::Machine) + } + + fn create_callback(function: F, kind: CallbackKind) -> Callback + where + F: Fn(CallbackContext) -> BoxFuture<'static, Result> + + Send + + Sync + + 'static, + { + Callback { + inner: Arc::new(Mutex::new(Some(CallbackInner { + function: Self::new_function(function, kind), + cache: Cache::new(), + }))), + is_user_provided: true, + } + } + + /// Create azure callback. + #[cfg(feature = "azure-oidc")] + fn azure_callback(client_id: Option<&str>, resource: &str) -> Function { + use futures_util::FutureExt; + let resource = resource.to_string(); + let client_id = client_id.map(|s| s.to_string()); + let mut url = format!( + "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={}", + resource + ); + if let Some(ref client_id) = client_id { + url.push_str(&format!("&client_id={}", client_id)); + } + Self::new_function( + move |_| { + let url = url.clone(); + async move { + let url = url.clone(); + let response = crate::runtime::HttpClient::default() + .get(&url) + .headers(&[("Metadata", "true"), ("Accept", "application/json")]) + .send::() + .await + .map_err(|e| { + Error::authentication_error( + MONGODB_OIDC_STR, + &format!("Failed to get access token from Azure IDMS: {}", e), + ) + }); + let response = response?; + let access_token = response + .get_str("access_token") + .map_err(|e| { + Error::authentication_error( + MONGODB_OIDC_STR, + &format!("Failed to get access token from Azure IDMS: {}", e), + ) + })? + .to_string(); + let expires_in = response + .get_str("expires_in") + .map_err(|e| { + Error::authentication_error( + MONGODB_OIDC_STR, + &format!("Failed to get expires_in from Azure IDMS: {}", e), + ) + })? + .parse::() + .map_err(|e| { + Error::authentication_error( + MONGODB_OIDC_STR, + &format!( + "Failed to parse expires_in from Azure IDMS as u64: {}", + e + ), + ) + })?; + let expires = Some(Instant::now() + Duration::from_secs(expires_in)); + Ok(IdpServerResponse { + access_token, + expires, + refresh_token: None, + }) + } + .boxed() + }, + CallbackKind::Machine, + ) + } + + /// Create gcp callback. + #[cfg(feature = "gcp-oidc")] + fn gcp_callback(resource: &str) -> Function { + use futures_util::FutureExt; + let url = format!( + "http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience={}", + resource + ); + Self::new_function( + move |_| { + let url = url.clone(); + async move { + let url = url.clone(); + let response = crate::runtime::HttpClient::default() + .get(&url) + .headers(&[("Metadata-Flavor", "Google")]) + .send_and_get_string() + .await + .map_err(|e| { + Error::authentication_error( + MONGODB_OIDC_STR, + &format!("Failed to get access token from GCP IDMS: {}", e), + ) + }); + let access_token = response?; + Ok(IdpServerResponse { + access_token, + expires: None, + refresh_token: None, + }) + } + .boxed() + }, + CallbackKind::Machine, + ) + } + + fn k8s_callback() -> Function { + Self::new_function( + move |_| { + use futures_util::FutureExt; + async move { + let path = std::env::var("AZURE_FEDERATED_TOKEN_FILE") + .or_else(|_| std::env::var("AWS_WEB_IDENTITY_TOKEN_FILE")) + .unwrap_or_else(|_| { + "/var/run/secrets/kubernetes.io/serviceaccount/token".to_string() + }); + let access_token = tokio::fs::read_to_string(path).await?; + Ok(IdpServerResponse { + access_token, + expires: None, + refresh_token: None, + }) + } + .boxed() + }, + CallbackKind::Machine, + ) + } +} + +/// The OIDC state containing the cache of necessary OIDC info as well as the function +#[derive(Debug)] +struct CallbackInner { + function: Function, + cache: Cache, +} + +/// Callback provides an interface for creating human and machine functions that return +/// access tokens for use in human and machine OIDC flows. +#[non_exhaustive] +struct Function { + inner: Box, + kind: CallbackKind, +} + +#[non_exhaustive] +#[derive(Clone, Copy, Debug)] +enum CallbackKind { + Human, + Machine, +} + +use std::fmt::Debug; + +impl std::fmt::Debug for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(format!("Callback: {:?}", self.kind).as_str()) + .finish() + } +} + +struct FunctionInner { + f: Box BoxFuture<'static, Result> + Send + Sync>, +} + +#[derive(Debug, Clone)] +pub(crate) struct Cache { + idp_server_info: Option, + refresh_token: Option, + access_token: Option, + token_gen_id: u32, + last_call_time: Instant, +} + +impl Cache { + fn new() -> Self { + Self { + idp_server_info: None, + refresh_token: None, + access_token: None, + token_gen_id: 0, + last_call_time: Instant::now(), + } + } + + async fn update( + &mut self, + response: &IdpServerResponse, + idp_server_info: Option, + ) { + if idp_server_info.is_some() { + self.idp_server_info = idp_server_info; + } + self.access_token = Some(response.access_token.clone()); + self.refresh_token.clone_from(&response.refresh_token); + self.last_call_time = Instant::now(); + self.token_gen_id += 1; + } + + async fn propagate_token_gen_id(&mut self, conn: &Connection) { + let mut token_gen_id = conn.oidc_token_gen_id.lock().await; + if *token_gen_id < self.token_gen_id { + *token_gen_id = self.token_gen_id; + } + } + + async fn invalidate(&mut self, conn: &Connection, force: bool) { + let mut token_gen_id = conn.oidc_token_gen_id.lock().await; + // It should be impossible for token_gen_id to be > cache.token_gen_id, but we check just in + // case + if force || *token_gen_id >= self.token_gen_id { + self.access_token = None; + *token_gen_id = 0; + } + } +} + +/// IdpServerInfo contains the information necessary to locate and authorize with an OIDC server. +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +#[derive(TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct IdpServerInfo { + /// issuer is the address of the IdP server. + pub issuer: String, + /// client_id is the client id for the application, which must be passed to the IdP in order to + /// perform authorization. + pub client_id: Option, + /// request_scopes are the scopes requested by the application, see [`Oauth Scope`](https://oauth.net/2/scope/) + pub request_scopes: Option>, +} + +/// CallbackContext contains the information necessary to perform the human or machine flow +/// in a function. The driver passes ownership of this struct to the Callback function. +/// ``` +/// use mongodb::{error::Error, Client, options::{ClientOptions, oidc::{Callback, CallbackContext, IdpServerResponse}}}; +/// use std::time::{Duration, Instant}; +/// use futures::future::FutureExt; +/// async fn do_human_flow(c: CallbackContext) -> Result<(String, Option, Option), Error> { +/// // Do the human flow here see: https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow +/// Ok(("some_access_token".to_string(), Some(Instant::now() + Duration::from_secs(60 * 60 * 12)), Some("some_refresh_token".to_string()))) +/// } +/// +/// async fn setup_client() -> Result { +/// let mut opts = +/// ClientOptions::parse("mongodb://localhost:27017,localhost:27018/admin?authSource=admin&authMechanism=MONGODB-OIDC").await?; +/// opts.credential.as_mut().unwrap().oidc_callback = +/// Callback::human(move |c: CallbackContext| { +/// async move { +/// let (access_token, expires, refresh_token) = do_human_flow(c).await?; +/// Ok(IdpServerResponse::builder().access_token(access_token).expires(expires).refresh_token(refresh_token).build()) +/// }.boxed() +/// }); +/// Client::with_options(opts) +/// } +/// ``` +#[derive(Clone, Debug, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct CallbackContext { + /// The time in the future when the function should return an error if it + /// it has not completed. + pub timeout: Option, + /// The version of the function API that the driver is using. + pub version: u32, + /// The refresh token that the driver has stored in the cache, which may not + /// exist. + pub refresh_token: Option, + /// The information necessary to locate and authorize with an OIDC server. + pub idp_info: Option, +} + +/// The return type of the OIDC authentication function. It contains the access token +/// with optional expiration time and refresh token. +/// ``` +/// use mongodb::{error::Error, Client, options::{ClientOptions, oidc::{Callback, CallbackContext, IdpServerResponse}}}; +/// use std::time::{Duration, Instant}; +/// use futures::future::FutureExt; +/// async fn do_human_flow(c: CallbackContext) -> Result<(String, Option, Option), Error> { +/// // Do the human flow here see: https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow +/// Ok(("some_access_token".to_string(), Some(Instant::now() + Duration::from_secs(60 * 60 * 12)), Some("some_refresh_token".to_string()))) +/// } +/// +/// async fn setup_client() -> Result { +/// let mut opts = +/// ClientOptions::parse("mongodb://localhost:27017,localhost:27018/admin?authSource=admin&authMechanism=MONGODB-OIDC").await?; +/// opts.credential.as_mut().unwrap().oidc_callback = +/// Callback::human(move |c: CallbackContext| { +/// async move { +/// let (access_token, expires, refresh_token) = do_human_flow(c).await?; +/// Ok(IdpServerResponse::builder().access_token(access_token).expires(expires).refresh_token(refresh_token).build()) +/// }.boxed() +/// }); +/// Client::with_options(opts) +/// } +/// ``` +#[derive(Clone, Debug, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct IdpServerResponse { + #[builder(!default)] + /// The token that the driver will use to authenticate with the server. + pub access_token: String, + /// The time when the access token expires. + pub expires: Option, + /// The token that the driver will use to refresh the access token when the + /// access_token expires. + pub refresh_token: Option, +} + +fn make_spec_auth_command( + source: String, + payload: Vec, + server_api: Option<&ServerApi>, +) -> Command { + let body = rawdoc! { + "saslStart": 1, + "mechanism": MONGODB_OIDC_STR, + "payload": Binary { subtype: BinarySubtype::Generic, bytes: payload }, + "db": "$external", + }; + + let mut command = Command::new("saslStart", source, body); + if let Some(server_api) = server_api { + command.set_server_api(server_api); + } + command +} + +pub(crate) async fn build_speculative_client_first(credential: &Credential) -> Option { + self::build_client_first(credential, None).await +} + +/// Constructs the first client message in the OIDC handshake for speculative authentication +pub(crate) async fn build_client_first( + credential: &Credential, + server_api: Option<&ServerApi>, +) -> Option { + if let Some(ref access_token) = credential + .oidc_callback + .inner + .lock() + .await + .as_ref()? + .cache + .access_token + { + let start_doc = rawdoc! { + "jwt": access_token.clone() + }; + let source = credential + .source + .clone() + .unwrap_or_else(|| "$external".to_string()); + return Some(make_spec_auth_command( + source, + start_doc.as_bytes().to_vec(), + server_api, + )); + } + None +} + +pub(crate) async fn reauthenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, +) -> Result<()> { + credential + .oidc_callback + .inner + .lock() + .await + .as_mut() + .unwrap() + .cache + .invalidate(conn, true) + .await; + authenticate_stream(conn, credential, server_api, None).await +} + +async fn setup_automatic_providers(credential: &Credential, callback: &mut Option) { + // If there is already a function, there is no need to set up an automatic provider + // this could happen in the case of a reauthentication, or if the user has already set up + // a function. A situation where the user has set up a function and an automatic provider + // would already have caused an InvalidArgument error in `validate_credential`. + if callback.is_some() { + return; + } + if let Some(ref p) = credential.mechanism_properties { + let environment = p.get_str(ENVIRONMENT_PROP_STR).unwrap_or(""); + #[cfg(any(feature = "azure-oidc", feature = "gcp-oidc"))] + let resource = p.get_str(TOKEN_RESOURCE_PROP_STR).unwrap_or(""); + let function = match environment { + #[cfg(feature = "azure-oidc")] + AZURE_ENVIRONMENT_VALUE_STR => { + let client_id = credential.username.as_deref(); + Some(Callback::azure_callback(client_id, resource)) + } + #[cfg(feature = "gcp-oidc")] + GCP_ENVIRONMENT_VALUE_STR => Some(Callback::gcp_callback(resource)), + K8S_ENVIRONMENT_VALUE_STR => Some(Callback::k8s_callback()), + _ => None, + }; + if let Some(function) = function { + *callback = Some(CallbackInner { + function, + cache: Cache::new(), + }) + } + } +} + +pub(crate) async fn authenticate_stream( + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + server_first: impl Into>, +) -> Result<()> { + // We need to hold the lock for the entire function so that multiple functions + // are not called during an authentication race, and so that token_gen_id on the Connection + // always matches that in the Credential Cache. + let mut guard = credential.oidc_callback.inner.lock().await; + + setup_automatic_providers(credential, &mut guard).await; + let CallbackInner { + cache, + function: Function { inner, kind }, + } = &mut guard + .as_mut() + .ok_or_else(|| auth_error("no functions supplied"))?; + + cache.propagate_token_gen_id(conn).await; + + if server_first.into().is_some() { + // speculative authentication succeeded, no need to authenticate again + // update the Connection gen_id to be that of the cred_cache + cache.propagate_token_gen_id(conn).await; + return Ok(()); + } + let source = credential.source.as_deref().unwrap_or("$external"); + + match kind { + CallbackKind::Machine => { + authenticate_machine(source, conn, credential, cache, server_api, inner.as_ref()).await + } + CallbackKind::Human => { + authenticate_human(source, conn, credential, cache, server_api, inner.as_ref()).await + } + } +} + +// send_sasl_start_command creates and sends a sasl_start command handling either +// one step or two step sasl based on whether or not the access token is Some. +async fn send_sasl_start_command( + source: &str, + conn: &mut Connection, + credential: &Credential, + server_api: Option<&ServerApi>, + access_token: Option, +) -> Result { + let mut start_doc = rawdoc! {}; + if let Some(access_token) = access_token { + start_doc.append(cstr!("jwt"), access_token); + } else if let Some(username) = credential.username.as_deref() { + start_doc.append(cstr!("n"), username); + } + let sasl_start = SaslStart::new( + source.to_string(), + AuthMechanism::MongoDbOidc, + start_doc.into_bytes(), + server_api.cloned(), + ) + .into_command()?; + send_sasl_command(conn, sasl_start).await +} + +// this is shared functionality between the human and machine flow. In the machine flow, the idp +// info will always be None, but the code is the same so we reuse it. +async fn do_single_step_function( + source: &str, + conn: &mut Connection, + cred_cache: &mut Cache, + credential: &Credential, + server_api: Option<&ServerApi>, + function: &FunctionInner, + timeout: Duration, +) -> Result<()> { + let idp_response = { + let cb_context = CallbackContext { + timeout: Some(Instant::now() + timeout), + version: API_VERSION, + refresh_token: None, + idp_info: cred_cache.idp_server_info.clone(), + }; + (function.f)(cb_context).await? + }; + let response = send_sasl_start_command( + source, + conn, + credential, + server_api, + Some(idp_response.access_token.clone()), + ) + .await?; + if response.done { + let server_info = cred_cache.idp_server_info.clone(); + cred_cache.update(&idp_response, server_info).await; + return Ok(()); + } + Err(invalid_auth_response()) +} + +// This is currently only used in the human flow, but is abstracted to make the algorithm more +// clear. The timeout is still passed in, so that the human flow can control the timeout in one +// place. +async fn do_two_step_function( + source: &str, + conn: &mut Connection, + cred_cache: &mut Cache, + credential: &Credential, + server_api: Option<&ServerApi>, + function: &FunctionInner, + timeout: Duration, +) -> Result<()> { + // Here we do not have the idpinfo, so we need to do the two step sasl conversation. + let response = send_sasl_start_command(source, conn, credential, server_api, None).await?; + if response.done { + return Err(invalid_auth_response()); + } + + let server_info: IdpServerInfo = crate::bson_compat::deserialize_from_slice(&response.payload) + .map_err(|_| invalid_auth_response())?; + let idp_response = { + let cb_context = CallbackContext { + timeout: Some(Instant::now() + timeout), + version: API_VERSION, + refresh_token: None, + idp_info: Some(server_info.clone()), + }; + (function.f)(cb_context).await? + }; + + // Update the credential and connection caches with the access token and the credential cache + // with the refresh token and token_gen_id + cred_cache.update(&idp_response, Some(server_info)).await; + + let sasl_continue = SaslContinue::new( + source.to_string(), + response.conversation_id, + rawdoc! { "jwt": idp_response.access_token }.into_bytes(), + server_api.cloned(), + ) + .into_command(); + let response = send_sasl_command(conn, sasl_continue).await?; + if !response.done { + return Err(invalid_auth_response()); + } + + Ok(()) +} + +fn get_allowed_hosts(mechanism_properties: Option<&Document>) -> Result> { + if mechanism_properties.is_none() { + return Ok(Vec::from(DEFAULT_ALLOWED_HOSTS)); + } + if let Some(allowed_hosts) = + mechanism_properties.and_then(|p| p.get_array(ALLOWED_HOSTS_PROP_STR).ok()) + { + return allowed_hosts + .iter() + .map(|host| { + host.as_str().ok_or_else(|| { + auth_error(format!( + "`{}` must contain only strings", + ALLOWED_HOSTS_PROP_STR + )) + }) + }) + .collect::>>(); + } + Ok(Vec::from(DEFAULT_ALLOWED_HOSTS)) +} + +fn validate_address_with_allowed_hosts( + mechanism_properties: Option<&Document>, + address: &ServerAddress, +) -> Result<()> { + #[allow(irrefutable_let_patterns)] + let hostname = if let ServerAddress::Tcp { host, .. } = address { + host.as_str() + } else { + return Err(auth_error("OIDC human flow only supports TCP addresses")); + }; + for pattern in get_allowed_hosts(mechanism_properties)? { + if pattern == hostname { + return Ok(()); + } + if pattern.starts_with("*.") && hostname.ends_with(&pattern[1..]) { + return Ok(()); + } + } + Err(auth_error( + "The Connection address is not in the allowed list of hosts", + )) +} + +async fn authenticate_human( + source: &str, + conn: &mut Connection, + credential: &Credential, + cred_cache: &mut Cache, + server_api: Option<&ServerApi>, + function: &FunctionInner, +) -> Result<()> { + validate_address_with_allowed_hosts(credential.mechanism_properties.as_ref(), &conn.address)?; + + // We need to hold the lock for the entire function so that multiple functions + // are not called during an authentication race. + + // If the access token is in the cache, we can use it to send the sasl start command and avoid + // the function and sasl_continue + if let Some(ref access_token) = cred_cache.access_token { + let response = send_sasl_start_command( + source, + conn, + credential, + server_api, + Some(access_token.clone()), + ) + .await; + if let Ok(response) = response { + if response.done { + return Ok(()); + } + } + cred_cache.invalidate(conn, false).await; + } + + // If the cache has a refresh token, we can avoid asking for the server info. + if let (refresh_token @ Some(_), idp_info) = ( + cred_cache.refresh_token.clone(), + cred_cache.idp_server_info.clone(), + ) { + let idp_response = { + let cb_context = CallbackContext { + timeout: Some(Instant::now() + HUMAN_CALLBACK_TIMEOUT), + version: API_VERSION, + refresh_token, + idp_info, + }; + (function.f)(cb_context).await? + }; + + let access_token = idp_response.access_token.clone(); + let response = + send_sasl_start_command(source, conn, credential, server_api, Some(access_token)).await; + if let Ok(response) = response { + if response.done { + // Update the credential and connection caches with the access token and the + // credential cache with the refresh token and token_gen_id + cred_cache.update(&idp_response, None).await; + return Ok(()); + } + // It should really not be possible for this to occur, we would get an error, if the + // response is not done. Just in case, we will fall through to two_step to try one + // more time. + } else { + // since this is an error, we will go ahead and invalidate the caches so we do not + // try to use them again and waste time. We should fall through so that we can + // do the shared flow from the beginning + cred_cache.invalidate(conn, false).await; + } + } + + // If the idpinfo is cached, we run the function and then do a single step sasl conversation. + // It seems the spec does not allow idpinfo to change on invalidations. + if cred_cache.idp_server_info.is_some() { + return do_single_step_function( + source, + conn, + cred_cache, + credential, + server_api, + function, + HUMAN_CALLBACK_TIMEOUT, + ) + .await; + } + + do_two_step_function( + source, + conn, + cred_cache, + credential, + server_api, + function, + HUMAN_CALLBACK_TIMEOUT, + ) + .await +} + +async fn authenticate_machine( + source: &str, + conn: &mut Connection, + credential: &Credential, + cred_cache: &mut Cache, + server_api: Option<&ServerApi>, + function: &FunctionInner, +) -> Result<()> { + // If the access token is in the cache, we can use it to send the sasl start command and avoid + // the function and sasl_continue + if let Some(ref access_token) = cred_cache.access_token { + let response = send_sasl_start_command( + source, + conn, + credential, + server_api, + Some(access_token.clone()), + ) + .await; + if let Ok(response) = response { + if response.done { + return Ok(()); + } + } + cred_cache.invalidate(conn, false).await; + tokio::time::sleep(MACHINE_INVALIDATE_SLEEP_TIMEOUT).await; + } + + do_single_step_function( + source, + conn, + cred_cache, + credential, + server_api, + function, + MACHINE_CALLBACK_TIMEOUT, + ) + .await +} + +fn auth_error(s: impl AsRef) -> Error { + Error::authentication_error(MONGODB_OIDC_STR, s.as_ref()) +} + +fn invalid_auth_response() -> Error { + Error::invalid_authentication_response(MONGODB_OIDC_STR) +} + +async fn send_sasl_command( + conn: &mut Connection, + command: crate::cmap::Command, +) -> Result { + let response = conn.send_message(command).await?; + SaslResponse::parse( + MONGODB_OIDC_STR, + response.auth_response_body(MONGODB_OIDC_STR)?, + ) +} + +pub(super) fn validate_credential(credential: &Credential) -> Result<()> { + let default_document = &Document::new(); + let properties = credential + .mechanism_properties + .as_ref() + .unwrap_or(default_document); + for k in properties.keys() { + if VALID_PROPERTIES.iter().all(|p| *p != k) { + return Err(Error::invalid_argument(format!( + "'{}' is not a valid property for {} authentication", + k, MONGODB_OIDC_STR, + ))); + } + } + let environment = properties.get_str(ENVIRONMENT_PROP_STR); + if environment.is_ok() && credential.oidc_callback.is_user_provided() { + return Err(Error::invalid_argument(format!( + "OIDC callback cannot be set for {} authentication, if an `{}` is set", + MONGODB_OIDC_STR, ENVIRONMENT_PROP_STR + ))); + } + let has_token_resource = properties.contains_key(TOKEN_RESOURCE_PROP_STR); + match environment { + Ok(AZURE_ENVIRONMENT_VALUE_STR) | Ok(GCP_ENVIRONMENT_VALUE_STR) => { + if !has_token_resource { + return Err(Error::invalid_argument(format!( + "`{}` must be set for {} authentication in the `{}` or `{}` `{}`", + TOKEN_RESOURCE_PROP_STR, + MONGODB_OIDC_STR, + AZURE_ENVIRONMENT_VALUE_STR, + GCP_ENVIRONMENT_VALUE_STR, + ENVIRONMENT_PROP_STR, + ))); + } + } + _ => { + if has_token_resource { + return Err(Error::invalid_argument(format!( + "`{}` must not be set for {} authentication unless using the `{}` or `{}` `{}`", + TOKEN_RESOURCE_PROP_STR, + MONGODB_OIDC_STR, + AZURE_ENVIRONMENT_VALUE_STR, + GCP_ENVIRONMENT_VALUE_STR, + ENVIRONMENT_PROP_STR, + ))); + } + } + } + if credential + .source + .as_ref() + .is_some_and(|source| source != "$external") + { + return Err(Error::invalid_argument(format!( + "only $external may be specified as an auth source for {MONGODB_OIDC_STR}", + ))); + } + #[cfg(test)] + if environment + .as_ref() + .is_ok_and(|ev| *ev == TEST_ENVIRONMENT_VALUE_STR) + && credential.username.is_some() + { + return Err(Error::invalid_argument(format!( + "username must not be set for {} authentication in the {} {}", + MONGODB_OIDC_STR, TEST_ENVIRONMENT_VALUE_STR, ENVIRONMENT_PROP_STR, + ))); + } + if credential.password.is_some() { + return Err(Error::invalid_argument(format!( + "password must not be set for {} authentication", + MONGODB_OIDC_STR + ))); + } + if let Ok(env) = environment { + if VALID_ENVIRONMENTS.iter().all(|e| *e != env) { + return Err(Error::invalid_argument(format!( + "unsupported environment for {} authentication: {}", + MONGODB_OIDC_STR, env, + ))); + } + } + if let Some(allowed_hosts) = properties.get(ALLOWED_HOSTS_PROP_STR) { + allowed_hosts.as_array().ok_or_else(|| { + Error::invalid_argument(format!("`{}` must be an array", ALLOWED_HOSTS_PROP_STR)) + })?; + } + Ok(()) +} diff --git a/src/client/auth/plain.rs b/src/client/auth/plain.rs index 081659bbd..0772cd914 100644 --- a/src/client/auth/plain.rs +++ b/src/client/auth/plain.rs @@ -33,9 +33,9 @@ pub(crate) async fn authenticate_stream( payload_bytes(username, password), server_api.cloned(), ) - .into_command(); + .into_command()?; - let response = conn.send_command(sasl_start, None).await?; + let response = conn.send_message(sasl_start).await?; let sasl_response = SaslResponse::parse("PLAIN", response.auth_response_body("PLAIN")?)?; if !sasl_response.done { diff --git a/src/client/auth/sasl.rs b/src/client/auth/sasl.rs index c6f99ba55..49112742f 100644 --- a/src/client/auth/sasl.rs +++ b/src/client/auth/sasl.rs @@ -1,5 +1,8 @@ +use crate::bson::{rawdoc, RawBson}; + use crate::{ - bson::{doc, spec::BinarySubtype, Binary, Bson, Document}, + bson::{spec::BinarySubtype, Binary, Bson, Document}, + bson_compat::cstr, bson_util, client::{auth::AuthMechanism, options::ServerApi}, cmap::Command, @@ -30,8 +33,8 @@ impl SaslStart { } } - pub(super) fn into_command(self) -> Command { - let mut body = doc! { + pub(super) fn into_command(self) -> Result { + let mut body = rawdoc! { "saslStart": 1, "mechanism": self.mechanism.as_str(), "payload": Binary { subtype: BinarySubtype::Generic, bytes: self.payload }, @@ -39,15 +42,15 @@ impl SaslStart { if self.mechanism == AuthMechanism::ScramSha1 || self.mechanism == AuthMechanism::ScramSha256 { - body.insert("options", doc! { "skipEmptyExchange": true }); + body.append(cstr!("options"), rawdoc! { "skipEmptyExchange": true }); } - let mut command = Command::new("saslStart".into(), self.source, body); + let mut command = Command::new("saslStart", self.source, body); if let Some(server_api) = self.server_api { command.set_server_api(&server_api); } - command + Ok(command) } } @@ -75,13 +78,15 @@ impl SaslContinue { } pub(super) fn into_command(self) -> Command { - let body = doc! { + // Unwrap safety: the Bson -> RawBson conversion is actually infallible + let raw_id: RawBson = self.conversation_id.try_into().unwrap(); + let body = rawdoc! { "saslContinue": 1, - "conversationId": self.conversation_id, + "conversationId": raw_id, "payload": Binary { subtype: BinarySubtype::Generic, bytes: self.payload }, }; - let mut command = Command::new("saslContinue".into(), self.source, body); + let mut command = Command::new("saslContinue", self.source, body); if let Some(server_api) = self.server_api { command.set_server_api(&server_api); } @@ -100,9 +105,9 @@ fn validate_command_success(auth_mechanism: &str, response: &Document) -> Result match bson_util::get_int(ok) { Some(1) => Ok(()), Some(_) => { - let source = bson::from_bson::>(Bson::Document( - response.clone(), - )) + let source = crate::bson_compat::deserialize_from_bson::< + CommandResponse, + >(Bson::Document(response.clone())) .map(|cmd_resp| cmd_resp.body.into()) .ok(); Err(Error::authentication_error( @@ -118,6 +123,7 @@ fn validate_command_success(auth_mechanism: &str, response: &Document) -> Result } /// Encapsulates the parsing of the response to a `saslStart` or `saslContinue` command. +#[derive(Debug)] pub(super) struct SaslResponse { pub(super) conversation_id: Bson, pub(super) done: bool, diff --git a/src/client/auth/scram.rs b/src/client/auth/scram.rs index fa5ef97a6..38af1f365 100644 --- a/src/client/auth/scram.rs +++ b/src/client/auth/scram.rs @@ -11,14 +11,15 @@ use hmac::{ Hmac, Mac, }; -use lazy_static::lazy_static; use md5::Md5; +use once_cell::sync::Lazy; use sha1::Sha1; use sha2::Sha256; use tokio::sync::RwLock; use crate::{ - bson::{doc, Bson, Document}, + bson::{Bson, Document}, + bson_compat::cstr, client::{ auth::{ self, @@ -48,12 +49,9 @@ const NO_CHANNEL_BINDING: char = 'n'; /// The minimum number of iterations of the hash function that we will accept from the server. const MIN_ITERATION_COUNT: u32 = 4096; -lazy_static! { - /// Cache of pre-computed salted passwords. - static ref CREDENTIAL_CACHE: RwLock>> = { - RwLock::new(HashMap::new()) - }; -} +/// Cache of pre-computed salted passwords. +static CREDENTIAL_CACHE: Lazy>>> = + Lazy::new(|| RwLock::new(HashMap::new())); #[derive(Hash, Eq, PartialEq)] struct CacheEntry { @@ -152,9 +150,9 @@ impl ScramVersion { ) -> Result { let client_first = self.build_client_first(credential, false, server_api)?; - let command = client_first.to_command(self); + let command = client_first.to_command(self)?; - let server_first = conn.send_command(command, None).await?; + let server_first = conn.send_message(command).await?; Ok(FirstRound { client_first, @@ -218,7 +216,7 @@ impl ScramVersion { let command = client_final.to_command(); - let server_final_response = conn.send_command(command, None).await?; + let server_final_response = conn.send_message(command).await?; let server_final = ServerFinal::parse(server_final_response.auth_response_body("SCRAM")?)?; server_final.validate(salted_password.as_slice(), &client_final, self)?; @@ -234,7 +232,7 @@ impl ScramVersion { ); let command = noop.into_command(); - let server_noop_response = conn.send_command(command, None).await?; + let server_noop_response = conn.send_message(command).await?; let server_noop_response_document: Document = server_noop_response.auth_response_body("SCRAM")?; @@ -319,7 +317,8 @@ impl ScramVersion { ) -> Result> { let normalized_password = match self { ScramVersion::Sha1 => { - let mut md5 = Md5::new(); + // nosemgrep: insecure-hashes + let mut md5 = Md5::new(); // mongodb rating: No Fix Needed md5.update(format!("{}:mongo:{}", username, password)); Cow::Owned(hex::encode(md5.finalize())) } @@ -449,7 +448,7 @@ impl ClientFirst { &self.message[..] } - pub(super) fn to_command(&self, scram: &ScramVersion) -> Command { + pub(super) fn to_command(&self, scram: &ScramVersion) -> Result { let payload = self.message().as_bytes().to_vec(); let auth_mech = AuthMechanism::from_scram_version(scram); let sasl_start = SaslStart::new( @@ -459,13 +458,13 @@ impl ClientFirst { self.server_api.clone(), ); - let mut cmd = sasl_start.into_command(); + let mut cmd = sasl_start.into_command()?; if self.include_db { - cmd.body.insert("db", self.source.clone()); + cmd.body.append(cstr!("db"), self.source.clone()); } - cmd + Ok(cmd) } } diff --git a/src/client/auth/test.rs b/src/client/auth/test.rs index e7695e986..d707fa0f3 100644 --- a/src/client/auth/test.rs +++ b/src/client/auth/test.rs @@ -1,15 +1,15 @@ -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use crate::{cmap::StreamDescription, options::AuthMechanism}; use super::sasl::SaslStart; -lazy_static! { - static ref MECHS: [String; 2] = [ +static MECHS: Lazy<[String; 2]> = Lazy::new(|| { + [ AuthMechanism::ScramSha1.as_str().to_string(), - AuthMechanism::ScramSha256.as_str().to_string() - ]; -} + AuthMechanism::ScramSha256.as_str().to_string(), + ] +}); #[test] fn negotiate_both_scram() { @@ -70,7 +70,7 @@ fn negotiate_mangled() { fn scram_sasl_first_options(mechanism: AuthMechanism) { let sasl_first = SaslStart::new(String::new(), mechanism, Vec::new(), None); - let command = sasl_first.into_command(); + let command = sasl_first.into_command().unwrap(); let options = match command.body.get_document("options") { Ok(options) => options, Err(_) => panic!("SaslStart should contain options document"), @@ -93,7 +93,7 @@ fn sasl_first_options_specified() { #[test] fn sasl_first_options_not_specified() { let sasl_first = SaslStart::new(String::new(), AuthMechanism::MongoDbX509, Vec::new(), None); - let command = sasl_first.into_command(); + let command = sasl_first.into_command().unwrap(); assert!( command.body.get_document("options").is_err(), "SaslStart should not contain options document for X.509 authentication" diff --git a/src/client/auth/x509.rs b/src/client/auth/x509.rs index e7cbdfb5f..238e6f081 100644 --- a/src/client/auth/x509.rs +++ b/src/client/auth/x509.rs @@ -1,5 +1,8 @@ +use crate::bson::rawdoc; + use crate::{ - bson::{doc, Document}, + bson::Document, + bson_compat::cstr, client::options::ServerApi, cmap::{Command, Connection, RawCommandResponse}, error::{Error, Result}, @@ -7,7 +10,7 @@ use crate::{ }; /// Constructs the first client message in the X.509 handshake for speculative authentication -pub(crate) fn build_speculative_client_first(credential: &Credential) -> Command { +pub(crate) fn build_speculative_client_first(credential: &Credential) -> Result { self::build_client_first(credential, None) } @@ -15,22 +18,22 @@ pub(crate) fn build_speculative_client_first(credential: &Credential) -> Command pub(crate) fn build_client_first( credential: &Credential, server_api: Option<&ServerApi>, -) -> Command { - let mut auth_command_doc = doc! { +) -> Result { + let mut auth_command_doc = rawdoc! { "authenticate": 1, "mechanism": "MONGODB-X509", }; if let Some(ref username) = credential.username { - auth_command_doc.insert("username", username); + auth_command_doc.append(cstr!("username"), username.as_str()); } - let mut command = Command::new("authenticate".into(), "$external".into(), auth_command_doc); + let mut command = Command::new("authenticate", "$external", auth_command_doc); if let Some(server_api) = server_api { command.set_server_api(server_api); } - command + Ok(command) } /// Sends the first client message in the X.509 handshake. @@ -39,9 +42,9 @@ pub(crate) async fn send_client_first( credential: &Credential, server_api: Option<&ServerApi>, ) -> Result { - let command = build_client_first(credential, server_api); + let command = build_client_first(credential, server_api)?; - conn.send_command(command, None).await + conn.send_message(command).await } /// Performs X.509 authentication for a given stream. diff --git a/src/client/csfle.rs b/src/client/csfle.rs index 037267d04..264a31ec0 100644 --- a/src/client/csfle.rs +++ b/src/client/csfle.rs @@ -5,7 +5,7 @@ pub(crate) mod state_machine; use std::{path::Path, time::Duration}; -use derivative::Derivative; +use derive_where::derive_where; use mongocrypt::Crypt; use crate::{ @@ -28,13 +28,12 @@ use self::state_machine::{CryptExecutor, MongocryptdOptions}; use super::WeakClient; -#[derive(Derivative)] -#[derivative(Debug)] +#[derive_where(Debug)] pub(super) struct ClientState { - #[derivative(Debug = "ignore")] + #[derive_where(skip)] crypt: Crypt, exec: CryptExecutor, - internal_client: Option, + _internal_client: Option, opts: AutoEncryptionOptions, } @@ -60,7 +59,7 @@ impl ClientState { let uri = opts .extra_option(&EO_MONGOCRYPTD_URI)? .unwrap_or(Self::MONGOCRYPTD_DEFAULT_URI); - let mut options = crate::options::ClientOptions::parse_uri(uri, None).await?; + let mut options = crate::options::ClientOptions::parse(uri).await?; options.server_selection_timeout = Some(Self::MONGOCRYPTD_SERVER_SELECTION_TIMEOUT); Some(Client::with_options(options)?) } else { @@ -79,7 +78,7 @@ impl ClientState { Ok(Self { crypt, exec, - internal_client: aux_clients.internal_client, + _internal_client: aux_clients.internal_client, opts, }) } @@ -97,12 +96,17 @@ impl ClientState { } fn make_crypt(opts: &AutoEncryptionOptions) -> Result { - let mut builder = Crypt::builder().kms_providers(&opts.kms_providers.credentials_doc()?)?; + let mut builder = Crypt::builder() + .kms_providers(&opts.kms_providers.credentials_doc()?)? + .use_need_kms_credentials_state() + .retry_kms(true)? + .use_range_v2()?; if let Some(m) = &opts.schema_map { - builder = builder.schema_map(&bson::to_document(m)?)?; + builder = builder.schema_map(&crate::bson_compat::serialize_to_document(m)?)?; } if let Some(m) = &opts.encrypted_fields_map { - builder = builder.encrypted_field_config_map(&bson::to_document(m)?)?; + builder = builder + .encrypted_field_config_map(&crate::bson_compat::serialize_to_document(m)?)?; } #[cfg(not(test))] let disable_crypt_shared = false; @@ -119,6 +123,16 @@ impl ClientState { if opts.bypass_query_analysis == Some(true) { builder = builder.bypass_query_analysis(); } + if let Some(key_cache_expiration) = opts.key_cache_expiration { + let expiration_ms: u64 = key_cache_expiration.as_millis().try_into().map_err(|_| { + Error::invalid_argument(format!( + "key_cache_expiration must not exceed {} milliseconds, got {:?}", + u64::MAX, + key_cache_expiration + )) + })?; + builder = builder.key_cache_expiration(expiration_ms)?; + } let crypt = builder.build()?; if opts.extra_option(&EO_CRYPT_SHARED_REQUIRED)? == Some(true) && crypt.shared_lib_version().is_none() @@ -201,7 +215,7 @@ impl ClientState { pub(crate) fn aux_collections( base_ns: &Namespace, - enc_fields: &bson::Document, + enc_fields: &crate::bson::Document, ) -> Result> { let mut out = vec![]; for &key in &["esc", "ecoc"] { @@ -216,3 +230,15 @@ pub(crate) fn aux_collections( } Ok(out) } + +impl Client { + pub(crate) async fn init_csfle(&self, opts: AutoEncryptionOptions) -> Result<()> { + let mut csfle_state = self.inner.csfle.write().await; + if csfle_state.is_some() { + return Err(Error::internal("double initialization of csfle state")); + } + *csfle_state = Some(ClientState::new(self, opts).await?); + + Ok(()) + } +} diff --git a/src/client/csfle/client_builder.rs b/src/client/csfle/client_builder.rs index a9e99c3f7..a3aa7a3ee 100644 --- a/src/client/csfle/client_builder.rs +++ b/src/client/csfle/client_builder.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crate::{bson::Document, error::Result, options::ClientOptions, Client}; use super::options::AutoEncryptionOptions; @@ -5,10 +7,8 @@ use super::options::AutoEncryptionOptions; /// A builder for constructing a `Client` with auto-encryption enabled. /// /// ```no_run -/// # use bson::doc; /// # use mongocrypt::ctx::KmsProvider; -/// # use mongodb::Client; -/// # use mongodb::error::Result; +/// # use mongodb::{Client, bson::{self, doc}, error::Result}; /// # async fn func() -> Result<()> { /// # let client_options = todo!(); /// # let key_vault_namespace = todo!(); @@ -17,7 +17,7 @@ use super::options::AutoEncryptionOptions; /// let encrypted_client = Client::encrypted_builder( /// client_options, /// key_vault_namespace, -/// [(KmsProvider::Local, doc! { "key": local_key }, None)], +/// [(KmsProvider::local(), doc! { "key": local_key }, None)], /// )? /// .key_vault_client(key_vault_client) /// .build() @@ -101,12 +101,18 @@ impl EncryptedClientBuilder { self } + /// Set the duration of time after which the data encryption key cache should expire. Defaults + /// to 60 seconds if unset. + pub fn key_cache_expiration(mut self, expiration: impl Into>) -> Self { + self.enc_opts.key_cache_expiration = expiration.into(); + self + } + /// Constructs a new `Client` using automatic encryption. May perform DNS lookups and/or spawn /// mongocryptd as part of `Client` initialization. pub async fn build(self) -> Result { let client = Client::with_options(self.client_options)?; - *client.inner.csfle.write().await = - Some(super::ClientState::new(&client, self.enc_opts).await?); + client.init_csfle(self.enc_opts).await?; Ok(client) } } diff --git a/src/client/csfle/client_encryption.rs b/src/client/csfle/client_encryption.rs index b6f25caf6..40d12283a 100644 --- a/src/client/csfle/client_encryption.rs +++ b/src/client/csfle/client_encryption.rs @@ -1,39 +1,32 @@ //! Support for explicit encryption. -use mongocrypt::{ - ctx::{Algorithm, Ctx, CtxBuilder, KmsProvider}, - Crypt, -}; +mod create_data_key; +mod encrypt; + +use std::time::Duration; + +use mongocrypt::{ctx::KmsProvider, Crypt}; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; use typed_builder::TypedBuilder; use crate::{ - bson::{ - doc, - spec::BinarySubtype, - Binary, - Bson, - Document, - RawBinaryRef, - RawBson, - RawDocumentBuf, - }, + bson::{doc, spec::BinarySubtype, Binary, RawBinaryRef, RawDocumentBuf}, client::options::TlsOptions, coll::options::CollectionOptions, - db::options::CreateCollectionOptions, error::{Error, Result}, options::{ReadConcern, WriteConcern}, results::DeleteResult, Client, Collection, Cursor, - Database, Namespace, }; use super::{options::KmsProviders, state_machine::CryptExecutor}; +pub use super::client_builder::EncryptedClientBuilder; +pub use crate::action::csfle::encrypt::{EncryptKey, RangeOptions}; + /// A handle to the key vault. Used to create data encryption keys, and to explicitly encrypt and /// decrypt values when auto-encryption is not an option. pub struct ClientEncryption { @@ -46,10 +39,8 @@ impl ClientEncryption { /// Initialize a new `ClientEncryption`. /// /// ```no_run - /// # use bson::doc; /// # use mongocrypt::ctx::KmsProvider; - /// # use mongodb::client_encryption::ClientEncryption; - /// # use mongodb::error::Result; + /// # use mongodb::{bson::doc, client_encryption::ClientEncryption, error::Result}; /// # fn func() -> Result<()> { /// # let kv_client = todo!(); /// # let kv_namespace = todo!(); @@ -58,8 +49,8 @@ impl ClientEncryption { /// kv_client, /// kv_namespace, /// [ - /// (KmsProvider::Local, doc! { "key": local_key }, None), - /// (KmsProvider::Kmip, doc! { "endpoint": "localhost:5698" }, None), + /// (KmsProvider::local(), doc! { "key": local_key }, None), + /// (KmsProvider::kmip(), doc! { "endpoint": "localhost:5698" }, None), /// ] /// )?; /// # Ok(()) @@ -68,101 +59,48 @@ impl ClientEncryption { pub fn new( key_vault_client: Client, key_vault_namespace: Namespace, - kms_providers: impl IntoIterator)>, + kms_providers: impl IntoIterator< + Item = (KmsProvider, crate::bson::Document, Option), + >, ) -> Result { - let kms_providers = KmsProviders::new(kms_providers)?; - let crypt = Crypt::builder() - .kms_providers(&kms_providers.credentials_doc()?)? - .use_need_kms_credentials_state() - .build()?; - let exec = CryptExecutor::new_explicit( - key_vault_client.weak(), - key_vault_namespace.clone(), - kms_providers, - )?; - let key_vault = key_vault_client - .database(&key_vault_namespace.db) - .collection_with_options( - &key_vault_namespace.coll, - CollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .read_concern(ReadConcern::MAJORITY) - .build(), - ); - Ok(ClientEncryption { - crypt, - exec, - key_vault, - }) + Self::builder(key_vault_client, key_vault_namespace, kms_providers).build() } - /// Creates a new key document and inserts into the key vault collection. - /// `CreateDataKeyAction::run` returns a `Binary` (subtype 0x04) with the _id of the created - /// document as a UUID. + /// Initialize a builder to construct a [`ClientEncryption`]. Methods on + /// [`ClientEncryptionBuilder`] can be chained to set options. /// - /// The returned `CreateDataKeyAction` must be executed via `run`, e.g. /// ```no_run - /// # use mongocrypt::ctx::Algorithm; - /// # use mongodb::client_encryption::ClientEncryption; - /// # use mongodb::error::Result; - /// # async fn func() -> Result<()> { - /// # let client_encryption: ClientEncryption = todo!(); - /// # let master_key = todo!(); - /// let key = client_encryption - /// .create_data_key(master_key) - /// .key_alt_names(["altname1".to_string(), "altname2".to_string()]) - /// .run().await?; + /// # use mongocrypt::ctx::KmsProvider; + /// # use mongodb::{bson::doc, client_encryption::ClientEncryption, error::Result}; + /// # fn func() -> Result<()> { + /// # let kv_client = todo!(); + /// # let kv_namespace = todo!(); + /// # let local_key = doc! { }; + /// let enc = ClientEncryption::builder( + /// kv_client, + /// kv_namespace, + /// [ + /// (KmsProvider::local(), doc! { "key": local_key }, None), + /// (KmsProvider::kmip(), doc! { "endpoint": "localhost:5698" }, None), + /// ] + /// ) + /// .build()?; + /// # Ok(()) /// # } /// ``` - #[must_use] - pub fn create_data_key(&self, master_key: MasterKey) -> CreateDataKeyAction { - CreateDataKeyAction { - client_enc: self, - opts: DataKeyOptions { - master_key, - key_alt_names: None, - key_material: None, - }, - } - } - - pub(crate) async fn create_data_key_final( - &self, - kms_provider: &KmsProvider, - opts: impl Into>, - ) -> Result { - let ctx = self.create_data_key_ctx(kms_provider, opts.into().as_ref())?; - let data_key = self.exec.run_ctx(ctx, None).await?; - self.key_vault.insert_one(&data_key, None).await?; - let bin_ref = data_key - .get_binary("_id") - .map_err(|e| Error::internal(format!("invalid data key id: {}", e)))?; - Ok(bin_ref.to_binary()) - } - - fn create_data_key_ctx( - &self, - kms_provider: &KmsProvider, - opts: Option<&DataKeyOptions>, - ) -> Result { - let mut builder = self.crypt.ctx_builder(); - let mut key_doc = doc! { "provider": kms_provider.name() }; - if let Some(opts) = opts { - if !matches!(opts.master_key, MasterKey::Local) { - let master_doc = bson::to_document(&opts.master_key)?; - key_doc.extend(master_doc); - } - if let Some(alt_names) = &opts.key_alt_names { - for name in alt_names { - builder = builder.key_alt_name(name)?; - } - } - if let Some(material) = &opts.key_material { - builder = builder.key_material(material)?; - } + pub fn builder( + key_vault_client: Client, + key_vault_namespace: Namespace, + kms_providers: impl IntoIterator< + Item = (KmsProvider, crate::bson::Document, Option), + >, + ) -> ClientEncryptionBuilder { + ClientEncryptionBuilder { + key_vault_client, + key_vault_namespace, + kms_providers: kms_providers.into_iter().collect(), + key_cache_expiration: None, } - builder = builder.key_encryption_key(&key_doc)?; - Ok(builder.build_datakey()?) } // pub async fn rewrap_many_data_key(&self, _filter: Document, _opts: impl @@ -173,19 +111,19 @@ impl ClientEncryption { /// collection. Returns the result of the internal deleteOne() operation on the key vault /// collection. pub async fn delete_key(&self, id: &Binary) -> Result { - self.key_vault.delete_one(doc! { "_id": id }, None).await + self.key_vault.delete_one(doc! { "_id": id }).await } /// Finds a single key document with the given UUID (BSON binary subtype 0x04). /// Returns the result of the internal find() operation on the key vault collection. pub async fn get_key(&self, id: &Binary) -> Result> { - self.key_vault.find_one(doc! { "_id": id }, None).await + self.key_vault.find_one(doc! { "_id": id }).await } /// Finds all documents in the key vault collection. /// Returns the result of the internal find() operation on the key vault collection. pub async fn get_keys(&self) -> Result> { - self.key_vault.find(doc! {}, None).await + self.key_vault.find(doc! {}).await } /// Adds a keyAltName to the keyAltNames array of the key document in the key vault collection @@ -200,7 +138,6 @@ impl ClientEncryption { .find_one_and_update( doc! { "_id": id }, doc! { "$addToSet": { "keyAltNames": key_alt_name } }, - None, ) .await } @@ -230,7 +167,7 @@ impl ClientEncryption { } }; self.key_vault - .find_one_and_update(doc! { "_id": id }, vec![update], None) + .find_one_and_update(doc! { "_id": id }, vec![update]) .await } @@ -240,161 +177,13 @@ impl ClientEncryption { key_alt_name: impl AsRef, ) -> Result> { self.key_vault - .find_one(doc! { "keyAltNames": key_alt_name.as_ref() }, None) + .find_one(doc! { "keyAltNames": key_alt_name.as_ref() }) .await } - /// Encrypts a BsonValue with a given key and algorithm. - /// `EncryptAction::run` returns a `Binary` (subtype 6) containing the encrypted value. - /// - /// To insert or query with an "Indexed" encrypted payload, use a `Client` configured with - /// `AutoEncryptionOptions`. `AutoEncryptionOptions.bypass_query_analysis` may be true. - /// `AutoEncryptionOptions.bypass_auto_encryption` must be false. - /// - /// The returned `EncryptAction` must be executed via `run`, e.g. - /// ```no_run - /// # use mongocrypt::ctx::Algorithm; - /// # use mongodb::client_encryption::ClientEncryption; - /// # use mongodb::error::Result; - /// # async fn func() -> Result<()> { - /// # let client_encryption: ClientEncryption = todo!(); - /// # let key = String::new(); - /// let encrypted = client_encryption - /// .encrypt( - /// "plaintext", - /// key, - /// Algorithm::AeadAes256CbcHmacSha512Deterministic, - /// ) - /// .contention_factor(10) - /// .run().await?; - /// # } - /// ``` - #[must_use] - pub fn encrypt( - &self, - value: impl Into, - key: impl Into, - algorithm: Algorithm, - ) -> EncryptAction { - EncryptAction { - client_enc: self, - value: value.into(), - opts: EncryptOptions { - key: key.into(), - algorithm, - contention_factor: None, - query_type: None, - range_options: None, - }, - } - } - - /// NOTE: This method is experimental only. It is not intended for public use. - /// - /// Encrypts a match or aggregate expression with the given key. - /// `EncryptExpressionAction::run` returns a [`Document`] containing the encrypted expression. - /// - /// The expression will be encrypted using the [`Algorithm::RangePreview`] algorithm and the - /// "rangePreview" query type. - /// - /// The returned `EncryptExpressionAction` must be executed via `run`, e.g. - /// ```no_run - /// # use mongocrypt::ctx::Algorithm; - /// # use mongodb::client_encryption::ClientEncryption; - /// # use mongodb::error::Result; - /// # use bson::rawdoc; - /// # async fn func() -> Result<()> { - /// # let client_encryption: ClientEncryption = todo!(); - /// # let key = String::new(); - /// let expression = rawdoc! { - /// "$and": [ - /// { "field": { "$gte": 5 } }, - /// { "field": { "$lte": 10 } }, - /// ] - /// }; - /// let encrypted_expression = client_encryption - /// .encrypt_expression( - /// expression, - /// key, - /// ) - /// .contention_factor(10) - /// .run().await?; - /// # } - /// ``` - #[must_use] - pub fn encrypt_expression( - &self, - expression: RawDocumentBuf, - key: impl Into, - ) -> EncryptExpressionAction { - EncryptExpressionAction { - client_enc: self, - value: expression, - opts: EncryptOptions { - key: key.into(), - algorithm: Algorithm::RangePreview, - contention_factor: None, - query_type: Some("rangePreview".into()), - range_options: None, - }, - } - } - - async fn encrypt_final(&self, value: RawBson, opts: EncryptOptions) -> Result { - let builder = self.get_ctx_builder(&opts)?; - let ctx = builder.build_explicit_encrypt(value)?; - let result = self.exec.run_ctx(ctx, None).await?; - let bin_ref = result - .get_binary("v") - .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; - Ok(bin_ref.to_binary()) - } - - async fn encrypt_expression_final( - &self, - value: RawDocumentBuf, - opts: EncryptOptions, - ) -> Result { - let builder = self.get_ctx_builder(&opts)?; - let ctx = builder.build_explicit_encrypt_expression(value)?; - let result = self.exec.run_ctx(ctx, None).await?; - let doc_ref = result - .get_document("v") - .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; - let doc = doc_ref - .to_owned() - .to_document() - .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; - Ok(doc) - } - - fn get_ctx_builder(&self, opts: &EncryptOptions) -> Result { - let mut builder = self.crypt.ctx_builder(); - match &opts.key { - EncryptKey::Id(id) => { - builder = builder.key_id(&id.bytes)?; - } - EncryptKey::AltName(name) => { - builder = builder.key_alt_name(name)?; - } - } - builder = builder.algorithm(opts.algorithm)?; - if let Some(factor) = opts.contention_factor { - builder = builder.contention_factor(factor)?; - } - if let Some(qtype) = &opts.query_type { - builder = builder.query_type(qtype)?; - } - if let Some(range_options) = &opts.range_options { - let options_doc = bson::to_document(range_options)?; - builder = builder.range_options(options_doc)?; - } - Ok(builder) - } - /// Decrypts an encrypted value (BSON binary of subtype 6). /// Returns the original BSON value. - pub async fn decrypt<'a>(&self, value: RawBinaryRef<'a>) -> Result { + pub async fn decrypt(&self, value: RawBinaryRef<'_>) -> Result { if value.subtype != BinarySubtype::Encrypted { return Err(Error::invalid_argument(format!( "Invalid binary subtype for decrypt: expected {:?}, got {:?}", @@ -412,283 +201,251 @@ impl ClientEncryption { .ok_or_else(|| Error::internal("invalid decryption result"))? .to_raw_bson()) } - - /// Creates a new collection with encrypted fields, automatically creating new data encryption - /// keys when needed based on the configured [`CreateCollectionOptions::encrypted_fields`]. - /// - /// Returns the potentially updated `encrypted_fields` along with status, as keys may have been - /// created even when a failure occurs. - /// - /// Does not affect any auto encryption settings on existing MongoClients that are already - /// configured with auto encryption. - pub async fn create_encrypted_collection( - &self, - db: &Database, - name: impl AsRef, - master_key: MasterKey, - options: CreateCollectionOptions, - ) -> (Document, Result<()>) { - let ef = match options.encrypted_fields.as_ref() { - Some(ef) => ef, - None => { - return ( - doc! {}, - Err(Error::invalid_argument( - "no encrypted_fields defined for collection", - )), - ); - } - }; - let mut ef_prime = ef.clone(); - if let Ok(fields) = ef_prime.get_array_mut("fields") { - for f in fields { - let f_doc = if let Some(d) = f.as_document_mut() { - d - } else { - continue; - }; - if f_doc.get("keyId") == Some(&Bson::Null) { - let d = match self.create_data_key(master_key.clone()).run().await { - Ok(v) => v, - Err(e) => return (ef_prime, Err(e)), - }; - f_doc.insert("keyId", d); - } - } - } - let mut opts_prime = options.clone(); - opts_prime.encrypted_fields = Some(ef_prime.clone()); - ( - ef_prime, - db.create_collection(name.as_ref(), opts_prime).await, - ) - } } -/// Options for creating a data key. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub(crate) struct DataKeyOptions { - pub(crate) master_key: MasterKey, - pub(crate) key_alt_names: Option>, - pub(crate) key_material: Option>, +/// Builder for constructing a [`ClientEncryption`]. Construct by calling +/// [`ClientEncryption::builder`]. +pub struct ClientEncryptionBuilder { + key_vault_client: Client, + key_vault_namespace: Namespace, + kms_providers: Vec<(KmsProvider, crate::bson::Document, Option)>, + key_cache_expiration: Option, } -/// A pending `ClientEncryption::create_data_key` action. -pub struct CreateDataKeyAction<'a> { - client_enc: &'a ClientEncryption, - opts: DataKeyOptions, -} - -impl<'a> CreateDataKeyAction<'a> { - /// Execute the pending data key creation. - pub async fn run(self) -> Result { - self.client_enc - .create_data_key_final(&self.opts.master_key.provider(), self.opts) - .await - } - - /// Set an optional list of alternate names that can be used to reference the key. - pub fn key_alt_names(mut self, names: impl IntoIterator) -> Self { - self.opts.key_alt_names = Some(names.into_iter().collect()); +impl ClientEncryptionBuilder { + /// Set the duration of time after which the data encryption key cache should expire. Defaults + /// to 60 seconds if unset. + pub fn key_cache_expiration(mut self, expiration: impl Into>) -> Self { + self.key_cache_expiration = expiration.into(); self } - /// Set a buffer of 96 bytes to use as custom key material for the data key being - /// created. If unset, key material for the new data key is generated from a cryptographically - /// secure random device. - pub fn key_material(mut self, material: impl IntoIterator) -> Self { - self.opts.key_material = Some(material.into_iter().collect()); - self + /// Build the [`ClientEncryption`]. + pub fn build(self) -> Result { + let kms_providers = KmsProviders::new(self.kms_providers)?; + + let mut crypt_builder = Crypt::builder() + .kms_providers(&kms_providers.credentials_doc()?)? + .use_need_kms_credentials_state() + .use_range_v2()? + .retry_kms(true)?; + if let Some(key_cache_expiration) = self.key_cache_expiration { + let expiration_ms: u64 = key_cache_expiration.as_millis().try_into().map_err(|_| { + Error::invalid_argument(format!( + "key_cache_expiration must not exceed {} milliseconds, got {:?}", + u64::MAX, + key_cache_expiration + )) + })?; + crypt_builder = crypt_builder.key_cache_expiration(expiration_ms)?; + } + let crypt = crypt_builder.build()?; + + let exec = CryptExecutor::new_explicit( + self.key_vault_client.weak(), + self.key_vault_namespace.clone(), + kms_providers, + )?; + let key_vault = self + .key_vault_client + .database(&self.key_vault_namespace.db) + .collection_with_options( + &self.key_vault_namespace.coll, + CollectionOptions::builder() + .write_concern(WriteConcern::majority()) + .read_concern(ReadConcern::majority()) + .build(), + ); + + Ok(ClientEncryption { + crypt, + exec, + key_vault, + }) } } /// A KMS-specific key used to encrypt data keys. -#[serde_with::skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] #[non_exhaustive] #[allow(missing_docs)] pub enum MasterKey { - #[serde(rename_all = "camelCase")] - Aws { - region: String, - /// The Amazon Resource Name (ARN) to the AWS customer master key (CMK). - key: String, - /// An alternate host identifier to send KMS requests to. May include port number. Defaults - /// to "kms.REGION.amazonaws.com" - endpoint: Option, - }, - #[serde(rename_all = "camelCase")] - Azure { - /// Host with optional port. Example: "example.vault.azure.net". - key_vault_endpoint: String, - key_name: String, - /// A specific version of the named key, defaults to using the key's primary version. - key_version: Option, - }, - #[serde(rename_all = "camelCase")] - Gcp { - project_id: String, - location: String, - key_ring: String, - key_name: String, - /// A specific version of the named key, defaults to using the key's primary version. - key_version: Option, - /// Host with optional port. Defaults to "cloudkms.googleapis.com". - endpoint: Option, - }, - /// Master keys are not applicable to `KmsProvider::Local`. - Local, - #[serde(rename_all = "camelCase")] - Kmip { - /// keyId is the KMIP Unique Identifier to a 96 byte KMIP Secret Data managed object. If - /// keyId is omitted, the driver creates a random 96 byte KMIP Secret Data managed object. - key_id: Option, - /// Host with optional port. - endpoint: Option, - }, + Aws(AwsMasterKey), + Azure(AzureMasterKey), + Gcp(GcpMasterKey), + Kmip(KmipMasterKey), + Local(LocalMasterKey), } -impl MasterKey { - /// Returns the `KmsProvider` associated with this key. - pub fn provider(&self) -> KmsProvider { - match self { - MasterKey::Aws { .. } => KmsProvider::Aws, - MasterKey::Azure { .. } => KmsProvider::Azure, - MasterKey::Gcp { .. } => KmsProvider::Gcp, - MasterKey::Kmip { .. } => KmsProvider::Kmip, - MasterKey::Local => KmsProvider::Local, - } - } -} +/// An AWS master key. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct AwsMasterKey { + /// The name for the key. The value for this field must be the same as the corresponding + /// [`KmsProvider`](mongocrypt::ctx::KmsProvider)'s name. + #[serde(skip)] + pub name: Option, -// #[non_exhaustive] -// pub struct RewrapManyDataKeyOptions { -// pub provider: KmsProvider, -// pub master_key: Option, -// } -// -// -// #[non_exhaustive] -// pub struct RewrapManyDataKeyResult { -// pub bulk_write_result: Option, -// } + /// The region. + pub region: String, -/// The options for explicit encryption. -#[derive(Debug, Clone)] -#[non_exhaustive] -pub(crate) struct EncryptOptions { - pub(crate) key: EncryptKey, - pub(crate) algorithm: Algorithm, - pub(crate) contention_factor: Option, - pub(crate) query_type: Option, - pub(crate) range_options: Option, + /// The Amazon Resource Name (ARN) to the AWS customer master key (CMK). + pub key: String, + + /// An alternate host identifier to send KMS requests to. May include port number. Defaults to + /// "kms.\.amazonaws.com". + pub endpoint: Option, } -/// NOTE: These options are experimental and not intended for public use. -/// -/// The index options for a Queryable Encryption field supporting "rangePreview" queries. -/// The options set must match the values set in the encryptedFields of the destination collection. -#[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, TypedBuilder)] +impl From for MasterKey { + fn from(aws_master_key: AwsMasterKey) -> Self { + Self::Aws(aws_master_key) + } +} + +/// An Azure master key. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] #[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] #[non_exhaustive] -pub struct RangeOptions { - /// The minimum value. This option must be set if `precision` is set. - pub min: Option, +pub struct AzureMasterKey { + /// The name for the key. The value for this field must be the same as the corresponding + /// [`KmsProvider`](mongocrypt::ctx::KmsProvider)'s name. + #[serde(skip)] + pub name: Option, - /// The maximum value. This option must be set if `precision` is set. - pub max: Option, + /// Host with optional port. Example: "example.vault.azure.net". + pub key_vault_endpoint: String, - /// The sparsity. - pub sparsity: i64, + /// The key name. + pub key_name: String, - /// The precision. This value must only be set for Double and Decimal128 fields. - pub precision: Option, + /// A specific version of the named key, defaults to using the key's primary version. + pub key_version: Option, } -/// A pending `ClientEncryption::encrypt` action. -pub struct EncryptAction<'a> { - client_enc: &'a ClientEncryption, - value: bson::RawBson, - opts: EncryptOptions, +impl From for MasterKey { + fn from(azure_master_key: AzureMasterKey) -> Self { + Self::Azure(azure_master_key) + } } -impl<'a> EncryptAction<'a> { - /// Execute the encryption. - pub async fn run(self) -> Result { - self.client_enc.encrypt_final(self.value, self.opts).await - } +/// A GCP master key. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct GcpMasterKey { + /// The name for the key. The value for this field must be the same as the corresponding + /// [`KmsProvider`](mongocrypt::ctx::KmsProvider)'s name. + #[serde(skip)] + pub name: Option, - /// Set the contention factor. - pub fn contention_factor(mut self, factor: impl Into>) -> Self { - self.opts.contention_factor = factor.into(); - self - } + /// The project ID. + pub project_id: String, - /// Set the query type. - pub fn query_type(mut self, qtype: impl Into) -> Self { - self.opts.query_type = Some(qtype.into()); - self - } + /// The location. + pub location: String, - /// NOTE: This method is experimental and not intended for public use. - /// - /// Set the range options. This method should only be called when the algorithm is - /// [`Algorithm::RangePreview`]. - pub fn range_options(mut self, range_options: impl Into>) -> Self { - self.opts.range_options = range_options.into(); - self - } -} + /// The key ring. + pub key_ring: String, + + /// The key name. + pub key_name: String, + + /// A specific version of the named key. Defaults to using the key's primary version. + pub key_version: Option, -/// A pending `ClientEncryption::encrypt_expression` action. -pub struct EncryptExpressionAction<'a> { - client_enc: &'a ClientEncryption, - value: RawDocumentBuf, - opts: EncryptOptions, + /// Host with optional port. Defaults to "cloudkms.googleapis.com". + pub endpoint: Option, } -impl<'a> EncryptExpressionAction<'a> { - /// Execute the encryption of the expression. - pub async fn run(self) -> Result { - self.client_enc - .encrypt_expression_final(self.value, self.opts) - .await +impl From for MasterKey { + fn from(gcp_master_key: GcpMasterKey) -> Self { + Self::Gcp(gcp_master_key) } +} - /// Set the contention factor. - pub fn contention_factor(mut self, factor: impl Into>) -> Self { - self.opts.contention_factor = factor.into(); - self - } +/// A local master key. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct LocalMasterKey { + /// The name for the key. The value for this field must be the same as the corresponding + /// [`KmsProvider`](mongocrypt::ctx::KmsProvider)'s name. + #[serde(skip)] + pub name: Option, +} - /// Set the range options. - pub fn range_options(mut self, range_options: impl Into>) -> Self { - self.opts.range_options = range_options.into(); - self +impl From for MasterKey { + fn from(local_master_key: LocalMasterKey) -> Self { + Self::Local(local_master_key) } } -/// An encryption key reference. -#[derive(Debug, Clone)] +/// A KMIP master key. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] #[non_exhaustive] -pub enum EncryptKey { - /// Find the key by _id value. - Id(Binary), - /// Find the key by alternate name. - AltName(String), +pub struct KmipMasterKey { + /// The name for the key. The value for this field must be the same as the corresponding + /// [`KmsProvider`](mongocrypt::ctx::KmsProvider)'s name. + #[serde(skip)] + pub name: Option, + + /// The KMIP Unique Identifier to a 96 byte KMIP Secret Data managed object. If this field is + /// not specified, the driver creates a random 96 byte KMIP Secret Data managed object. + pub key_id: Option, + + /// If true (recommended), the KMIP server must decrypt this key. Defaults to false. + pub delegated: Option, + + /// Host with optional port. + pub endpoint: Option, } -impl From for EncryptKey { - fn from(bin: Binary) -> Self { - Self::Id(bin) +impl From for MasterKey { + fn from(kmip_master_key: KmipMasterKey) -> Self { + Self::Kmip(kmip_master_key) } } -impl From for EncryptKey { - fn from(s: String) -> Self { - Self::AltName(s) +impl MasterKey { + /// Returns the `KmsProvider` associated with this key. + pub fn provider(&self) -> KmsProvider { + let (provider, name) = match self { + MasterKey::Aws(AwsMasterKey { name, .. }) => (KmsProvider::aws(), name.clone()), + MasterKey::Azure(AzureMasterKey { name, .. }) => (KmsProvider::azure(), name.clone()), + MasterKey::Gcp(GcpMasterKey { name, .. }) => (KmsProvider::gcp(), name.clone()), + MasterKey::Kmip(KmipMasterKey { name, .. }) => (KmsProvider::kmip(), name.clone()), + MasterKey::Local(LocalMasterKey { name, .. }) => (KmsProvider::local(), name.clone()), + }; + if let Some(name) = name { + provider.with_name(name) + } else { + provider + } } } + +// #[non_exhaustive] +// pub struct RewrapManyDataKeyOptions { +// pub provider: KmsProvider, +// pub master_key: Option, +// } +// +// +// #[non_exhaustive] +// pub struct RewrapManyDataKeyResult { +// pub bulk_write_result: Option, +// } diff --git a/src/client/csfle/client_encryption/create_data_key.rs b/src/client/csfle/client_encryption/create_data_key.rs new file mode 100644 index 000000000..faca26041 --- /dev/null +++ b/src/client/csfle/client_encryption/create_data_key.rs @@ -0,0 +1,63 @@ +use crate::bson::{doc, Binary}; +use mongocrypt::ctx::{Ctx, KmsProvider}; + +use crate::{ + action::{ + action_impl, + csfle::{CreateDataKey, DataKeyOptions}, + }, + error::{Error, Result}, +}; + +use super::{ClientEncryption, MasterKey}; + +#[action_impl] +impl<'a> Action for CreateDataKey<'a> { + type Future = CreateDataKeyFuture; + + async fn execute(self) -> Result { + #[allow(unused_mut)] + let mut provider = self.master_key.provider(); + #[cfg(test)] + if let Some(tp) = self.test_kms_provider { + provider = tp; + } + let ctx = self + .client_enc + .create_data_key_ctx(provider, self.master_key, self.options)?; + let data_key = self.client_enc.exec.run_ctx(ctx, None).await?; + self.client_enc.key_vault.insert_one(&data_key).await?; + let bin_ref = data_key + .get_binary("_id") + .map_err(|e| Error::internal(format!("invalid data key id: {}", e)))?; + Ok(bin_ref.to_binary()) + } +} + +impl ClientEncryption { + fn create_data_key_ctx( + &self, + kms_provider: KmsProvider, + master_key: MasterKey, + opts: Option, + ) -> Result { + let mut builder = self.crypt.ctx_builder(); + let mut key_doc = doc! { "provider": kms_provider.as_string() }; + if !matches!(master_key, MasterKey::Local(_)) { + let master_doc = crate::bson_compat::serialize_to_document(&master_key)?; + key_doc.extend(master_doc); + } + if let Some(opts) = opts { + if let Some(alt_names) = &opts.key_alt_names { + for name in alt_names { + builder = builder.key_alt_name(name)?; + } + } + if let Some(material) = &opts.key_material { + builder = builder.key_material(material)?; + } + } + builder = builder.key_encryption_key(&key_doc)?; + Ok(builder.build_datakey()?) + } +} diff --git a/src/client/csfle/client_encryption/encrypt.rs b/src/client/csfle/client_encryption/encrypt.rs new file mode 100644 index 000000000..8e46950e6 --- /dev/null +++ b/src/client/csfle/client_encryption/encrypt.rs @@ -0,0 +1,94 @@ +use crate::bson::{Binary, Document}; +use mongocrypt::ctx::{Algorithm, CtxBuilder}; + +use crate::{ + action::{ + action_impl, + csfle::encrypt::{Encrypt, EncryptKey, EncryptOptions, Expression, Value}, + }, + error::{Error, Result}, +}; + +use super::ClientEncryption; + +#[action_impl] +impl<'a> Action for Encrypt<'a, Value> { + type Future = EncryptFuture; + + async fn execute(self) -> Result { + let ctx = self + .client_enc + .get_ctx_builder(self.key, self.algorithm, self.options.unwrap_or_default())? + .build_explicit_encrypt(self.mode.value)?; + let result = self.client_enc.exec.run_ctx(ctx, None).await?; + let bin_ref = result + .get_binary("v") + .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; + Ok(bin_ref.to_binary()) + } +} + +#[action_impl] +impl<'a> Action for Encrypt<'a, Expression> { + type Future = EncryptExpressionFuture; + + async fn execute(mut self) -> Result { + let options = self.options.get_or_insert_with(Default::default); + match options.query_type { + Some(ref query_type) => { + if query_type != "range" { + return Err(Error::invalid_argument(format!( + "query_type cannot be set for encrypt_expression, got {}", + query_type + ))); + } + } + None => options.query_type = Some("range".to_string()), + } + + let ctx = self + .client_enc + .get_ctx_builder(self.key, self.algorithm, self.options.unwrap_or_default())? + .build_explicit_encrypt_expression(self.mode.value)?; + let result = self.client_enc.exec.run_ctx(ctx, None).await?; + let doc_ref = result + .get_document("v") + .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; + let doc = doc_ref + .to_owned() + .to_document() + .map_err(|e| Error::internal(format!("invalid encryption result: {}", e)))?; + Ok(doc) + } +} + +impl ClientEncryption { + fn get_ctx_builder( + &self, + key: EncryptKey, + algorithm: Algorithm, + opts: EncryptOptions, + ) -> Result { + let mut builder = self.crypt.ctx_builder(); + match key { + EncryptKey::Id(id) => { + builder = builder.key_id(&id.bytes)?; + } + EncryptKey::AltName(name) => { + builder = builder.key_alt_name(&name)?; + } + } + builder = builder.algorithm(algorithm)?; + if let Some(factor) = opts.contention_factor { + builder = builder.contention_factor(factor)?; + } + if let Some(qtype) = &opts.query_type { + builder = builder.query_type(qtype)?; + } + if let Some(range_options) = &opts.range_options { + let options_doc = crate::bson_compat::serialize_to_document(range_options)?; + builder = builder.algorithm_range(options_doc)?; + } + Ok(builder) + } +} diff --git a/src/client/csfle/options.rs b/src/client/csfle/options.rs index 711cb5924..d137ed0df 100644 --- a/src/client/csfle/options.rs +++ b/src/client/csfle/options.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; -use bson::Array; +use crate::bson::Array; use mongocrypt::ctx::KmsProvider; use serde::Deserialize; @@ -8,6 +8,7 @@ use crate::{ bson::{Bson, Document}, client::options::TlsOptions, error::{Error, Result}, + serde_util, Namespace, }; @@ -17,7 +18,7 @@ use crate::{ /// collection. Automatic encryption is not supported for operations on a database or view, and /// operations that are not bypassed will result in error (see [libmongocrypt: Auto Encryption /// Allow-List]( -/// https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#libmongocrypt-auto-encryption-allow-list +/// https://specifications.readthedocs.io/en/latest/client-side-encryption/client-side-encryption/#libmongocrypt-auto-encryption-allow-list /// )). To bypass automatic encryption for all operations, set bypassAutoEncryption=true in /// AutoEncryptionOpts. #[derive(Debug, Clone, Deserialize)] @@ -59,6 +60,14 @@ pub(crate) struct AutoEncryptionOptions { #[cfg(test)] #[serde(skip)] pub(crate) disable_crypt_shared: Option, + /// The duration after which the data encryption key cache expires. Defaults to 60 seconds if + /// unset. + #[serde( + default, + rename = "keyExpirationMS", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" + )] + pub(crate) key_cache_expiration: Option, } fn default_key_vault_namespace() -> Namespace { @@ -81,6 +90,7 @@ impl AutoEncryptionOptions { bypass_query_analysis: None, #[cfg(test)] disable_crypt_shared: None, + key_cache_expiration: None, } } } @@ -97,7 +107,7 @@ pub(crate) type KmsProvidersTlsOptions = HashMap; impl KmsProviders { pub(crate) fn new( - providers: impl IntoIterator)>, + providers: impl IntoIterator)>, ) -> Result { let mut credentials = HashMap::new(); let mut tls_options = None; @@ -119,7 +129,9 @@ impl KmsProviders { } pub(crate) fn credentials_doc(&self) -> Result { - Ok(bson::to_document(&self.credentials)?) + Ok(crate::bson_compat::serialize_to_document( + &self.credentials, + )?) } pub(crate) fn tls_options(&self) -> Option<&KmsProvidersTlsOptions> { @@ -130,6 +142,49 @@ impl KmsProviders { &self.credentials } + #[cfg(test)] + pub(crate) fn set_test_options(&mut self) { + use mongocrypt::ctx::KmsProviderType; + + use crate::{ + bson::doc, + test::csfle::{ALL_KMS_PROVIDERS, AWS_KMS}, + }; + + let all_kms_providers = ALL_KMS_PROVIDERS.clone(); + for (provider, test_credentials, tls_options) in all_kms_providers { + if self.credentials.contains_key(&provider) + && !matches!(provider.provider_type(), KmsProviderType::Local) + { + self.set(provider, test_credentials, tls_options); + } + } + + let aws_temp_provider = KmsProvider::other("awsTemporary".to_string()); + if self.credentials.contains_key(&aws_temp_provider) { + let aws_credentials = doc! { + "accessKeyId": std::env::var("CSFLE_AWS_TEMP_ACCESS_KEY_ID").unwrap(), + "secretAccessKey": std::env::var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY").unwrap(), + "sessionToken": std::env::var("CSFLE_AWS_TEMP_SESSION_TOKEN").unwrap() + }; + self.set(KmsProvider::aws(), aws_credentials, AWS_KMS.clone().2); + self.clear(&aws_temp_provider); + } + + let aws_temp_no_session_token_provider = KmsProvider::other("awsTemporaryNoSessionToken"); + if self + .credentials + .contains_key(&aws_temp_no_session_token_provider) + { + let aws_credentials = doc! { + "accessKeyId": std::env::var("CSFLE_AWS_TEMP_ACCESS_KEY_ID").unwrap(), + "secretAccessKey": std::env::var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY").unwrap(), + }; + self.set(KmsProvider::aws(), aws_credentials, AWS_KMS.clone().2); + self.clear(&aws_temp_no_session_token_provider); + } + } + #[cfg(test)] pub(crate) fn set(&mut self, provider: KmsProvider, creds: Document, tls: Option) { self.credentials.insert(provider.clone(), creds); diff --git a/src/client/csfle/state_machine.rs b/src/client/csfle/state_machine.rs index a47c260e7..5d7363126 100644 --- a/src/client/csfle/state_machine.rs +++ b/src/client/csfle/state_machine.rs @@ -2,11 +2,15 @@ use std::{ convert::TryInto, ops::DerefMut, path::{Path, PathBuf}, + time::Duration, }; -use bson::{rawdoc, Document, RawDocument, RawDocumentBuf}; +use crate::{ + bson::{rawdoc, Document, RawDocument, RawDocumentBuf}, + bson_compat::{cstr, CString}, +}; use futures_util::{stream, TryStreamExt}; -use mongocrypt::ctx::{Ctx, KmsProvider, State}; +use mongocrypt::ctx::{Ctx, KmsCtx, KmsProviderType, State}; use rayon::ThreadPool; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -14,10 +18,9 @@ use tokio::{ }; use crate::{ - client::{options::ServerAddress, WeakClient}, - coll::options::FindOptions, + client::{csfle::options::KmsProvidersTlsOptions, options::ServerAddress, WeakClient}, error::{Error, Result}, - operation::{RawOutput, RunCommand}, + operation::{run_command::RunCommand, RawOutput}, options::ReadConcern, runtime::{process::Process, AsyncStream, TlsConfig}, Client, @@ -114,7 +117,7 @@ impl CryptExecutor { let db = metadata_client.database(db.as_ref().ok_or_else(|| { Error::internal("db required for NeedMongoCollinfo state") })?); - let mut cursor = db.list_collections(filter, None).await?; + let mut cursor = db.list_collections().filter(filter).await?; if cursor.advance().await? { ctx.mongo_feed(cursor.current())?; } @@ -126,7 +129,7 @@ impl CryptExecutor { let db = db.as_ref().ok_or_else(|| { Error::internal("db required for NeedMongoMarkings state") })?; - let op = RawOutput(RunCommand::new_raw(db.to_string(), command, None, None)?); + let op = RawOutput(RunCommand::new(db.to_string(), command, None, None)); let mongocryptd_client = self.mongocryptd_client.as_ref().ok_or_else(|| { Error::invalid_argument("this operation requires mongocryptd") })?; @@ -164,12 +167,8 @@ impl CryptExecutor { .database(&kv_ns.db) .collection::(&kv_ns.coll); let mut cursor = kv_coll - .find( - filter, - FindOptions::builder() - .read_concern(ReadConcern::MAJORITY) - .build(), - ) + .find(filter) + .read_concern(ReadConcern::majority()) .await?; while cursor.advance().await? { ctx.mongo_feed(cursor.current())?; @@ -179,134 +178,170 @@ impl CryptExecutor { State::NeedKms => { let ctx = result_mut(&mut ctx)?; let scope = ctx.kms_scope(); - let mut kms_ctxen: Vec> = vec![]; - while let Some(kms_ctx) = scope.next_kms_ctx() { - kms_ctxen.push(Ok(kms_ctx)); + + async fn execute( + kms_ctx: &mut KmsCtx<'_>, + tls_options: Option<&KmsProvidersTlsOptions>, + ) -> Result<()> { + let endpoint = kms_ctx.endpoint()?; + let addr = ServerAddress::parse(endpoint)?; + let provider = kms_ctx.kms_provider()?; + let tls_options = tls_options + .and_then(|tls| tls.get(&provider)) + .cloned() + .unwrap_or_default(); + let mut stream = + AsyncStream::connect(addr, Some(&TlsConfig::new(tls_options)?)).await?; + stream.write_all(kms_ctx.message()?).await?; + let mut buf = vec![0]; + while kms_ctx.bytes_needed() > 0 { + let buf_size = kms_ctx.bytes_needed().try_into().map_err(|e| { + Error::internal(format!("buffer size overflow: {}", e)) + })?; + buf.resize(buf_size, 0); + let count = stream.read(&mut buf).await?; + kms_ctx.feed(&buf[0..count])?; + } + Ok(()) + } + + loop { + let mut kms_contexts: Vec> = Vec::new(); + while let Some(kms_ctx) = scope.next_kms_ctx() { + kms_contexts.push(Ok(kms_ctx)); + } + if kms_contexts.is_empty() { + break; + } + + stream::iter(kms_contexts) + .try_for_each_concurrent(None, |mut kms_ctx| async move { + let sleep_micros = + u64::try_from(kms_ctx.sleep_micros()).unwrap_or(0); + if sleep_micros > 0 { + tokio::time::sleep(Duration::from_micros(sleep_micros)).await; + } + + if let Err(error) = + execute(&mut kms_ctx, self.kms_providers.tls_options()).await + { + if !kms_ctx.retry_failure() { + return Err(error); + } + } + + Ok(()) + }) + .await?; } - stream::iter(kms_ctxen) - .try_for_each_concurrent(None, |mut kms_ctx| async move { - let endpoint = kms_ctx.endpoint()?; - let addr = ServerAddress::parse(endpoint)?; - let provider = kms_ctx.kms_provider()?; - let tls_options = self - .kms_providers - .tls_options() - .and_then(|tls| tls.get(&provider)) - .cloned() - .unwrap_or_default(); - let mut stream = - AsyncStream::connect(addr, Some(&TlsConfig::new(tls_options)?)) - .await?; - stream.write_all(kms_ctx.message()?).await?; - let mut buf = vec![0]; - while kms_ctx.bytes_needed() > 0 { - let buf_size = kms_ctx.bytes_needed().try_into().map_err(|e| { - Error::internal(format!("buffer size overflow: {}", e)) - })?; - buf.resize(buf_size, 0); - let count = stream.read(&mut buf).await?; - kms_ctx.feed(&buf[0..count])?; - } - Ok(()) - }) - .await?; } State::NeedKmsCredentials => { let ctx = result_mut(&mut ctx)?; #[allow(unused_mut)] let mut kms_providers = rawdoc! {}; let credentials = self.kms_providers.credentials(); - if credentials - .get(&KmsProvider::Aws) - .map_or(false, Document::is_empty) - { - #[cfg(feature = "aws-auth")] - { - let aws_creds = crate::client::auth::aws::AwsCredential::get( - &crate::client::auth::Credential::default(), - &crate::runtime::HttpClient::default(), - ) - .await?; - let mut creds = rawdoc! { - "accessKeyId": aws_creds.access_key(), - "secretAccessKey": aws_creds.secret_key(), - }; - if let Some(token) = aws_creds.session_token() { - creds.append("sessionToken", token); - } - kms_providers.append("aws", creds); + for (provider, options) in credentials { + if !options.is_empty() { + continue; } - #[cfg(not(feature = "aws-auth"))] - { - return Err(Error::invalid_argument( - "On-demand AWS KMS credentials require the `aws-auth` feature.", - )); - } - } - if credentials - .get(&KmsProvider::Azure) - .map_or(false, Document::is_empty) - { - #[cfg(feature = "azure-kms")] - { - kms_providers.append("azure", self.azure.get_token().await?); - } - #[cfg(not(feature = "azure-kms"))] - { - return Err(Error::invalid_argument( - "On-demand Azure KMS credentials require the `azure-kms` feature.", - )); - } - } - if credentials - .get(&KmsProvider::Gcp) - .map_or(false, Document::is_empty) - { - #[cfg(feature = "gcp-kms")] - { - use crate::runtime::HttpClient; - use serde::Deserialize; - - #[derive(Deserialize)] - struct ResponseBody { - access_token: String, - } - fn kms_error(error: String) -> Error { - let message = format!( - "An error occurred when obtaining GCP credentials: {}", - error - ); - let error = mongocrypt::error::Error { - kind: mongocrypt::error::ErrorKind::Kms, - message: Some(message), - code: None, - }; - error.into() + let prov_name: CString = provider.as_string().try_into()?; + match provider.provider_type() { + KmsProviderType::Aws => { + #[cfg(feature = "aws-auth")] + { + use crate::{ + client::auth::{aws::AwsCredential, Credential}, + runtime::HttpClient, + }; + + let aws_creds = AwsCredential::get( + &Credential::default(), + &HttpClient::default(), + ) + .await?; + let mut creds = rawdoc! { + "accessKeyId": aws_creds.access_key(), + "secretAccessKey": aws_creds.secret_key(), + }; + if let Some(token) = aws_creds.session_token() { + creds.append(cstr!("sessionToken"), token); + } + kms_providers.append(prov_name, creds); + } + #[cfg(not(feature = "aws-auth"))] + { + return Err(Error::invalid_argument( + "On-demand AWS KMS credentials require the `aws-auth` \ + feature.", + )); + } } + KmsProviderType::Azure => { + #[cfg(feature = "azure-kms")] + { + kms_providers.append(prov_name, self.azure.get_token().await?); + } + #[cfg(not(feature = "azure-kms"))] + { + return Err(Error::invalid_argument( + "On-demand Azure KMS credentials require the `azure-kms` \ + feature.", + )); + } + } + KmsProviderType::Gcp => { + #[cfg(feature = "gcp-kms")] + { + use crate::runtime::HttpClient; + use serde::Deserialize; + + #[derive(Deserialize)] + struct ResponseBody { + access_token: String, + } - let http_client = HttpClient::default(); - let host = std::env::var("GCE_METADATA_HOST") - .unwrap_or_else(|_| "metadata.google.internal".into()); - let uri = format!( - "http://{}/computeMetadata/v1/instance/service-accounts/default/token", - host - ); - - let response: ResponseBody = http_client - .get(&uri) - .headers(&[("Metadata-Flavor", "Google")]) - .send() - .await - .map_err(|e| kms_error(e.to_string()))?; - kms_providers - .append("gcp", rawdoc! { "accessToken": response.access_token }); - } - #[cfg(not(feature = "gcp-kms"))] - { - return Err(Error::invalid_argument( - "On-demand GCP KMS credentials require the `gcp-kms` feature.", - )); + fn kms_error(error: String) -> Error { + let message = format!( + "An error occurred when obtaining GCP credentials: {}", + error + ); + let error = mongocrypt::error::Error { + kind: mongocrypt::error::ErrorKind::Kms, + message: Some(message), + code: None, + }; + error.into() + } + + let http_client = HttpClient::default(); + let host = std::env::var("GCE_METADATA_HOST") + .unwrap_or_else(|_| "metadata.google.internal".into()); + let uri = format!( + "http://{}/computeMetadata/v1/instance/service-accounts/default/token", + host + ); + + let response: ResponseBody = http_client + .get(&uri) + .headers(&[("Metadata-Flavor", "Google")]) + .send() + .await + .map_err(|e| kms_error(e.to_string()))?; + kms_providers.append( + cstr!("gcp"), + rawdoc! { "accessToken": response.access_token }, + ); + } + #[cfg(not(feature = "gcp-kms"))] + { + return Err(Error::invalid_argument( + "On-demand GCP KMS credentials require the `gcp-kms` \ + feature.", + )); + } + } + _ => {} } } ctx.provide_kms_providers(&kms_providers)?; @@ -418,7 +453,7 @@ fn raw_to_doc(raw: &RawDocument) -> Result { #[cfg(feature = "azure-kms")] pub(crate) mod azure { - use bson::{rawdoc, RawDocumentBuf}; + use crate::bson::{rawdoc, RawDocumentBuf}; use serde::Deserialize; use std::time::{Duration, Instant}; use tokio::sync::Mutex; diff --git a/src/client/executor.rs b/src/client/executor.rs index f628ee5b8..ee3e81400 100644 --- a/src/client/executor.rs +++ b/src/client/executor.rs @@ -1,14 +1,21 @@ -#[cfg(feature = "in-use-encryption-unstable")] -use bson::RawDocumentBuf; -use bson::{doc, RawBsonRef, RawDocument, Timestamp}; -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] +use crate::bson::RawDocumentBuf; +use crate::bson::{doc, RawBsonRef, RawDocument, Timestamp}; +#[cfg(not(feature = "bson-3"))] +use crate::bson_compat::RawDocumentExt as _; +#[cfg(feature = "in-use-encryption")] use futures_core::future::BoxFuture; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use serde::de::DeserializeOwned; -use std::{collections::HashSet, sync::Arc, time::Instant}; +use std::{ + borrow::BorrowMut, + collections::HashSet, + sync::{atomic::Ordering, Arc}, + time::Instant, +}; -use super::{session::TransactionState, Client, ClientSession}; +use super::{options::ServerAddress, session::TransactionState, Client, ClientSession}; use crate::{ bson::Document, change_stream::{ @@ -19,11 +26,14 @@ use crate::{ WatchArgs, }, cmap::{ - conn::PinnedConnectionHandle, - Connection, + conn::{ + pooled::PooledConnection, + wire::{next_request_id, Message}, + PinnedConnectionHandle, + }, ConnectionPool, - RawCommand, RawCommandResponse, + StreamDescription, }, cursor::{session::SessionCursor, Cursor, CursorSpecification}, error::{ @@ -42,41 +52,40 @@ use crate::{ }, hello::LEGACY_HELLO_COMMAND_NAME_LOWERCASE, operation::{ + aggregate::{change_stream::ChangeStreamAggregate, AggregateTarget}, AbortTransaction, - AggregateTarget, - ChangeStreamAggregate, CommandErrorBody, CommitTransaction, + ExecutionContext, Operation, Retryability, }, options::{ChangeStreamOptions, SelectionCriteria}, - sdam::{HandshakePhase, SelectedServer, ServerType, TopologyType, TransactionSupportStatus}, + sdam::{HandshakePhase, ServerType, TopologyType, TransactionSupportStatus}, selection_criteria::ReadPreference, + tracking_arc::TrackingArc, ClusterTime, }; -lazy_static! { - pub(crate) static ref REDACTED_COMMANDS: HashSet<&'static str> = { - let mut hash_set = HashSet::new(); - hash_set.insert("authenticate"); - hash_set.insert("saslstart"); - hash_set.insert("saslcontinue"); - hash_set.insert("getnonce"); - hash_set.insert("createuser"); - hash_set.insert("updateuser"); - hash_set.insert("copydbgetnonce"); - hash_set.insert("copydbsaslstart"); - hash_set.insert("copydb"); - hash_set - }; - pub(crate) static ref HELLO_COMMAND_NAMES: HashSet<&'static str> = { - let mut hash_set = HashSet::new(); - hash_set.insert("hello"); - hash_set.insert(LEGACY_HELLO_COMMAND_NAME_LOWERCASE); - hash_set - }; -} +pub(crate) static REDACTED_COMMANDS: Lazy> = Lazy::new(|| { + let mut hash_set = HashSet::new(); + hash_set.insert("authenticate"); + hash_set.insert("saslstart"); + hash_set.insert("saslcontinue"); + hash_set.insert("getnonce"); + hash_set.insert("createuser"); + hash_set.insert("updateuser"); + hash_set.insert("copydbgetnonce"); + hash_set.insert("copydbsaslstart"); + hash_set.insert("copydb"); + hash_set +}); +pub(crate) static HELLO_COMMAND_NAMES: Lazy> = Lazy::new(|| { + let mut hash_set = HashSet::new(); + hash_set.insert("hello"); + hash_set.insert(LEGACY_HELLO_COMMAND_NAME_LOWERCASE); + hash_set +}); impl Client { /// Execute the given operation. @@ -86,65 +95,86 @@ impl Client { /// sessions and an explicit session is not provided. pub(crate) async fn execute_operation( &self, - op: T, + mut op: impl BorrowMut, session: impl Into>, ) -> Result { - self.execute_operation_with_details(op, session) + self.execute_operation_with_details(op.borrow_mut(), session) .await .map(|details| details.output) } async fn execute_operation_with_details( &self, - op: T, + op: &mut T, session: impl Into>, ) -> Result> { - Box::pin(async { - // TODO RUST-9: allow unacknowledged write concerns - if !op.is_acknowledged() { - return Err(ErrorKind::InvalidArgument { - message: "Unacknowledged write concerns are not supported".to_string(), - } - .into()); + // Validate inputs that can be checked before server selection and connection checkout. + if self.inner.shutdown.executed.load(Ordering::SeqCst) { + return Err(ErrorKind::Shutdown.into()); + } + // TODO RUST-9: remove this validation + if !op.is_acknowledged() { + return Err(ErrorKind::InvalidArgument { + message: "Unacknowledged write concerns are not supported".to_string(), } - let session = session.into(); - if let Some(session) = &session { - if !Arc::ptr_eq(&self.inner, &session.client().inner) { - return Err(ErrorKind::InvalidArgument { - message: "the session provided to an operation must be created from the \ - same client as the collection/database" - .into(), - } - .into()); - } + .into()); + } + if let Some(write_concern) = op.write_concern() { + write_concern.validate()?; + } - if let Some(SelectionCriteria::ReadPreference(read_preference)) = - op.selection_criteria() - { - if session.in_transaction() && read_preference != &ReadPreference::Primary { - return Err(ErrorKind::Transaction { - message: "read preference in a transaction must be primary".into(), - } - .into()); - } + // Validate the session and update its transaction status if needed. + let mut session = session.into(); + if let Some(ref mut session) = session { + if !TrackingArc::ptr_eq(&self.inner, &session.client().inner) { + return Err(Error::invalid_argument( + "the session provided to an operation must be created from the same client as \ + the collection/database on which the operation is being performed", + )); + } + if op + .selection_criteria() + .and_then(|sc| sc.as_read_pref()) + .is_some_and(|rp| rp != &ReadPreference::Primary) + && session.in_transaction() + { + return Err(ErrorKind::Transaction { + message: "read preference in a transaction must be primary".into(), } + .into()); } - self.execute_operation_with_retry(op, session).await - }) - .await + // If the current transaction has been committed/aborted and it is not being + // re-committed/re-aborted, reset the transaction's state to None. + if matches!( + session.transaction.state, + TransactionState::Committed { .. } + ) && op.name() != CommitTransaction::NAME + || session.transaction.state == TransactionState::Aborted + && op.name() != AbortTransaction::NAME + { + session.transaction.reset(); + } + } + + Box::pin(async { self.execute_operation_with_retry(op, session).await }).await } /// Execute the given operation, returning the cursor created by the operation. /// /// Server selection be will performed using the criteria specified on the operation, if any. - pub(crate) async fn execute_cursor_operation(&self, op: Op) -> Result> + pub(crate) async fn execute_cursor_operation( + &self, + mut op: impl BorrowMut, + ) -> Result> where Op: Operation, { Box::pin(async { - let mut details = self.execute_operation_with_details(op, None).await?; + let mut details = self + .execute_operation_with_details(op.borrow_mut(), None) + .await?; let pinned = - self.pin_connection_for_cursor(&details.output, &mut details.connection)?; + self.pin_connection_for_cursor(&details.output, &mut details.connection, None)?; Ok(Cursor::new( self.clone(), details.output, @@ -157,48 +187,42 @@ impl Client { pub(crate) async fn execute_session_cursor_operation( &self, - op: Op, + mut op: impl BorrowMut, session: &mut ClientSession, ) -> Result> where Op: Operation, { let mut details = self - .execute_operation_with_details(op, &mut *session) + .execute_operation_with_details(op.borrow_mut(), &mut *session) .await?; - let pinned = - self.pin_connection_for_session(&details.output, &mut details.connection, session)?; + let pinned = self.pin_connection_for_cursor( + &details.output, + &mut details.connection, + Some(session), + )?; Ok(SessionCursor::new(self.clone(), details.output, pinned)) } - fn is_load_balanced(&self) -> bool { + pub(crate) fn is_load_balanced(&self) -> bool { self.inner.options.load_balanced.unwrap_or(false) } - fn pin_connection_for_cursor( + pub(crate) fn pin_connection_for_cursor( &self, spec: &CursorSpecification, - conn: &mut Connection, - ) -> Result> { - if self.is_load_balanced() && spec.info.id != 0 { - Ok(Some(conn.pin()?)) - } else { - Ok(None) - } - } - - fn pin_connection_for_session( - &self, - spec: &CursorSpecification, - conn: &mut Connection, - session: &mut ClientSession, + conn: &mut PooledConnection, + session: Option<&mut ClientSession>, ) -> Result> { - if let Some(handle) = session.transaction.pinned_connection() { + if let Some(handle) = session.and_then(|s| s.transaction.pinned_connection()) { // Cursor operations on a transaction share the same pinned connection. Ok(Some(handle.replicate())) + } else if self.is_load_balanced() && spec.info.id != 0 { + // Cursor operations on load balanced topologies always pin connections. + Ok(Some(conn.pin()?)) } else { - self.pin_connection_for_cursor(spec, conn) + Ok(None) } } @@ -222,16 +246,17 @@ impl Client { let mut implicit_session = resume_data .as_mut() .and_then(|rd| rd.implicit_session.take()); - let op = ChangeStreamAggregate::new(&args, resume_data)?; + let mut op = ChangeStreamAggregate::new(&args, resume_data)?; let mut details = self - .execute_operation_with_details(op, implicit_session.as_mut()) + .execute_operation_with_details(&mut op, implicit_session.as_mut()) .await?; if let Some(session) = implicit_session { details.implicit_session = Some(session); } let (cursor_spec, cs_data) = details.output; - let pinned = self.pin_connection_for_cursor(&cursor_spec, &mut details.connection)?; + let pinned = + self.pin_connection_for_cursor(&cursor_spec, &mut details.connection, None)?; let cursor = Cursor::new(self.clone(), cursor_spec, details.implicit_session, pinned); Ok(ChangeStream::new(cursor, args, cs_data)) @@ -257,14 +282,17 @@ impl Client { target, options, }; - let op = ChangeStreamAggregate::new(&args, resume_data)?; + let mut op = ChangeStreamAggregate::new(&args, resume_data)?; let mut details = self - .execute_operation_with_details(op, &mut *session) + .execute_operation_with_details(&mut op, &mut *session) .await?; let (cursor_spec, cs_data) = details.output; - let pinned = - self.pin_connection_for_session(&cursor_spec, &mut details.connection, session)?; + let pinned = self.pin_connection_for_cursor( + &cursor_spec, + &mut details.connection, + Some(session), + )?; let cursor = SessionCursor::new(self.clone(), cursor_spec, pinned); Ok(SessionChangeStream::new(cursor, args, cs_data)) @@ -273,26 +301,13 @@ impl Client { } /// Selects a server and executes the given operation on it, optionally using a provided - /// session. Retries the operation upon failure if retryability is supported. + /// session. Retries the operation upon failure if retryability is supported or after + /// reauthenticating if reauthentication is required. async fn execute_operation_with_retry( &self, - mut op: T, + op: &mut T, mut session: Option<&mut ClientSession>, ) -> Result> { - // If the current transaction has been committed/aborted and it is not being - // re-committed/re-aborted, reset the transaction's state to TransactionState::None. - if let Some(ref mut session) = session { - if matches!( - session.transaction.state, - TransactionState::Committed { .. } - ) && op.name() != CommitTransaction::NAME - || session.transaction.state == TransactionState::Aborted - && op.name() != AbortTransaction::NAME - { - session.transaction.reset(); - } - } - let mut retry: Option = None; let mut implicit_session: Option = None; loop { @@ -305,35 +320,52 @@ impl Client { .and_then(|s| s.transaction.pinned_mongos()) .or_else(|| op.selection_criteria()); - let server = match self.select_server(selection_criteria, op.name()).await { - Ok(server) => server, + let (server, effective_criteria) = match self + .select_server( + selection_criteria, + crate::bson_compat::cstr_to_str(op.name()), + retry.as_ref().map(|r| &r.first_server), + op.override_criteria(), + ) + .await + { + Ok(out) => out, Err(mut err) => { retry.first_error()?; - err.add_labels_and_update_pin(None, &mut session, None)?; + err.add_labels_and_update_pin(None, &mut session, None); return Err(err); } }; + let server_addr = server.address.clone(); - let mut conn = match get_connection(&session, &op, &server.pool).await { + let mut conn = match get_connection(&session, op, &server.pool).await { Ok(c) => c, Err(mut err) => { retry.first_error()?; - err.add_labels_and_update_pin(None, &mut session, None)?; + err.add_labels_and_update_pin(None, &mut session, None); if err.is_read_retryable() && self.inner.options.retry_writes != Some(false) { err.add_label(RETRYABLE_WRITE_ERROR); } - let op_retry = match self.get_op_retryability(&op, &session) { - Retryability::Read => err.is_read_retryable(), - Retryability::Write => err.is_write_retryable(), - _ => false, + let retryability = op.retryability().with_options(self.options()); + let can_retry = match retryability { + // Read-retryable operations should be retried on pool cleared errors during + // connection checkout regardless of transaction status. + Retryability::Read if err.is_pool_cleared() => true, + _ => { + retryability.can_retry_error(&err) + && !session + .as_ref() + .is_some_and(|session| session.in_transaction()) + } }; - if err.is_pool_cleared() || op_retry { + if can_retry { retry = Some(ExecutionRetry { prior_txn_number: None, first_error: err, + first_server: server_addr.clone(), }); continue; } else { @@ -355,23 +387,28 @@ impl Client { session = implicit_session.as_mut(); } - let retryability = self.get_retryability(&conn, &op, &session)?; + let retryability = self.get_retryability(op, &session, conn.stream_description()?); if retryability == Retryability::None { retry.first_error()?; } - let txn_number = retry - .as_ref() - .and_then(|r| r.prior_txn_number) - .or_else(|| get_txn_number(&mut session, retryability)); + let txn_number = + if let Some(txn_number) = retry.as_ref().and_then(|r| r.prior_txn_number) { + Some(txn_number) + } else { + session + .as_mut() + .and_then(|s| s.get_txn_number_for_operation(retryability)) + }; let details = match self .execute_operation_on_connection( - &mut op, + op, &mut conn, &mut session, txn_number, retryability, + effective_criteria, ) .await { @@ -399,7 +436,7 @@ impl Client { self.inner .topology .handle_application_error( - server.address.clone(), + server_addr.clone(), err.clone(), HandshakePhase::after_completion(&conn), ) @@ -410,20 +447,20 @@ impl Client { drop(server); if let Some(r) = retry { - if err.is_server_error() + if (err.is_server_error() || err.is_read_retryable() - || err.is_write_retryable() + || err.is_write_retryable()) + && !err.contains_label("NoWritesPerformed") { return Err(err); } else { return Err(r.first_error); } - } else if retryability == Retryability::Read && err.is_read_retryable() - || retryability == Retryability::Write && err.is_write_retryable() - { + } else if retryability.can_retry_error(&err) { retry = Some(ExecutionRetry { prior_txn_number: txn_number, first_error: err, + first_server: server_addr.clone(), }); continue; } else { @@ -436,25 +473,190 @@ impl Client { } /// Executes an operation on a given connection, optionally using a provided session. - async fn execute_operation_on_connection( + pub(crate) async fn execute_operation_on_connection( &self, op: &mut T, - connection: &mut Connection, + connection: &mut PooledConnection, session: &mut Option<&mut ClientSession>, txn_number: Option, retryability: Retryability, + effective_criteria: SelectionCriteria, ) -> Result { - if let Some(wc) = op.write_concern() { - wc.validate()?; + loop { + let cmd = self.build_command( + op, + connection, + session, + txn_number, + effective_criteria.clone(), + )?; + + let connection_info = connection.info(); + let service_id = connection.service_id(); + let request_id = next_request_id(); + let should_redact = cmd.should_redact(); + let cmd_name = cmd.name.clone(); + let target_db = cmd.target_db.clone(); + + let mut message = Message::try_from(cmd)?; + message.request_id = Some(request_id); + #[cfg(feature = "in-use-encryption")] + { + let guard = self.inner.csfle.read().await; + if let Some(ref csfle) = *guard { + if csfle.opts().bypass_auto_encryption != Some(true) { + let encrypted_payload = self + .auto_encrypt(csfle, &message.document_payload, &target_db) + .await?; + message.document_payload = encrypted_payload; + } + } + } + + self.emit_command_event(|| { + let command_body = if should_redact { + Document::new() + } else { + message.get_command_document() + }; + CommandEvent::Started(CommandStartedEvent { + command: command_body, + db: target_db.clone(), + command_name: cmd_name.clone(), + request_id, + connection: connection_info.clone(), + service_id, + }) + }) + .await; + + let start_time = Instant::now(); + let command_result = match connection.send_message(message).await { + Ok(response) => { + let is_sharded = + connection.stream_description()?.initial_server_type == ServerType::Mongos; + self.parse_response(op, session, is_sharded, response).await + } + Err(err) => Err(err), + }; + + let duration = start_time.elapsed(); + + let result = match command_result { + Err(mut err) => { + self.emit_command_event(|| { + let mut err = err.clone(); + if should_redact { + err.redact(); + } + + CommandEvent::Failed(CommandFailedEvent { + duration, + command_name: cmd_name.clone(), + failure: err, + request_id, + connection: connection_info.clone(), + service_id, + }) + }) + .await; + + if let Some(ref mut session) = session { + if err.is_network_error() { + session.mark_dirty(); + } + } + + err.add_labels_and_update_pin( + Some(connection.stream_description()?), + session, + Some(retryability), + ); + + op.handle_error(err) + } + Ok(response) => { + self.emit_command_event(|| { + let reply = if should_redact { + Document::new() + } else { + response + .body() + .unwrap_or_else(|e| doc! { "deserialization error": e.to_string() }) + }; + + CommandEvent::Succeeded(CommandSucceededEvent { + duration, + reply, + command_name: cmd_name.clone(), + request_id, + connection: connection_info.clone(), + service_id, + }) + }) + .await; + + #[cfg(feature = "in-use-encryption")] + let response = { + let guard = self.inner.csfle.read().await; + if let Some(ref csfle) = *guard { + let new_body = self.auto_decrypt(csfle, response.raw_body()).await?; + RawCommandResponse::new_raw(response.source, new_body) + } else { + response + } + }; + + let context = ExecutionContext { + connection, + session: session.as_deref_mut(), + effective_criteria: effective_criteria.clone(), + }; + + match op.handle_response(response, context).await { + Ok(response) => Ok(response), + Err(mut err) => { + err.add_labels_and_update_pin( + Some(connection.stream_description()?), + session, + Some(retryability), + ); + Err(err) + } + } + } + }; + + if result + .as_ref() + .err() + .is_some_and(|e| e.is_reauthentication_required()) + { + // This retry is done outside of the normal retry loop because all operations, + // regardless of retryability, should be retried after reauthentication. + self.reauthenticate_connection(connection).await?; + continue; + } else { + return result; + } } + } + fn build_command( + &self, + op: &mut T, + connection: &mut PooledConnection, + session: &mut Option<&mut ClientSession>, + txn_number: Option, + effective_criteria: SelectionCriteria, + ) -> Result { let stream_description = connection.stream_description()?; let is_sharded = stream_description.initial_server_type == ServerType::Mongos; let mut cmd = op.build(stream_description)?; self.inner.topology.update_command_with_read_pref( connection.address(), &mut cmd, - op.selection_criteria(), + &effective_criteria, ); match session { @@ -486,10 +688,7 @@ impl Client { } // If this is a causally consistent session, set `readConcern.afterClusterTime`. // Causal consistency defaults to true, unless snapshot is true. - else if session - .options() - .and_then(|opts| opts.causal_consistency) - .unwrap_or(true) + else if session.causal_consistency() && matches!( session.transaction.state, TransactionState::None | TransactionState::Starting @@ -503,6 +702,9 @@ impl Client { TransactionState::Starting => { cmd.set_start_transaction(); cmd.set_autocommit(); + if session.causal_consistency() { + cmd.set_after_cluster_time(session); + } if let Some(ref options) = session.transaction.options { if let Some(ref read_concern) = options.read_concern { @@ -555,215 +757,14 @@ impl Client { cmd.set_cluster_time(cluster_time); } - let connection_info = connection.info(); - let service_id = connection.service_id(); - let request_id = crate::cmap::conn::next_request_id(); - if let Some(ref server_api) = self.inner.options.server_api { cmd.set_server_api(server_api); } - let should_redact = cmd.should_redact(); - - let cmd_name = cmd.name.clone(); - let target_db = cmd.target_db.clone(); - - let serialized = op.serialize_command(cmd)?; - #[cfg(feature = "in-use-encryption-unstable")] - let serialized = { - let guard = self.inner.csfle.read().await; - if let Some(ref csfle) = *guard { - if csfle.opts().bypass_auto_encryption != Some(true) { - self.auto_encrypt(csfle, RawDocument::from_bytes(&serialized)?, &target_db) - .await? - .into_bytes() - } else { - serialized - } - } else { - serialized - } - }; - let raw_cmd = RawCommand { - name: cmd_name.clone(), - target_db, - exhaust_allowed: false, - bytes: serialized, - }; - - self.emit_command_event(|| { - let command_body = if should_redact { - Document::new() - } else { - Document::from_reader(raw_cmd.bytes.as_slice()) - .unwrap_or_else(|e| doc! { "serialization error": e.to_string() }) - }; - CommandEvent::Started(CommandStartedEvent { - command: command_body, - db: raw_cmd.target_db.clone(), - command_name: raw_cmd.name.clone(), - request_id, - connection: connection_info.clone(), - service_id, - }) - }); - - let start_time = Instant::now(); - let command_result = match connection.send_raw_command(raw_cmd, request_id).await { - Ok(response) => { - async fn handle_response( - client: &Client, - op: &T, - session: &mut Option<&mut ClientSession>, - is_sharded: bool, - response: RawCommandResponse, - ) -> Result { - let raw_doc = RawDocument::from_bytes(response.as_bytes())?; - - let ok = match raw_doc.get("ok")? { - Some(b) => crate::bson_util::get_int_raw(b).ok_or_else(|| { - ErrorKind::InvalidResponse { - message: format!( - "expected ok value to be a number, instead got {:?}", - b - ), - } - })?, - None => { - return Err(ErrorKind::InvalidResponse { - message: "missing 'ok' value in response".to_string(), - } - .into()) - } - }; - - let cluster_time: Option = raw_doc - .get("$clusterTime")? - .and_then(RawBsonRef::as_document) - .map(|d| bson::from_slice(d.as_bytes())) - .transpose()?; - - let at_cluster_time = op.extract_at_cluster_time(raw_doc)?; - - client - .update_cluster_time(cluster_time, at_cluster_time, session) - .await; - - if let (Some(session), Some(ts)) = ( - session.as_mut(), - raw_doc - .get("operationTime")? - .and_then(RawBsonRef::as_timestamp), - ) { - session.advance_operation_time(ts); - } - - if ok == 1 { - if let Some(ref mut session) = session { - if is_sharded && session.in_transaction() { - let recovery_token = raw_doc - .get("recoveryToken")? - .and_then(RawBsonRef::as_document) - .map(|d| bson::from_slice(d.as_bytes())) - .transpose()?; - session.transaction.recovery_token = recovery_token; - } - } - - Ok(response) - } else { - Err(response - .body::() - .map(|error_response| error_response.into()) - .unwrap_or_else(|e| { - Error::from(ErrorKind::InvalidResponse { - message: format!("error deserializing command error: {}", e), - }) - })) - } - } - - handle_response(self, op, session, is_sharded, response).await - } - Err(err) => Err(err), - }; - - let duration = start_time.elapsed(); - - match command_result { - Err(mut err) => { - self.emit_command_event(|| { - let mut err = err.clone(); - if should_redact { - err.redact(); - } - - CommandEvent::Failed(CommandFailedEvent { - duration, - command_name: cmd_name.clone(), - failure: err, - request_id, - connection: connection_info.clone(), - service_id, - }) - }); - - if let Some(ref mut session) = session { - if err.is_network_error() { - session.mark_dirty(); - } - } - - err.add_labels_and_update_pin(Some(connection), session, Some(retryability))?; - op.handle_error(err) - } - Ok(response) => { - self.emit_command_event(|| { - let reply = if should_redact { - Document::new() - } else { - response - .body() - .unwrap_or_else(|e| doc! { "deserialization error": e.to_string() }) - }; - - CommandEvent::Succeeded(CommandSucceededEvent { - duration, - reply, - command_name: cmd_name.clone(), - request_id, - connection: connection_info.clone(), - service_id, - }) - }); - - #[cfg(feature = "in-use-encryption-unstable")] - let response = { - let guard = self.inner.csfle.read().await; - if let Some(ref csfle) = *guard { - let new_body = self.auto_decrypt(csfle, response.raw_body()).await?; - RawCommandResponse::new_raw(response.source, new_body) - } else { - response - } - }; - - match op.handle_response(response, connection.stream_description()?) { - Ok(response) => Ok(response), - Err(mut err) => { - err.add_labels_and_update_pin( - Some(connection), - session, - Some(retryability), - )?; - Err(err) - } - } - } - } + Ok(cmd) } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] fn auto_encrypt<'a>( &'a self, csfle: &'a super::csfle::ClientState, @@ -779,7 +780,7 @@ impl Client { }) } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] fn auto_decrypt<'a>( &'a self, csfle: &'a super::csfle::ClientState, @@ -791,6 +792,98 @@ impl Client { }) } + async fn reauthenticate_connection(&self, connection: &mut PooledConnection) -> Result<()> { + let credential = + self.inner + .options + .credential + .as_ref() + .ok_or_else(|| ErrorKind::Authentication { + message: "the connection requires reauthentication but no credential was set" + .to_string(), + })?; + let server_api = self.inner.options.server_api.as_ref(); + + credential + .mechanism + .as_ref() + .ok_or(ErrorKind::Authentication { + message: "the connection requires reauthentication but no authentication \ + mechanism was set" + .to_string(), + })? + .reauthenticate_stream(connection, credential, server_api) + .await + } + + async fn parse_response( + &self, + op: &T, + session: &mut Option<&mut ClientSession>, + is_sharded: bool, + response: RawCommandResponse, + ) -> Result { + let raw_doc = RawDocument::decode_from_bytes(response.as_bytes())?; + + let ok = match raw_doc.get("ok")? { + Some(b) => { + crate::bson_util::get_int_raw(b).ok_or_else(|| ErrorKind::InvalidResponse { + message: format!("expected ok value to be a number, instead got {:?}", b), + })? + } + None => { + return Err(ErrorKind::InvalidResponse { + message: "missing 'ok' value in response".to_string(), + } + .into()) + } + }; + + let cluster_time: Option = raw_doc + .get("$clusterTime")? + .and_then(RawBsonRef::as_document) + .map(|d| crate::bson_compat::deserialize_from_slice(d.as_bytes())) + .transpose()?; + + let at_cluster_time = op.extract_at_cluster_time(raw_doc)?; + + self.update_cluster_time(cluster_time, at_cluster_time, session) + .await; + + if let (Some(session), Some(ts)) = ( + session.as_mut(), + raw_doc + .get("operationTime")? + .and_then(RawBsonRef::as_timestamp), + ) { + session.advance_operation_time(ts); + } + + if ok == 1 { + if let Some(ref mut session) = session { + if is_sharded && session.in_transaction() { + let recovery_token = raw_doc + .get("recoveryToken")? + .and_then(RawBsonRef::as_document) + .map(|d| crate::bson_compat::deserialize_from_slice(d.as_bytes())) + .transpose()?; + session.transaction.recovery_token = recovery_token; + } + } + + Ok(response) + } else { + Err(response + .body::() + .map(|error_response| error_response.into()) + .unwrap_or_else(|e| { + Error::from(ErrorKind::InvalidResponse { + message: format!("error deserializing command error: {}", e), + }) + })) + } + } + async fn select_data_bearing_server(&self, operation_name: &str) -> Result<()> { let topology_type = self.inner.topology.topology_type(); let criteria = SelectionCriteria::Predicate(Arc::new(move |server_info| { @@ -798,7 +891,9 @@ impl Client { (matches!(topology_type, TopologyType::Single) && server_type.is_available()) || server_type.is_data_bearing() })); - let _: SelectedServer = self.select_server(Some(&criteria), operation_name).await?; + let _ = self + .select_server(Some(&criteria), operation_name, None, |_, _| None) + .await?; Ok(()) } @@ -820,52 +915,37 @@ impl Client { } } - /// Returns the retryability level for the execution of this operation. - fn get_op_retryability( + /// Returns the retryability level for the execution of this operation with the given session + /// and connection stream description. + fn get_retryability( &self, op: &T, session: &Option<&mut ClientSession>, + stream_description: &StreamDescription, ) -> Retryability { + // commitTransaction and abortTransaction are always retried, regardless of the value of + // retry_writes. + if op.name() == CommitTransaction::NAME || op.name() == AbortTransaction::NAME { + return Retryability::Write; + } + if session .as_ref() - .map(|session| session.in_transaction()) - .unwrap_or(false) + .is_some_and(|session| session.in_transaction()) { return Retryability::None; } - match op.retryability() { - Retryability::Read if self.inner.options.retry_reads != Some(false) => { - Retryability::Read - } - // commitTransaction and abortTransaction should be retried regardless of the - // value for retry_writes set on the Client - Retryability::Write - if op.name() == CommitTransaction::NAME - || op.name() == AbortTransaction::NAME - || self.inner.options.retry_writes != Some(false) => - { + + match op.retryability().with_options(self.options()) { + Retryability::Write if stream_description.supports_retryable_writes() => { Retryability::Write } + // All servers compatible with the driver support retryable reads. + Retryability::Read => Retryability::Read, _ => Retryability::None, } } - /// Returns the retryability level for the execution of this operation on this connection. - fn get_retryability( - &self, - conn: &Connection, - op: &T, - session: &Option<&mut ClientSession>, - ) -> Result { - match self.get_op_retryability(op, session) { - Retryability::Read => Ok(Retryability::Read), - Retryability::Write if conn.stream_description()?.supports_retryable_writes() => { - Ok(Retryability::Write) - } - _ => Ok(Retryability::None), - } - } - async fn update_cluster_time( &self, cluster_time: Option, @@ -894,7 +974,7 @@ async fn get_connection( session: &Option<&mut ClientSession>, op: &T, pool: &ConnectionPool, -) -> Result { +) -> Result { let session_pinned = session .as_ref() .and_then(|s| s.transaction.pinned_connection()); @@ -909,25 +989,6 @@ async fn get_connection( } } -fn get_txn_number( - session: &mut Option<&mut ClientSession>, - retryability: Retryability, -) -> Option { - match session { - Some(ref mut session) => { - if session.transaction.state != TransactionState::None { - Some(session.txn_number()) - } else { - match retryability { - Retryability::Write => Some(session.get_and_increment_txn_number()), - _ => None, - } - } - } - None => None, - } -} - impl Error { /// Adds the necessary labels to this Error, and unpins the session if needed. /// @@ -944,18 +1005,16 @@ impl Error { /// ClientSession should be unpinned. fn add_labels_and_update_pin( &mut self, - conn: Option<&Connection>, + stream_description: Option<&StreamDescription>, session: &mut Option<&mut ClientSession>, retryability: Option, - ) -> Result<()> { + ) { let transaction_state = session.as_ref().map_or(&TransactionState::None, |session| { &session.transaction.state }); - let max_wire_version = if let Some(conn) = conn { - conn.stream_description()?.max_wire_version - } else { - None - }; + let max_wire_version = stream_description.and_then(|sd| sd.max_wire_version); + let server_type = stream_description.map(|sd| sd.initial_server_type); + match transaction_state { TransactionState::Starting | TransactionState::InProgress => { if self.is_network_error() || self.is_server_selection_error() { @@ -964,7 +1023,7 @@ impl Error { } TransactionState::Committed { .. } => { if let Some(max_wire_version) = max_wire_version { - if self.should_add_retryable_write_label(max_wire_version) { + if self.should_add_retryable_write_label(max_wire_version, server_type) { self.add_label(RETRYABLE_WRITE_ERROR); } } @@ -974,7 +1033,7 @@ impl Error { } TransactionState::Aborted => { if let Some(max_wire_version) = max_wire_version { - if self.should_add_retryable_write_label(max_wire_version) { + if self.should_add_retryable_write_label(max_wire_version, server_type) { self.add_label(RETRYABLE_WRITE_ERROR); } } @@ -982,7 +1041,7 @@ impl Error { TransactionState::None => { if retryability == Some(Retryability::Write) { if let Some(max_wire_version) = max_wire_version { - if self.should_add_retryable_write_label(max_wire_version) { + if self.should_add_retryable_write_label(max_wire_version, server_type) { self.add_label(RETRYABLE_WRITE_ERROR); } } @@ -997,20 +1056,20 @@ impl Error { session.unpin(); } } - - Ok(()) } } struct ExecutionDetails { output: T::O, - connection: Connection, + connection: PooledConnection, implicit_session: Option, } +#[derive(Debug)] struct ExecutionRetry { prior_txn_number: Option, first_error: Error, + first_server: ServerAddress, } trait RetryHelper { diff --git a/src/client/mod.rs b/src/client/mod.rs deleted file mode 100644 index a54e1d8c2..000000000 --- a/src/client/mod.rs +++ /dev/null @@ -1,626 +0,0 @@ -pub mod auth; -#[cfg(feature = "in-use-encryption-unstable")] -pub(crate) mod csfle; -mod executor; -pub mod options; -pub mod session; - -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; - -#[cfg(feature = "in-use-encryption-unstable")] -pub use self::csfle::client_builder::*; -use derivative::Derivative; - -#[cfg(test)] -use crate::options::ServerAddress; -#[cfg(feature = "tracing-unstable")] -use crate::trace::{ - command::CommandTracingEventEmitter, - server_selection::ServerSelectionTracingEventEmitter, - trace_or_log_enabled, - TracingOrLogLevel, - COMMAND_TRACING_EVENT_TARGET, -}; -use crate::{ - bson::Document, - change_stream::{ - event::ChangeStreamEvent, - options::ChangeStreamOptions, - session::SessionChangeStream, - ChangeStream, - }, - concern::{ReadConcern, WriteConcern}, - db::Database, - error::{Error, ErrorKind, Result}, - event::command::{handle_command_event, CommandEvent}, - operation::{AggregateTarget, ListDatabases}, - options::{ - ClientOptions, - DatabaseOptions, - ListDatabasesOptions, - ReadPreference, - SelectionCriteria, - SessionOptions, - }, - results::DatabaseSpecification, - sdam::{server_selection, SelectedServer, Topology}, - ClientSession, -}; - -pub(crate) use executor::{HELLO_COMMAND_NAMES, REDACTED_COMMANDS}; -pub(crate) use session::{ClusterTime, SESSIONS_UNSUPPORTED_COMMANDS}; - -use session::{ServerSession, ServerSessionPool}; - -const DEFAULT_SERVER_SELECTION_TIMEOUT: Duration = Duration::from_secs(30); - -/// This is the main entry point for the API. A `Client` is used to connect to a MongoDB cluster. -/// By default, it will monitor the topology of the cluster, keeping track of any changes, such -/// as servers being added or removed. -/// -/// `Client` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, -/// so it can safely be shared across threads or async tasks. For example: -/// -/// ```rust -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # use mongodb::{bson::Document, Client, error::Result}; -/// # #[cfg(feature = "async-std-runtime")] -/// # use async_std::task; -/// # #[cfg(feature = "tokio-runtime")] -/// # use tokio::task; -/// # -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # async fn start_workers() -> Result<()> { -/// let client = Client::with_uri_str("mongodb://example.com").await?; -/// -/// for i in 0..5 { -/// let client_ref = client.clone(); -/// -/// task::spawn(async move { -/// let collection = client_ref.database("items").collection::(&format!("coll{}", i)); -/// -/// // Do something with the collection -/// }); -/// } -/// # -/// # Ok(()) -/// # } -/// ``` -/// ## Notes on performance -/// Spawning many asynchronous tasks that use the driver concurrently like this is often the best -/// way to achieve maximum performance, as the driver is designed to work well in such situations. -/// -/// Additionally, using a custom Rust type that implements `Serialize` and `Deserialize` as the -/// generic parameter of [`Collection`](../struct.Collection.html) instead of [`bson::Document`] can -/// reduce the amount of time the driver and your application spends serializing and deserializing -/// BSON, which can also lead to increased performance. -/// -/// ## TCP Keepalive -/// TCP keepalive is enabled by default with ``tcp_keepalive_time`` set to 120 seconds. The -/// driver does not set ``tcp_keepalive_intvl``. See the -/// [MongoDB Diagnostics FAQ keepalive section](https://www.mongodb.com/docs/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments) -/// for instructions on setting these values at the system level. -#[derive(Clone, Debug)] -pub struct Client { - inner: Arc, -} - -#[derive(Derivative)] -#[derivative(Debug)] -struct ClientInner { - topology: Topology, - options: ClientOptions, - session_pool: ServerSessionPool, - #[cfg(feature = "in-use-encryption-unstable")] - csfle: tokio::sync::RwLock>, -} - -impl Client { - /// Creates a new `Client` connected to the cluster specified by `uri`. `uri` must be a valid - /// MongoDB connection string. - /// - /// See the documentation on - /// [`ClientOptions::parse`](options/struct.ClientOptions.html#method.parse) for more details. - pub async fn with_uri_str(uri: impl AsRef) -> Result { - let options = ClientOptions::parse_uri(uri.as_ref(), None).await?; - - Client::with_options(options) - } - - /// Creates a new `Client` connected to the cluster specified by `options`. - pub fn with_options(options: ClientOptions) -> Result { - options.validate()?; - - let inner = Arc::new(ClientInner { - topology: Topology::new(options.clone())?, - session_pool: ServerSessionPool::new(), - #[cfg(feature = "in-use-encryption-unstable")] - csfle: Default::default(), - options, - }); - Ok(Self { inner }) - } - - /// Return an `EncryptedClientBuilder` for constructing a `Client` with auto-encryption enabled. - /// - /// ```no_run - /// # use bson::doc; - /// # use mongocrypt::ctx::KmsProvider; - /// # use mongodb::Client; - /// # use mongodb::error::Result; - /// # async fn func() -> Result<()> { - /// # let client_options = todo!(); - /// # let key_vault_namespace = todo!(); - /// # let key_vault_client: Client = todo!(); - /// # let local_key: bson::Binary = todo!(); - /// let encrypted_client = Client::encrypted_builder( - /// client_options, - /// key_vault_namespace, - /// [(KmsProvider::Local, doc! { "key": local_key }, None)], - /// )? - /// .key_vault_client(key_vault_client) - /// .build() - /// .await?; - /// # Ok(()) - /// # } - /// ``` - #[cfg(feature = "in-use-encryption-unstable")] - pub fn encrypted_builder( - client_options: ClientOptions, - key_vault_namespace: crate::Namespace, - kms_providers: impl IntoIterator< - Item = ( - mongocrypt::ctx::KmsProvider, - bson::Document, - Option, - ), - >, - ) -> Result { - Ok(EncryptedClientBuilder::new( - client_options, - csfle::options::AutoEncryptionOptions::new( - key_vault_namespace, - csfle::options::KmsProviders::new(kms_providers)?, - ), - )) - } - - #[cfg(all(test, feature = "in-use-encryption-unstable"))] - pub(crate) async fn mongocryptd_spawned(&self) -> bool { - self.inner - .csfle - .read() - .await - .as_ref() - .map_or(false, |cs| cs.exec().mongocryptd_spawned()) - } - - #[cfg(all(test, feature = "in-use-encryption-unstable"))] - pub(crate) async fn has_mongocryptd_client(&self) -> bool { - self.inner - .csfle - .read() - .await - .as_ref() - .map_or(false, |cs| cs.exec().has_mongocryptd_client()) - } - - #[cfg(not(feature = "tracing-unstable"))] - pub(crate) fn emit_command_event(&self, generate_event: impl FnOnce() -> CommandEvent) { - if let Some(ref handler) = self.inner.options.command_event_handler { - let event = generate_event(); - handle_command_event(handler.as_ref(), event); - } - } - - #[cfg(feature = "tracing-unstable")] - pub(crate) fn emit_command_event(&self, generate_event: impl FnOnce() -> CommandEvent) { - let tracing_emitter = if trace_or_log_enabled!( - target: COMMAND_TRACING_EVENT_TARGET, - TracingOrLogLevel::Debug - ) { - Some(CommandTracingEventEmitter::new( - self.inner.options.tracing_max_document_length_bytes, - self.inner.topology.id, - )) - } else { - None - }; - let apm_event_handler = self.inner.options.command_event_handler.as_ref(); - if !(tracing_emitter.is_some() || apm_event_handler.is_some()) { - return; - } - - let event = generate_event(); - if let (Some(event_handler), Some(ref tracing_emitter)) = - (apm_event_handler, &tracing_emitter) - { - handle_command_event(event_handler.as_ref(), event.clone()); - handle_command_event(tracing_emitter, event); - } else if let Some(event_handler) = apm_event_handler { - handle_command_event(event_handler.as_ref(), event); - } else if let Some(ref tracing_emitter) = tracing_emitter { - handle_command_event(tracing_emitter, event); - } - } - - /// Gets the default selection criteria the `Client` uses for operations.. - pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.inner.options.selection_criteria.as_ref() - } - - /// Gets the default read concern the `Client` uses for operations. - pub fn read_concern(&self) -> Option<&ReadConcern> { - self.inner.options.read_concern.as_ref() - } - - /// Gets the default write concern the `Client` uses for operations. - pub fn write_concern(&self) -> Option<&WriteConcern> { - self.inner.options.write_concern.as_ref() - } - - /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. - /// The `Database` options (e.g. read preference and write concern) will default to those of the - /// `Client`. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn database(&self, name: &str) -> Database { - Database::new(self.clone(), name, None) - } - - /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. - /// Operations done with this `Database` will use the options specified by `options` by default - /// and will otherwise default to those of the `Client`. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn database_with_options(&self, name: &str, options: DatabaseOptions) -> Database { - Database::new(self.clone(), name, Some(options)) - } - - /// Gets a handle to the default database specified in the `ClientOptions` or MongoDB connection - /// string used to construct this `Client`. - /// - /// If no default database was specified, `None` will be returned. - pub fn default_database(&self) -> Option { - self.inner - .options - .default_database - .as_ref() - .map(|db_name| self.database(db_name)) - } - - async fn list_databases_common( - &self, - filter: impl Into>, - options: impl Into>, - session: Option<&mut ClientSession>, - ) -> Result> { - let op = ListDatabases::new(filter.into(), false, options.into()); - self.execute_operation(op, session).await.and_then(|dbs| { - dbs.into_iter() - .map(|db_spec| { - bson::from_slice(db_spec.as_bytes()).map_err(crate::error::Error::from) - }) - .collect() - }) - } - - /// Gets information about each database present in the cluster the Client is connected to. - pub async fn list_databases( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - self.list_databases_common(filter, options, None).await - } - - /// Gets information about each database present in the cluster the Client is connected to - /// using the provided `ClientSession`. - pub async fn list_databases_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - self.list_databases_common(filter, options, Some(session)) - .await - } - - /// Gets the names of the databases present in the cluster the Client is connected to. - pub async fn list_database_names( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - let op = ListDatabases::new(filter.into(), true, options.into()); - match self.execute_operation(op, None).await { - Ok(databases) => databases - .into_iter() - .map(|doc| { - let name = doc - .get_str("name") - .map_err(|_| ErrorKind::InvalidResponse { - message: "Expected \"name\" field in server response, but it was not \ - found" - .to_string(), - })?; - Ok(name.to_string()) - }) - .collect(), - Err(e) => Err(e), - } - } - - /// Starts a new `ClientSession`. - pub async fn start_session( - &self, - options: impl Into>, - ) -> Result { - let options = options.into(); - if let Some(ref options) = options { - options.validate()?; - } - Ok(ClientSession::new(self.clone(), options, false).await) - } - - /// Starts a new [`ChangeStream`] that receives events for all changes in the cluster. The - /// stream does not observe changes from system collections or the "config", "local" or - /// "admin" databases. Note that this method (`watch` on a cluster) is only supported in - /// MongoDB 4.0 or greater. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read - /// concern. Anything else will cause a server error. - /// - /// Note that using a `$project` stage to remove any of the `_id` `operationType` or `ns` fields - /// will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub async fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - options - .get_or_insert_with(Default::default) - .all_changes_for_cluster = Some(true); - let target = AggregateTarget::Database("admin".to_string()); - self.execute_watch(pipeline, options, target, None).await - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in the cluster - /// using the provided [`ClientSession`]. See [`Client::watch`] for more information. - pub async fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - options - .get_or_insert_with(Default::default) - .all_changes_for_cluster = Some(true); - let target = AggregateTarget::Database("admin".to_string()); - self.execute_watch_with_session(pipeline, options, target, None, session) - .await - } - - /// Check in a server session to the server session pool. The session will be discarded if it is - /// expired or dirty. - pub(crate) async fn check_in_server_session(&self, session: ServerSession) { - let timeout = self.inner.topology.logical_session_timeout(); - self.inner.session_pool.check_in(session, timeout).await; - } - - #[cfg(test)] - pub(crate) async fn clear_session_pool(&self) { - self.inner.session_pool.clear().await; - } - - #[cfg(test)] - pub(crate) async fn is_session_checked_in(&self, id: &Document) -> bool { - self.inner.session_pool.contains(id).await - } - - /// Get the address of the server selected according to the given criteria. - /// This method is only used in tests. - #[cfg(test)] - pub(crate) async fn test_select_server( - &self, - criteria: Option<&SelectionCriteria>, - ) -> Result { - let server = self.select_server(criteria, "Test select server").await?; - Ok(server.address.clone()) - } - - /// Select a server using the provided criteria. If none is provided, a primary read preference - /// will be used instead. - #[allow(unused_variables)] // we only use the operation_name for tracing. - async fn select_server( - &self, - criteria: Option<&SelectionCriteria>, - operation_name: &str, - ) -> Result { - let criteria = - criteria.unwrap_or(&SelectionCriteria::ReadPreference(ReadPreference::Primary)); - - let start_time = Instant::now(); - let timeout = self - .inner - .options - .server_selection_timeout - .unwrap_or(DEFAULT_SERVER_SELECTION_TIMEOUT); - - #[cfg(feature = "tracing-unstable")] - let event_emitter = ServerSelectionTracingEventEmitter::new( - self.inner.topology.id, - criteria, - operation_name, - start_time, - timeout, - ); - #[cfg(feature = "tracing-unstable")] - event_emitter.emit_started_event(self.inner.topology.watch().observe_latest().description); - // We only want to emit this message once per operation at most. - #[cfg(feature = "tracing-unstable")] - let mut emitted_waiting_message = false; - - let mut watcher = self.inner.topology.watch(); - loop { - let state = watcher.observe_latest(); - - let result = server_selection::attempt_to_select_server( - criteria, - &state.description, - &state.servers(), - ); - match result { - Err(error) => { - #[cfg(feature = "tracing-unstable")] - event_emitter.emit_failed_event(&state.description, &error); - - return Err(error); - } - Ok(result) => { - if let Some(server) = result { - #[cfg(feature = "tracing-unstable")] - event_emitter.emit_succeeded_event(&state.description, &server); - - return Ok(server); - } else { - #[cfg(feature = "tracing-unstable")] - if !emitted_waiting_message { - event_emitter.emit_waiting_event(&state.description); - emitted_waiting_message = true; - } - - watcher.request_immediate_check(); - - let change_occurred = start_time.elapsed() < timeout - && watcher - .wait_for_update(timeout - start_time.elapsed()) - .await; - if !change_occurred { - let error: Error = ErrorKind::ServerSelection { - message: state - .description - .server_selection_timeout_error_message(criteria), - } - .into(); - - #[cfg(feature = "tracing-unstable")] - event_emitter.emit_failed_event(&state.description, &error); - - return Err(error); - } - } - } - } - } - } - - #[cfg(all(test, not(feature = "sync"), not(feature = "tokio-sync")))] - pub(crate) fn get_hosts(&self) -> Vec { - let watcher = self.inner.topology.watch(); - let state = watcher.peek_latest(); - - state - .servers() - .keys() - .map(|stream_address| format!("{}", stream_address)) - .collect() - } - - #[cfg(test)] - pub(crate) async fn sync_workers(&self) { - self.inner.topology.sync_workers().await; - } - - #[cfg(test)] - pub(crate) fn topology_description(&self) -> crate::sdam::TopologyDescription { - self.inner - .topology - .watch() - .peek_latest() - .description - .clone() - } - - #[cfg(test)] - pub(crate) fn topology(&self) -> &Topology { - &self.inner.topology - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) async fn primary_description(&self) -> Option { - let start_time = Instant::now(); - let timeout = self - .inner - .options - .server_selection_timeout - .unwrap_or(DEFAULT_SERVER_SELECTION_TIMEOUT); - let mut watcher = self.inner.topology.watch(); - loop { - let topology = watcher.observe_latest(); - if let Some(desc) = topology.description.primary() { - return Some(desc.clone()); - } - if !watcher - .wait_for_update(timeout - start_time.elapsed()) - .await - { - return None; - } - } - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) fn weak(&self) -> WeakClient { - WeakClient { - inner: Arc::downgrade(&self.inner), - } - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) async fn auto_encryption_opts( - &self, - ) -> Option> { - tokio::sync::RwLockReadGuard::try_map(self.inner.csfle.read().await, |csfle| { - csfle.as_ref().map(|cs| cs.opts()) - }) - .ok() - } - - #[cfg(test)] - pub(crate) fn options(&self) -> &ClientOptions { - &self.inner.options - } -} - -#[cfg(feature = "in-use-encryption-unstable")] -#[derive(Clone, Debug)] -pub(crate) struct WeakClient { - inner: std::sync::Weak, -} - -#[cfg(feature = "in-use-encryption-unstable")] -impl WeakClient { - #[allow(dead_code)] - pub(crate) fn upgrade(&self) -> Option { - self.inner.upgrade().map(|inner| Client { inner }) - } -} diff --git a/src/client/options.rs b/src/client/options.rs new file mode 100644 index 000000000..aea136115 --- /dev/null +++ b/src/client/options.rs @@ -0,0 +1,2740 @@ +#[cfg(test)] +mod test; + +mod bulk_write; +mod parse; +mod resolver_config; + +use std::{ + cmp::Ordering, + collections::HashSet, + convert::TryFrom, + fmt::{self, Display, Formatter, Write}, + hash::{Hash, Hasher}, + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + path::PathBuf, + str::FromStr, + time::Duration, +}; + +use crate::bson::UuidRepresentation; +use derive_where::derive_where; +use macro_magic::export_tokens; +use once_cell::sync::Lazy; +use serde::{de::Unexpected, Deserialize, Deserializer, Serialize}; +use serde_with::skip_serializing_none; +use strsim::jaro_winkler; +use typed_builder::TypedBuilder; + +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +use crate::options::Compressor; +#[cfg(test)] +use crate::srv::LookupHosts; +use crate::{ + bson::{doc, Bson, Document}, + client::auth::{AuthMechanism, Credential}, + concern::{Acknowledgment, ReadConcern, WriteConcern}, + error::{Error, ErrorKind, Result}, + event::EventHandler, + options::ReadConcernLevel, + sdam::{verify_max_staleness, DEFAULT_HEARTBEAT_FREQUENCY, MIN_HEARTBEAT_FREQUENCY}, + selection_criteria::{ReadPreference, SelectionCriteria, TagSet}, + serde_util::{self, write_concern_is_empty}, + srv::{OriginalSrvInfo, SrvResolver}, +}; + +pub use bulk_write::*; +#[cfg(feature = "dns-resolver")] +pub use resolver_config::ResolverConfig; +#[cfg(not(feature = "dns-resolver"))] +pub(crate) use resolver_config::ResolverConfig; + +pub(crate) const DEFAULT_PORT: u16 = 27017; + +const URI_OPTIONS: &[&str] = &[ + "appname", + "authmechanism", + "authsource", + "authmechanismproperties", + "compressors", + "connecttimeoutms", + "directconnection", + "heartbeatfrequencyms", + "journal", + "localthresholdms", + "maxidletimems", + "maxstalenessseconds", + "maxpoolsize", + "minpoolsize", + "maxconnecting", + "readconcernlevel", + "readpreference", + "readpreferencetags", + "replicaset", + "retrywrites", + "retryreads", + "servermonitoringmode", + "serverselectiontimeoutms", + "sockettimeoutms", + "tls", + "ssl", + "tlsinsecure", + "tlsallowinvalidcertificates", + "tlscafile", + "tlscertificatekeyfile", + "uuidRepresentation", + "w", + "waitqueuetimeoutms", + "wtimeoutms", + "zlibcompressionlevel", + "srvservicename", +]; + +/// Reserved characters as defined by [Section 2.2 of RFC-3986](https://tools.ietf.org/html/rfc3986#section-2.2). +/// Usernames / passwords that contain these characters must instead include the URL encoded version +/// of them when included as part of the connection string. +static USERINFO_RESERVED_CHARACTERS: Lazy> = + Lazy::new(|| [':', '/', '?', '#', '[', ']', '@'].iter().collect()); + +static ILLEGAL_DATABASE_CHARACTERS: Lazy> = + Lazy::new(|| ['/', '\\', ' ', '"', '$'].iter().collect()); + +/// An enum representing the address of a MongoDB server. +#[derive(Clone, Debug, Eq, Serialize)] +#[non_exhaustive] +pub enum ServerAddress { + /// A TCP/IP host and port combination. + Tcp { + /// The hostname or IP address where the MongoDB server can be found. + host: String, + + /// The TCP port that the MongoDB server is listening on. + /// + /// The default is 27017. + port: Option, + }, + /// A Unix Domain Socket path. + #[cfg(unix)] + Unix { + /// The path to the Unix Domain Socket. + path: PathBuf, + }, +} + +impl From for ServerAddress { + fn from(item: SocketAddr) -> Self { + ServerAddress::Tcp { + host: item.ip().to_string(), + port: Some(item.port()), + } + } +} + +impl<'de> Deserialize<'de> for ServerAddress { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum ServerAddressHelper { + String(String), + Object { host: String, port: Option }, + } + + let helper = ServerAddressHelper::deserialize(deserializer)?; + match helper { + ServerAddressHelper::String(string) => { + Self::parse(string).map_err(serde::de::Error::custom) + } + ServerAddressHelper::Object { host, port } => { + #[cfg(unix)] + if host.ends_with("sock") { + return Ok(Self::Unix { + path: PathBuf::from(host), + }); + } + + Ok(Self::Tcp { host, port }) + } + } + } +} + +impl Default for ServerAddress { + fn default() -> Self { + Self::Tcp { + host: "localhost".into(), + port: None, + } + } +} + +impl PartialEq for ServerAddress { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::Tcp { host, port }, + Self::Tcp { + host: other_host, + port: other_port, + }, + ) => host == other_host && port.unwrap_or(27017) == other_port.unwrap_or(27017), + #[cfg(unix)] + (Self::Unix { path }, Self::Unix { path: other_path }) => path == other_path, + #[cfg(unix)] + _ => false, + } + } +} + +impl Hash for ServerAddress { + fn hash(&self, state: &mut H) + where + H: Hasher, + { + match self { + Self::Tcp { host, port } => { + host.hash(state); + port.unwrap_or(27017).hash(state); + } + #[cfg(unix)] + Self::Unix { path } => path.hash(state), + } + } +} + +impl FromStr for ServerAddress { + type Err = Error; + fn from_str(address: &str) -> Result { + ServerAddress::parse(address) + } +} + +impl ServerAddress { + /// Parses an address string into a [`ServerAddress`]. + pub fn parse(address: impl AsRef) -> Result { + let address = address.as_ref(); + + if address.ends_with(".sock") { + #[cfg(unix)] + { + let address = percent_decode(address, "unix domain sockets must be URL-encoded")?; + return Ok(Self::Unix { + path: PathBuf::from(address), + }); + } + #[cfg(not(unix))] + return Err(ErrorKind::InvalidArgument { + message: "unix domain sockets are not supported on this platform".to_string(), + } + .into()); + } + + let (hostname, port) = if let Some(ip_literal) = address.strip_prefix("[") { + let Some((hostname, port)) = ip_literal.split_once("]") else { + return Err(ErrorKind::InvalidArgument { + message: format!( + "invalid server address {}: missing closing ']' in IP literal hostname", + address + ), + } + .into()); + }; + + if let Err(parse_error) = Ipv6Addr::from_str(hostname) { + return Err(ErrorKind::InvalidArgument { + message: format!("invalid server address {}: {}", address, parse_error), + } + .into()); + } + + let port = if port.is_empty() { + None + } else if let Some(port) = port.strip_prefix(":") { + Some(port) + } else { + return Err(ErrorKind::InvalidArgument { + message: format!( + "invalid server address {}: the hostname can only be followed by a port \ + prefixed with ':', got {}", + address, port + ), + } + .into()); + }; + + (hostname, port) + } else { + match address.split_once(":") { + Some((hostname, port)) => (hostname, Some(port)), + None => (address, None), + } + }; + + if hostname.is_empty() { + return Err(ErrorKind::InvalidArgument { + message: format!( + "invalid server address {}: the hostname cannot be empty", + address + ), + } + .into()); + } + + let normalized_hostname = if let Ok(v4) = hostname.parse::() { + v4.to_string() + } else if let Ok(v6) = hostname.parse::() { + v6.to_string() + } else { + hostname.to_lowercase() + }; + + let port = if let Some(port) = port { + match u16::from_str(port) { + Ok(0) | Err(_) => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "invalid server address {}: the port must be an integer between 1 and \ + 65535, got {}", + address, port + ), + } + .into()) + } + Ok(port) => Some(port), + } + } else { + None + }; + + Ok(Self::Tcp { + host: normalized_hostname, + port, + }) + } + + #[cfg(feature = "dns-resolver")] + pub(crate) fn host(&self) -> std::borrow::Cow<'_, str> { + match self { + Self::Tcp { host, .. } => std::borrow::Cow::Borrowed(host.as_str()), + #[cfg(unix)] + Self::Unix { path } => path.to_string_lossy(), + } + } +} + +impl fmt::Display for ServerAddress { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Tcp { host, port } => { + write!(fmt, "{}:{}", host, port.unwrap_or(DEFAULT_PORT)) + } + #[cfg(unix)] + Self::Unix { path } => write!(fmt, "{}", path.display()), + } + } +} + +/// Specifies the server API version to declare +#[derive(Clone, Debug, PartialEq, Serialize)] +#[non_exhaustive] +pub enum ServerApiVersion { + /// Use API version 1. + #[serde(rename = "1")] + V1, +} + +impl FromStr for ServerApiVersion { + type Err = Error; + + fn from_str(str: &str) -> Result { + match str { + "1" => Ok(Self::V1), + _ => Err(ErrorKind::InvalidArgument { + message: format!("invalid server api version string: {}", str), + } + .into()), + } + } +} + +impl Display for ServerApiVersion { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::V1 => write!(f, "1"), + } + } +} + +impl<'de> Deserialize<'de> for ServerApiVersion { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + + ServerApiVersion::from_str(&s).map_err(|_| { + serde::de::Error::invalid_value(Unexpected::Str(&s), &"a valid version number") + }) + } +} + +/// Options used to declare a stable server API. For more information, see the [Stable API]( +/// https://www.mongodb.com/docs/v5.0/reference/stable-api/) manual page. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct ServerApi { + /// The declared API version. + #[serde(rename = "apiVersion")] + #[builder(!default)] + pub version: ServerApiVersion, + + /// Whether the MongoDB server should reject all commands that are not part of the + /// declared API version. This includes command options and aggregation pipeline stages. + #[serde(rename = "apiStrict")] + pub strict: Option, + + /// Whether the MongoDB server should return command failures when functionality that is + /// deprecated from the declared API version is used. + /// Note that at the time of this writing, no deprecations in version 1 exist. + #[serde(rename = "apiDeprecationErrors")] + pub deprecation_errors: Option, +} + +/// Contains the options that can be used to create a new [`Client`](../struct.Client.html). +#[derive(Clone, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[derive_where(Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ClientOptions { + /// The initial list of seeds that the Client should connect to. + /// + /// Note that by default, the driver will autodiscover other nodes in the cluster. To connect + /// directly to a single server (rather than autodiscovering the rest of the cluster), set the + /// `direct_connection` field to `true`. + #[builder(default_code = "vec![ServerAddress::Tcp { + host: \"localhost\".to_string(), + port: Some(27017), + }]")] + #[serde(default = "default_hosts")] + pub hosts: Vec, + + /// The application name that the Client will send to the server as part of the handshake. This + /// can be used in combination with the server logs to determine which Client is connected to a + /// server. + pub app_name: Option, + + /// The allowed compressors to use to compress messages sent to and decompress messages + /// received from the server. This list should be specified in priority order, as the + /// compressor used for messages will be the first compressor in this list that is also + /// supported by the server selected for operations. + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + #[serde(skip)] + pub compressors: Option>, + + /// The handler that should process all Connection Monitoring and Pooling events. + #[derive_where(skip)] + #[builder(setter(strip_option))] + #[serde(skip)] + pub cmap_event_handler: Option>, + + /// The handler that should process all command-related events. + /// + /// Note that monitoring command events may incur a performance penalty. + #[derive_where(skip)] + #[builder(setter(strip_option))] + #[serde(skip)] + pub command_event_handler: Option>, + + /// The connect timeout passed to each underlying TcpStream when attemtping to connect to the + /// server. + /// + /// The default value is 10 seconds. + pub connect_timeout: Option, + + /// The credential to use for authenticating connections made by this client. + pub credential: Option, + + /// Specifies whether the Client should directly connect to a single host rather than + /// autodiscover all servers in the cluster. + /// + /// The default value is false. + pub direct_connection: Option, + + /// Extra information to append to the driver version in the metadata of the handshake with the + /// server. This should be used by libraries wrapping the driver, e.g. ODMs. + pub driver_info: Option, + + /// The amount of time each monitoring thread should wait between performing server checks. + /// + /// The default value is 10 seconds. + pub heartbeat_freq: Option, + + /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. + #[builder(setter(skip))] + #[serde(rename = "loadbalanced")] + pub load_balanced: Option, + + /// When running a read operation with a ReadPreference that allows selecting secondaries, + /// `local_threshold` is used to determine how much longer the average round trip time between + /// the driver and server is allowed compared to the least round trip time of all the suitable + /// servers. For example, if the average round trip times of the suitable servers are 5 ms, 10 + /// ms, and 15 ms, and the local threshold is 8 ms, then the first two servers are within the + /// latency window and could be chosen for the operation, but the last one is not. + /// + /// A value of zero indicates that there is no latency window, so only the server with the + /// lowest average round trip time is eligible. + /// + /// The default value is 15 ms. + pub local_threshold: Option, + + /// The amount of time that a connection can remain idle in a connection pool before being + /// closed. A value of zero indicates that connections should not be closed due to being idle. + /// + /// By default, connections will not be closed due to being idle. + pub max_idle_time: Option, + + /// The maximum amount of connections that the Client should allow to be created in a + /// connection pool for a given server. If an operation is attempted on a server while + /// `max_pool_size` connections are checked out, the operation will block until an in-progress + /// operation finishes and its connection is checked back into the pool. + /// + /// The default value is 10. + pub max_pool_size: Option, + + /// The minimum number of connections that should be available in a server's connection pool at + /// a given time. If fewer than `min_pool_size` connections are in the pool, connections will + /// be added to the pool in the background until `min_pool_size` is reached. + /// + /// The default value is 0. + pub min_pool_size: Option, + + /// The maximum number of new connections that can be created concurrently. + /// + /// If specified, this value must be greater than 0. The default is 2. + pub max_connecting: Option, + + /// Specifies the default read concern for operations performed on the Client. See the + /// ReadConcern type documentation for more details. + pub read_concern: Option, + + /// The name of the replica set that the Client should connect to. + pub repl_set_name: Option, + + /// Whether or not the client should retry a read operation if the operation fails. + /// + /// The default value is true. + pub retry_reads: Option, + + /// Whether or not the client should retry a write operation if the operation fails. + /// + /// The default value is true. + pub retry_writes: Option, + + /// Configures which server monitoring protocol to use. + /// + /// The default is [`Auto`](ServerMonitoringMode::Auto). + pub server_monitoring_mode: Option, + + /// The handler that should process all Server Discovery and Monitoring events. + #[derive_where(skip)] + #[builder(setter(strip_option))] + #[serde(skip)] + pub sdam_event_handler: Option>, + + /// The default selection criteria for operations performed on the Client. See the + /// SelectionCriteria type documentation for more details. + pub selection_criteria: Option, + + /// The declared API version for this client. + /// The declared API version is applied to all commands run through the client, including those + /// sent through any handle derived from the client. + /// + /// Specifying stable API options in the command document passed to `run_command` AND + /// declaring an API version on the client is not supported and is considered undefined + /// behaviour. To run any command with a different API version or without declaring one, create + /// a separate client that declares the appropriate API version. + /// + /// For more information, see the [Stable API]( + /// https://www.mongodb.com/docs/v5.0/reference/stable-api/) manual page. + pub server_api: Option, + + /// The amount of time the Client should attempt to select a server for an operation before + /// timing outs + /// + /// The default value is 30 seconds. + pub server_selection_timeout: Option, + + /// Default database for this client. + /// + /// By default, no default database is specified. + pub default_database: Option, + + /// Overrides the default "mongodb" service name for SRV lookup in both discovery and polling + pub srv_service_name: Option, + + #[builder(setter(skip))] + #[derive_where(skip(Debug))] + pub(crate) socket_timeout: Option, + + /// The TLS configuration for the Client to use in its connections with the server. + /// + /// By default, TLS is disabled. + pub tls: Option, + + /// The maximum number of bytes that the driver should include in a tracing event + /// or log message's extended JSON string representation of a BSON document, e.g. a + /// command or reply from the server. + /// If truncation of a document at the exact specified length would occur in the middle + /// of a Unicode codepoint, the document will be truncated at the closest larger length + /// which falls on a boundary between codepoints. + /// Note that in cases where truncation occurs the output will not be valid JSON. + /// + /// The default value is 1000. + #[cfg(feature = "tracing-unstable")] + pub tracing_max_document_length_bytes: Option, + + /// Specifies the default write concern for operations performed on the Client. See the + /// WriteConcern type documentation for more details. + pub write_concern: Option, + + /// Limit on the number of mongos connections that may be created for sharded topologies. + pub srv_max_hosts: Option, + + /// Information from the SRV URI that generated these client options, if applicable. + #[builder(setter(skip))] + #[serde(skip)] + #[derive_where(skip(Debug))] + pub(crate) original_srv_info: Option, + + #[cfg(test)] + #[builder(setter(skip))] + #[derive_where(skip(Debug))] + pub(crate) original_uri: Option, + + /// Configuration of the DNS resolver used for SRV and TXT lookups. + /// By default, the host system's resolver configuration will be used. + /// + /// On Windows, there is a known performance issue in [hickory_resolver] with using the default + /// system configuration, so a custom configuration is recommended. + #[builder(setter(skip))] + #[serde(skip)] + #[derive_where(skip(Debug))] + #[cfg(feature = "dns-resolver")] + pub(crate) resolver_config: Option, + + /// Control test behavior of the client. + #[cfg(test)] + #[builder(setter(skip))] + #[serde(skip)] + #[derive_where(skip)] + pub(crate) test_options: Option, +} + +#[cfg(test)] +#[derive(Debug, Clone, Default)] +pub(crate) struct TestOptions { + /// Override MIN_HEARTBEAT_FREQUENCY. + pub(crate) min_heartbeat_freq: Option, + + /// Disable server and SRV-polling monitor threads. + pub(crate) disable_monitoring_threads: bool, + + /// Mock response for `SrvPollingMonitor::lookup_hosts`. + pub(crate) mock_lookup_hosts: Option>, + + /// Async-capable command event listener. + pub(crate) async_event_listener: Option, +} + +pub(crate) type TestEventSender = tokio::sync::mpsc::Sender< + crate::runtime::AcknowledgedMessage, +>; + +fn default_hosts() -> Vec { + vec![ServerAddress::default()] +} + +impl Default for ClientOptions { + fn default() -> Self { + Self::builder().build() + } +} + +#[cfg(test)] +impl Serialize for ClientOptions { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct ClientOptionsHelper<'a> { + appname: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + connecttimeoutms: &'a Option, + + #[serde(flatten, serialize_with = "Credential::serialize_for_client_options")] + credential: &'a Option, + + directconnection: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + heartbeatfrequencyms: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + localthresholdms: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + maxidletimems: &'a Option, + + maxpoolsize: &'a Option, + + minpoolsize: &'a Option, + + maxconnecting: &'a Option, + + #[serde(flatten, serialize_with = "ReadConcern::serialize_for_client_options")] + readconcern: &'a Option, + + replicaset: &'a Option, + + retryreads: &'a Option, + + retrywrites: &'a Option, + + servermonitoringmode: Option, + + #[serde( + flatten, + serialize_with = "SelectionCriteria::serialize_for_client_options" + )] + selectioncriteria: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + serverselectiontimeoutms: &'a Option, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + sockettimeoutms: &'a Option, + + #[serde(flatten, serialize_with = "Tls::serialize_for_client_options")] + tls: &'a Option, + + #[serde(flatten, serialize_with = "WriteConcern::serialize_for_client_options")] + writeconcern: &'a Option, + + zlibcompressionlevel: &'a Option, + + loadbalanced: &'a Option, + + srvmaxhosts: Option, + + srvservicename: &'a Option, + } + + let client_options = ClientOptionsHelper { + appname: &self.app_name, + connecttimeoutms: &self.connect_timeout, + credential: &self.credential, + directconnection: &self.direct_connection, + heartbeatfrequencyms: &self.heartbeat_freq, + localthresholdms: &self.local_threshold, + maxidletimems: &self.max_idle_time, + maxpoolsize: &self.max_pool_size, + minpoolsize: &self.min_pool_size, + maxconnecting: &self.max_connecting, + readconcern: &self.read_concern, + replicaset: &self.repl_set_name, + retryreads: &self.retry_reads, + retrywrites: &self.retry_writes, + servermonitoringmode: self + .server_monitoring_mode + .as_ref() + .map(|m| format!("{:?}", m).to_lowercase()), + selectioncriteria: &self.selection_criteria, + serverselectiontimeoutms: &self.server_selection_timeout, + sockettimeoutms: &self.socket_timeout, + tls: &self.tls, + writeconcern: &self.write_concern, + loadbalanced: &self.load_balanced, + zlibcompressionlevel: &None, + srvmaxhosts: self + .srv_max_hosts + .map(|v| v.try_into()) + .transpose() + .map_err(serde::ser::Error::custom)?, + srvservicename: &self.srv_service_name, + }; + + client_options.serialize(serializer) + } +} + +/// Contains the options that can be set via a MongoDB connection string. +/// +/// The format of a MongoDB connection string is described [here](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats). +#[derive(Debug, Default, PartialEq)] +#[non_exhaustive] +pub struct ConnectionString { + /// The initial list of seeds that the Client should connect to, or a DNS name used for SRV + /// lookup of the initial seed list. + /// + /// Note that by default, the driver will autodiscover other nodes in the cluster. To connect + /// directly to a single server (rather than autodiscovering the rest of the cluster), set the + /// `direct_connection` field to `true`. + pub host_info: HostInfo, + + /// The application name that the Client will send to the server as part of the handshake. This + /// can be used in combination with the server logs to determine which Client is connected to a + /// server. + pub app_name: Option, + + /// The TLS configuration for the Client to use in its connections with the server. + /// + /// By default, TLS is disabled. + pub tls: Option, + + /// The amount of time each monitoring thread should wait between performing server checks. + /// + /// The default value is 10 seconds. + pub heartbeat_frequency: Option, + + /// When running a read operation with a ReadPreference that allows selecting secondaries, + /// `local_threshold` is used to determine how much longer the average round trip time between + /// the driver and server is allowed compared to the least round trip time of all the suitable + /// servers. For example, if the average round trip times of the suitable servers are 5 ms, 10 + /// ms, and 15 ms, and the local threshold is 8 ms, then the first two servers are within the + /// latency window and could be chosen for the operation, but the last one is not. + /// + /// A value of zero indicates that there is no latency window, so only the server with the + /// lowest average round trip time is eligible. + /// + /// The default value is 15 ms. + pub local_threshold: Option, + + /// Specifies the default read concern for operations performed on the Client. See the + /// ReadConcern type documentation for more details. + pub read_concern: Option, + + /// The name of the replica set that the Client should connect to. + pub replica_set: Option, + + /// Specifies the default write concern for operations performed on the Client. See the + /// WriteConcern type documentation for more details. + pub write_concern: Option, + + /// The amount of time the Client should attempt to select a server for an operation before + /// timing outs + /// + /// The default value is 30 seconds. + pub server_selection_timeout: Option, + + /// The maximum amount of connections that the Client should allow to be created in a + /// connection pool for a given server. If an operation is attempted on a server while + /// `max_pool_size` connections are checked out, the operation will block until an in-progress + /// operation finishes and its connection is checked back into the pool. + /// + /// The default value is 10. + pub max_pool_size: Option, + + /// The minimum number of connections that should be available in a server's connection pool at + /// a given time. If fewer than `min_pool_size` connections are in the pool, connections will + /// be added to the pool in the background until `min_pool_size` is reached. + /// + /// The default value is 0. + pub min_pool_size: Option, + + /// The maximum number of new connections that can be created concurrently. + /// + /// If specified, this value must be greater than 0. The default is 2. + pub max_connecting: Option, + + /// The amount of time that a connection can remain idle in a connection pool before being + /// closed. A value of zero indicates that connections should not be closed due to being idle. + /// + /// By default, connections will not be closed due to being idle. + pub max_idle_time: Option, + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + /// The compressors that the Client is willing to use in the order they are specified + /// in the configuration. The Client sends this list of compressors to the server. + /// The server responds with the intersection of its supported list of compressors. + /// The order of compressors indicates preference of compressors. + pub compressors: Option>, + + /// The connect timeout passed to each underlying TcpStream when attempting to connect to the + /// server. + /// + /// The default value is 10 seconds. + pub connect_timeout: Option, + + /// Whether or not the client should retry a read operation if the operation fails. + /// + /// The default value is true. + pub retry_reads: Option, + + /// Whether or not the client should retry a write operation if the operation fails. + /// + /// The default value is true. + pub retry_writes: Option, + + /// Configures which server monitoring protocol to use. + /// + /// The default is [`Auto`](ServerMonitoringMode::Auto). + pub server_monitoring_mode: Option, + + /// Specifies whether the Client should directly connect to a single host rather than + /// autodiscover all servers in the cluster. + /// + /// The default value is false. + pub direct_connection: Option, + + /// The credential to use for authenticating connections made by this client. + pub credential: Option, + + /// Default database for this client. + /// + /// By default, no default database is specified. + pub default_database: Option, + + /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. + pub load_balanced: Option, + + /// Amount of time spent attempting to send or receive on a socket before timing out; note that + /// this only applies to application operations, not server discovery and monitoring. + pub socket_timeout: Option, + + /// Default read preference for the client. + pub read_preference: Option, + + /// The [`UuidRepresentation`] to use when decoding [`Binary`](crate::bson::Binary) values with + /// the [`UuidOld`](crate::bson::spec::BinarySubtype::UuidOld) subtype. This is not used by + /// the driver; client code can use this when deserializing relevant values with + /// [`Binary::to_uuid_with_representation`](crate::bson::binary::Binary::to_uuid_with_representation). + pub uuid_representation: Option, + + /// Limit on the number of mongos connections that may be created for sharded topologies. + pub srv_max_hosts: Option, + + /// Overrides the default "mongodb" service name for SRV lookup in both discovery and polling + pub srv_service_name: Option, + + wait_queue_timeout: Option, + tls_insecure: Option, + + #[cfg(test)] + original_uri: String, +} + +/// Elements from the connection string that are not top-level fields in `ConnectionString`. +#[derive(Debug, Default)] +struct ConnectionStringParts { + read_preference_tags: Option>, + max_staleness: Option, + auth_mechanism: Option, + auth_mechanism_properties: Option, + zlib_compression: Option, + auth_source: Option, +} + +/// Specification for mongodb server connections. +#[derive(Debug, PartialEq, Clone)] +#[non_exhaustive] +pub enum HostInfo { + /// A set of addresses. + HostIdentifiers(Vec), + /// A DNS record for SRV lookup. + DnsRecord(String), +} + +impl Default for HostInfo { + fn default() -> Self { + Self::HostIdentifiers(vec![]) + } +} + +impl HostInfo { + async fn resolve( + self, + resolver_config: Option, + srv_service_name: Option, + ) -> Result { + Ok(match self { + Self::HostIdentifiers(hosts) => ResolvedHostInfo::HostIdentifiers(hosts), + Self::DnsRecord(hostname) => { + let mut resolver = + SrvResolver::new(resolver_config.clone(), srv_service_name).await?; + let config = resolver.resolve_client_options(&hostname).await?; + ResolvedHostInfo::DnsRecord { hostname, config } + } + }) + } +} + +enum ResolvedHostInfo { + HostIdentifiers(Vec), + DnsRecord { + hostname: String, + config: crate::srv::ResolvedConfig, + }, +} + +/// Specifies whether TLS configuration should be used with the operations that the +/// [`Client`](../struct.Client.html) performs. +#[derive(Clone, Debug, Deserialize, PartialEq)] +pub enum Tls { + /// Enable TLS with the specified options. + Enabled(TlsOptions), + + /// Disable TLS. + Disabled, +} + +impl From for Tls { + fn from(options: TlsOptions) -> Self { + Self::Enabled(options) + } +} + +impl From for Option { + fn from(options: TlsOptions) -> Self { + Some(Tls::Enabled(options)) + } +} + +impl Tls { + #[cfg(test)] + pub(crate) fn serialize_for_client_options( + tls: &Option, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + match tls { + Some(Tls::Enabled(tls_options)) => { + TlsOptions::serialize_for_client_options(tls_options, serializer) + } + _ => serializer.serialize_none(), + } + } +} + +/// Specifies the TLS configuration that the [`Client`](../struct.Client.html) should use. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct TlsOptions { + /// Whether or not the [`Client`](../struct.Client.html) should return an error if the server + /// presents an invalid certificate. This setting should _not_ be set to `true` in + /// production; it should only be used for testing. + /// + /// The default value is to error when the server presents an invalid certificate. + pub allow_invalid_certificates: Option, + + /// The path to the CA file that the [`Client`](../struct.Client.html) should use for TLS. If + /// none is specified, then the driver will use the Mozilla root certificates from the + /// `webpki-roots` crate. + pub ca_file_path: Option, + + /// The path to the certificate file that the [`Client`](../struct.Client.html) should present + /// to the server to verify its identify. If none is specified, then the + /// [`Client`](../struct.Client.html) will not attempt to verify its identity to the + /// server. + pub cert_key_file_path: Option, + + /// Whether or not the [`Client`](../struct.Client.html) should return an error if the hostname + /// is invalid. + /// + /// The default value is to error on invalid hostnames. + #[cfg(feature = "openssl-tls")] + pub allow_invalid_hostnames: Option, + + /// If set, the key in `cert_key_file_path` must be encrypted with this password. + #[cfg(feature = "cert-key-password")] + pub tls_certificate_key_file_password: Option>, +} + +impl TlsOptions { + #[cfg(test)] + pub(crate) fn serialize_for_client_options( + tls_options: &TlsOptions, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + #[derive(Serialize)] + struct TlsOptionsHelper<'a> { + tls: bool, + tlscafile: Option<&'a str>, + tlscertificatekeyfile: Option<&'a str>, + tlsallowinvalidcertificates: Option, + #[cfg(feature = "cert-key-password")] + tlscertificatekeyfilepassword: Option<&'a str>, + } + + let state = TlsOptionsHelper { + tls: true, + tlscafile: tls_options + .ca_file_path + .as_ref() + .map(|s| s.to_str().unwrap()), + tlscertificatekeyfile: tls_options + .cert_key_file_path + .as_ref() + .map(|s| s.to_str().unwrap()), + tlsallowinvalidcertificates: tls_options.allow_invalid_certificates, + #[cfg(feature = "cert-key-password")] + tlscertificatekeyfilepassword: tls_options + .tls_certificate_key_file_password + .as_deref() + .map(|b| std::str::from_utf8(b).unwrap()), + }; + state.serialize(serializer) + } +} + +/// Extra information to append to the driver version in the metadata of the handshake with the +/// server. This should be used by libraries wrapping the driver, e.g. ODMs. +#[derive(Clone, Debug, Deserialize, TypedBuilder, PartialEq)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct DriverInfo { + /// The name of the library wrapping the driver. + #[builder(!default)] + pub name: String, + + /// The version of the library wrapping the driver. + pub version: Option, + + /// Optional platform information for the wrapping driver. + pub platform: Option, +} + +impl ClientOptions { + /// Creates a new ClientOptions with the `original_srv_hostname` field set to the testing value + /// used in the SRV tests. + #[cfg(test)] + pub(crate) fn new_srv() -> Self { + Self { + original_srv_info: Some(OriginalSrvInfo { + hostname: "localhost.test.test.build.10gen.cc".into(), + min_ttl: Duration::from_secs(60), + }), + ..Default::default() + } + } + + pub(crate) fn tls_options(&self) -> Option { + match self.tls { + Some(Tls::Enabled(ref opts)) => Some(opts.clone()), + _ => None, + } + } + + /// Ensure the options set are valid, returning an error describing the problem if they are not. + pub(crate) fn validate(&self) -> Result<()> { + if let Some(true) = self.direct_connection { + if self.hosts.len() > 1 { + return Err(ErrorKind::InvalidArgument { + message: "cannot specify multiple seeds with directConnection=true".to_string(), + } + .into()); + } + } + + if let Some(ref write_concern) = self.write_concern { + write_concern.validate()?; + } + + if self.load_balanced.unwrap_or(false) { + if self.hosts.len() > 1 { + return Err(ErrorKind::InvalidArgument { + message: "cannot specify multiple seeds with loadBalanced=true".to_string(), + } + .into()); + } + if self.repl_set_name.is_some() { + return Err(ErrorKind::InvalidArgument { + message: "cannot specify replicaSet with loadBalanced=true".to_string(), + } + .into()); + } + if self.direct_connection == Some(true) { + return Err(ErrorKind::InvalidArgument { + message: "cannot specify directConnection=true with loadBalanced=true" + .to_string(), + } + .into()); + } + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + if let Some(ref compressors) = self.compressors { + for compressor in compressors { + compressor.validate()?; + } + } + + if let Some(0) = self.max_pool_size { + return Err(Error::invalid_argument("cannot specify maxPoolSize=0")); + } + + if let Some(0) = self.max_connecting { + return Err(Error::invalid_argument("cannot specify maxConnecting=0")); + } + + if let Some(SelectionCriteria::ReadPreference(ref rp)) = self.selection_criteria { + if let Some(max_staleness) = rp.max_staleness() { + verify_max_staleness( + max_staleness, + self.heartbeat_freq.unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY), + )?; + } + } + + if let Some(heartbeat_frequency) = self.heartbeat_freq { + if heartbeat_frequency < self.min_heartbeat_frequency() { + return Err(ErrorKind::InvalidArgument { + message: format!( + "'heartbeat_freq' must be at least {}ms, but {}ms was given", + self.min_heartbeat_frequency().as_millis(), + heartbeat_frequency.as_millis() + ), + } + .into()); + } + } + + #[cfg(feature = "tracing-unstable")] + { + let hostnames = if let Some(info) = &self.original_srv_info { + vec![info.hostname.to_ascii_lowercase()] + } else { + self.hosts + .iter() + .filter_map(|addr| match addr { + ServerAddress::Tcp { host, .. } => Some(host.to_ascii_lowercase()), + #[cfg(unix)] + _ => None, + }) + .collect() + }; + if hostnames.iter().any(|s| s.ends_with(".cosmos.azure.com")) { + tracing::info!("You appear to be connected to a CosmosDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/cosmosdb"); + } + if hostnames.iter().any(|s| { + s.ends_with(".docdb.amazonaws.com") || s.ends_with(".docdb-elastic.amazonaws.com") + }) { + tracing::info!("You appear to be connected to a DocumentDB cluster. For more information regarding feature compatibility and support please visit https://www.mongodb.com/supportability/documentdb"); + } + } + + Ok(()) + } + + /// Applies the options in other to these options if a value is not already present + #[cfg(test)] + pub(crate) fn merge(&mut self, other: ClientOptions) { + if self.hosts.is_empty() { + self.hosts = other.hosts; + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + merge_options!(other, self, [compressors]); + + merge_options!( + other, + self, + [ + app_name, + cmap_event_handler, + command_event_handler, + connect_timeout, + credential, + direct_connection, + driver_info, + heartbeat_freq, + load_balanced, + local_threshold, + max_idle_time, + max_pool_size, + min_pool_size, + read_concern, + repl_set_name, + retry_reads, + retry_writes, + selection_criteria, + server_api, + server_selection_timeout, + socket_timeout, + test_options, + tls, + write_concern, + original_srv_info, + original_uri + ] + ); + } + + #[cfg(test)] + pub(crate) fn test_options_mut(&mut self) -> &mut TestOptions { + self.test_options.get_or_insert_with(Default::default) + } + + pub(crate) fn min_heartbeat_frequency(&self) -> Duration { + #[cfg(test)] + { + self.test_options + .as_ref() + .and_then(|to| to.min_heartbeat_freq) + .unwrap_or(MIN_HEARTBEAT_FREQUENCY) + } + + #[cfg(not(test))] + { + MIN_HEARTBEAT_FREQUENCY + } + } + + pub(crate) fn resolver_config(&self) -> Option<&ResolverConfig> { + #[cfg(feature = "dns-resolver")] + { + self.resolver_config.as_ref() + } + #[cfg(not(feature = "dns-resolver"))] + { + None + } + } +} + +/// Splits the string once on the first instance of the given delimiter. If the delimiter is not +/// present, returns the entire string as the "left" side. +/// +/// e.g. +/// "abc.def" split on "." -> ("abc", Some("def")) +/// "ab.cd.ef" split on "." -> ("ab", Some("cd.ef")) +/// "abcdef" split on "." -> ("abcdef", None) +fn split_once_left<'a>(s: &'a str, delimiter: &str) -> (&'a str, Option<&'a str>) { + match s.split_once(delimiter) { + Some((l, r)) => (l, Some(r)), + None => (s, None), + } +} + +/// Splits the string once on the last instance of the given delimiter. If the delimiter is not +/// present, returns the entire string as the "right" side. +/// +/// e.g. +/// "abd.def" split on "." -> (Some("abc"), "def") +/// "ab.cd.ef" split on "." -> (Some("ab.cd"), "ef") +/// "abcdef" split on "." -> (None, "abcdef") +fn split_once_right<'a>(s: &'a str, delimiter: &str) -> (Option<&'a str>, &'a str) { + match s.rsplit_once(delimiter) { + Some((l, r)) => (Some(l), r), + None => (None, s), + } +} + +fn percent_decode(s: &str, err_message: &str) -> Result { + match percent_encoding::percent_decode_str(s).decode_utf8() { + Ok(result) => Ok(result.to_string()), + Err(_) => Err(ErrorKind::InvalidArgument { + message: err_message.to_string(), + } + .into()), + } +} + +fn validate_and_parse_userinfo(s: &str, userinfo_type: &str) -> Result { + if s.chars().any(|c| USERINFO_RESERVED_CHARACTERS.contains(&c)) { + return Err(Error::invalid_argument(format!( + "{} must be URL encoded", + userinfo_type + ))); + } + + // All instances of '%' in the username must be part of an percent-encoded substring. This means + // that there must be two hexidecimal digits following any '%' in the username. + if s.split('%') + .skip(1) + .any(|part| part.len() < 2 || part[0..2].chars().any(|c| !c.is_ascii_hexdigit())) + { + return Err(Error::invalid_argument(format!( + "{} cannot contain unescaped %", + userinfo_type + ))); + } + + percent_decode(s, &format!("{} must be URL encoded", userinfo_type)) +} + +impl TryFrom<&str> for ConnectionString { + type Error = Error; + + fn try_from(value: &str) -> Result { + Self::parse(value) + } +} + +impl TryFrom<&String> for ConnectionString { + type Error = Error; + + fn try_from(value: &String) -> Result { + Self::parse(value) + } +} + +impl TryFrom for ConnectionString { + type Error = Error; + + fn try_from(value: String) -> Result { + Self::parse(value) + } +} + +impl ConnectionString { + /// Parses a MongoDB connection string into a [`ConnectionString`] struct. If the string is + /// malformed or one of the options has an invalid value, an error will be returned. + pub fn parse(s: impl AsRef) -> Result { + let s = s.as_ref(); + + let Some((scheme, after_scheme)) = s.split_once("://") else { + return Err(Error::invalid_argument( + "connection string contains no scheme", + )); + }; + + let srv = match scheme { + "mongodb" => false, + #[cfg(feature = "dns-resolver")] + "mongodb+srv" => true, + #[cfg(not(feature = "dns-resolver"))] + "mongodb+srv" => { + return Err(Error::invalid_argument( + "mongodb+srv connection strings cannot be used when the 'dns-resolver' \ + feature is disabled", + )) + } + other => { + return Err(Error::invalid_argument(format!( + "unsupported connection string scheme: {}", + other + ))) + } + }; + + let (pre_options, options) = split_once_left(after_scheme, "?"); + let (user_info, hosts_and_auth_db) = split_once_right(pre_options, "@"); + + // if '@' is in the host section, it MUST be interpreted as a request for authentication + let authentication_requested = user_info.is_some(); + let (username, password) = match user_info { + Some(user_info) => { + let (username, password) = split_once_left(user_info, ":"); + let username = if username.is_empty() { + None + } else { + Some(validate_and_parse_userinfo(username, "username")?) + }; + let password = match password { + Some(password) => Some(validate_and_parse_userinfo(password, "password")?), + None => None, + }; + (username, password) + } + None => (None, None), + }; + + let (hosts, auth_db) = split_once_left(hosts_and_auth_db, "/"); + + let hosts = hosts + .split(",") + .map(ServerAddress::parse) + .collect::>>()?; + let host_info = if !srv { + HostInfo::HostIdentifiers(hosts) + } else { + match &hosts[..] { + [ServerAddress::Tcp { host, port: None }] => HostInfo::DnsRecord(host.clone()), + [ServerAddress::Tcp { + host: _, + port: Some(_), + }] => { + return Err(Error::invalid_argument( + "a port cannot be specified with 'mongodb+srv'", + )); + } + #[cfg(unix)] + [ServerAddress::Unix { .. }] => { + return Err(Error::invalid_argument( + "unix sockets cannot be used with 'mongodb+srv'", + )); + } + _ => { + return Err(Error::invalid_argument( + "exactly one host must be specified with 'mongodb+srv'", + )) + } + } + }; + + let db = match auth_db { + Some("") | None => None, + Some(db) => { + let decoded = percent_decode(db, "database name must be URL encoded")?; + for c in decoded.chars() { + if ILLEGAL_DATABASE_CHARACTERS.contains(&c) { + return Err(Error::invalid_argument(format!( + "illegal character in database name: {}", + c + ))); + } + } + Some(decoded) + } + }; + + let mut conn_str = ConnectionString { + host_info, + #[cfg(test)] + original_uri: s.into(), + ..Default::default() + }; + + let mut parts = match options { + Some(options) => conn_str.parse_options(options)?, + None => ConnectionStringParts::default(), + }; + + if conn_str.srv_service_name.is_some() && !srv { + return Err(Error::invalid_argument( + "srvServiceName cannot be specified with a non-SRV URI", + )); + } + + if let Some(srv_max_hosts) = conn_str.srv_max_hosts { + if !srv { + return Err(Error::invalid_argument( + "srvMaxHosts cannot be specified with a non-SRV URI", + )); + } + if srv_max_hosts > 0 { + if conn_str.replica_set.is_some() { + return Err(Error::invalid_argument( + "srvMaxHosts and replicaSet cannot both be present", + )); + } + if conn_str.load_balanced == Some(true) { + return Err(Error::invalid_argument( + "srvMaxHosts and loadBalanced=true cannot both be present", + )); + } + } + } + + if let Some(username) = username { + let credential = conn_str.credential.get_or_insert_with(Default::default); + credential.username = Some(username); + credential.password = password; + } + + if parts.auth_source.as_deref() == Some("") { + return Err(ErrorKind::InvalidArgument { + message: "empty authSource provided".to_string(), + } + .into()); + } + + match parts.auth_mechanism { + Some(ref mechanism) => { + let credential = conn_str.credential.get_or_insert_with(Default::default); + credential.source = parts.auth_source; + + if let Some(mut doc) = parts.auth_mechanism_properties.take() { + match doc.remove("CANONICALIZE_HOST_NAME") { + Some(Bson::String(s)) => { + let val = match &s.to_lowercase()[..] { + "true" => Bson::Boolean(true), + "false" => Bson::Boolean(false), + "none" | "forward" | "forwardandreverse" => Bson::String(s), + _ => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "Invalid CANONICALIZE_HOST_NAME value: {}. Valid \ + values are 'none', 'forward', 'forwardAndReverse', \ + 'true', 'false'", + s + ), + } + .into()); + } + }; + doc.insert("CANONICALIZE_HOST_NAME", val); + } + Some(val) => { + doc.insert("CANONICALIZE_HOST_NAME", val); + } + None => {} + } + + credential.mechanism_properties = Some(doc); + } + + #[cfg(feature = "gssapi-auth")] + if mechanism == &AuthMechanism::Gssapi { + // Set mongodb as the default SERVICE_NAME if none is provided + let mut doc = if let Some(doc) = credential.mechanism_properties.take() { + doc + } else { + Document::new() + }; + + if !doc.contains_key("SERVICE_NAME") { + doc.insert("SERVICE_NAME", "mongodb"); + } + + credential.mechanism_properties = Some(doc); + } + + credential.mechanism = Some(mechanism.clone()); + mechanism.validate_credential(credential)?; + } + None => { + if let Some(ref mut credential) = conn_str.credential { + credential.source = parts.auth_source; + } else if authentication_requested { + return Err(ErrorKind::InvalidArgument { + message: "username and mechanism both not provided, but authentication \ + was requested" + .to_string(), + } + .into()); + } + } + }; + + // set default database. + conn_str.default_database = db; + + if conn_str.tls.is_none() && conn_str.is_srv() { + conn_str.tls = Some(Tls::Enabled(Default::default())); + } + + Ok(conn_str) + } + + /// Amount of time spent attempting to check out a connection from a server's connection pool + /// before timing out. Not supported by the Rust driver. + pub fn wait_queue_timeout(&self) -> Option { + self.wait_queue_timeout + } + + /// Relax TLS constraints as much as possible (e.g. allowing invalid certificates or hostname + /// mismatches). Not supported by the Rust driver. + pub fn tls_insecure(&self) -> Option { + self.tls_insecure + } + + fn is_srv(&self) -> bool { + matches!(self.host_info, HostInfo::DnsRecord(_)) + } + + fn parse_options(&mut self, options: &str) -> Result { + let mut parts = ConnectionStringParts::default(); + if options.is_empty() { + return Ok(parts); + } + + let mut keys: Vec<&str> = Vec::new(); + + for option_pair in options.split('&') { + let (key, value) = match option_pair.find('=') { + Some(index) => option_pair.split_at(index), + None => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string options is not a `key=value` pair: {}", + option_pair, + ), + } + .into()) + } + }; + + if key.to_lowercase() != "readpreferencetags" && keys.contains(&key) { + return Err(ErrorKind::InvalidArgument { + message: "repeated options are not allowed in the connection string" + .to_string(), + } + .into()); + } else { + keys.push(key); + } + + // Skip leading '=' in value. + self.parse_option_pair( + &mut parts, + &key.to_lowercase(), + percent_encoding::percent_decode(&value.as_bytes()[1..]) + .decode_utf8_lossy() + .as_ref(), + )?; + } + + if let Some(tags) = parts.read_preference_tags.take() { + self.read_preference = match self.read_preference.take() { + Some(read_pref) => Some(read_pref.with_tags(tags)?), + None => { + return Err(ErrorKind::InvalidArgument { + message: "cannot set read preference tags without also setting read \ + preference mode" + .to_string(), + } + .into()) + } + }; + } + + if let Some(max_staleness) = parts.max_staleness.take() { + self.read_preference = match self.read_preference.take() { + Some(read_pref) => Some(read_pref.with_max_staleness(max_staleness)?), + None => { + return Err(ErrorKind::InvalidArgument { + message: "cannot set max staleness without also setting read preference \ + mode" + .to_string(), + } + .into()) + } + }; + } + + if let Some(true) = self.direct_connection { + if self.is_srv() { + return Err(ErrorKind::InvalidArgument { + message: "cannot use SRV-style URI with directConnection=true".to_string(), + } + .into()); + } + } + + #[cfg(feature = "zlib-compression")] + if let Some(zlib_compression_level) = parts.zlib_compression { + if let Some(compressors) = self.compressors.as_mut() { + for compressor in compressors { + compressor.write_zlib_level(zlib_compression_level)?; + } + } + } + #[cfg(not(feature = "zlib-compression"))] + if parts.zlib_compression.is_some() { + return Err(ErrorKind::InvalidArgument { + message: "zlibCompressionLevel may not be specified without the zlib-compression \ + feature flag enabled" + .into(), + } + .into()); + } + + Ok(parts) + } + + fn parse_option_pair( + &mut self, + parts: &mut ConnectionStringParts, + key: &str, + value: &str, + ) -> Result<()> { + macro_rules! get_bool { + ($value:expr, $option:expr) => { + match $value { + "true" => true, + "false" => false, + _ => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string `{}` option must be a boolean", + $option, + ), + } + .into()) + } + } + }; + } + + macro_rules! get_duration { + ($value:expr, $option:expr) => { + match $value.parse::() { + Ok(i) => i, + _ => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string `{}` option must be a non-negative integer", + $option + ), + } + .into()) + } + } + }; + } + + macro_rules! get_u32 { + ($value:expr, $option:expr) => { + match value.parse::() { + Ok(u) => u, + Err(_) => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string `{}` argument must be a positive integer", + $option, + ), + } + .into()) + } + } + }; + } + + macro_rules! get_i32 { + ($value:expr, $option:expr) => { + match value.parse::() { + Ok(u) => u, + Err(_) => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string `{}` argument must be an integer", + $option + ), + } + .into()) + } + } + }; + } + + match key { + "appname" => { + self.app_name = Some(value.into()); + } + "authmechanism" => { + parts.auth_mechanism = Some(AuthMechanism::from_str(value)?); + } + "authsource" => parts.auth_source = Some(value.to_string()), + "authmechanismproperties" => { + let mut properties = Document::new(); + + for property in value.split(",") { + let Some((k, v)) = property.split_once(":") else { + return Err(Error::invalid_argument(format!( + "each entry in authMechanismProperties must be a colon-separated \ + key-value pair, got {}", + property + ))); + }; + if k == "ALLOWED_HOSTS" || k == "OIDC_CALLBACK" || k == "OIDC_HUMAN_CALLBACK" { + return Err(Error::invalid_argument(format!( + "{} must only be specified through client options", + k + ))); + } + properties.insert(k, v); + } + + parts.auth_mechanism_properties = Some(properties); + } + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + "compressors" => { + let mut compressors: Option> = None; + for compressor in value.split(',') { + let compressor = Compressor::from_str(compressor)?; + compressors + .get_or_insert_with(Default::default) + .push(compressor); + } + self.compressors = compressors; + } + k @ "connecttimeoutms" => { + self.connect_timeout = Some(Duration::from_millis(get_duration!(value, k))); + } + k @ "directconnection" => { + self.direct_connection = Some(get_bool!(value, k)); + } + k @ "heartbeatfrequencyms" => { + self.heartbeat_frequency = Some(Duration::from_millis(get_duration!(value, k))); + } + k @ "journal" => { + let write_concern = self.write_concern.get_or_insert_with(Default::default); + write_concern.journal = Some(get_bool!(value, k)); + } + k @ "loadbalanced" => { + self.load_balanced = Some(get_bool!(value, k)); + } + k @ "localthresholdms" => { + self.local_threshold = Some(Duration::from_millis(get_duration!(value, k))) + } + k @ "maxidletimems" => { + self.max_idle_time = Some(Duration::from_millis(get_duration!(value, k))); + } + "maxstalenessseconds" => { + let max_staleness_seconds = value.parse::().map_err(|e| { + Error::invalid_argument(format!("invalid maxStalenessSeconds value: {}", e)) + })?; + + let max_staleness = match max_staleness_seconds.cmp(&-1) { + Ordering::Less => { + return Err(Error::invalid_argument(format!( + "maxStalenessSeconds must be -1 or positive, instead got {}", + max_staleness_seconds + ))); + } + Ordering::Equal => { + // -1 maxStaleness means no maxStaleness, which is the default + return Ok(()); + } + Ordering::Greater => Duration::from_secs(max_staleness_seconds as u64), + }; + + parts.max_staleness = Some(max_staleness); + } + k @ "maxpoolsize" => { + self.max_pool_size = Some(get_u32!(value, k)); + } + k @ "minpoolsize" => { + self.min_pool_size = Some(get_u32!(value, k)); + } + k @ "maxconnecting" => { + self.max_connecting = Some(get_u32!(value, k)); + } + "readconcernlevel" => { + self.read_concern = Some(ReadConcernLevel::from_str(value).into()); + } + "readpreference" => { + self.read_preference = Some(match &value.to_lowercase()[..] { + "primary" => ReadPreference::Primary, + "secondary" => ReadPreference::Secondary { + options: Default::default(), + }, + "primarypreferred" => ReadPreference::PrimaryPreferred { + options: Default::default(), + }, + "secondarypreferred" => ReadPreference::SecondaryPreferred { + options: Default::default(), + }, + "nearest" => ReadPreference::Nearest { + options: Default::default(), + }, + other => { + return Err(ErrorKind::InvalidArgument { + message: format!("'{}' is not a valid read preference", other), + } + .into()) + } + }); + } + "readpreferencetags" => { + let tags: Result = if value.is_empty() { + Ok(TagSet::new()) + } else { + value + .split(',') + .map(|tag| { + let mut values = tag.split(':'); + + match (values.next(), values.next()) { + (Some(key), Some(value)) => { + Ok((key.to_string(), value.to_string())) + } + _ => Err(ErrorKind::InvalidArgument { + message: format!( + "'{}' is not a valid read preference tag (which must be \ + of the form 'key:value'", + value, + ), + } + .into()), + } + }) + .collect() + }; + + parts + .read_preference_tags + .get_or_insert_with(Vec::new) + .push(tags?); + } + "replicaset" => { + self.replica_set = Some(value.to_string()); + } + k @ "retrywrites" => { + self.retry_writes = Some(get_bool!(value, k)); + } + k @ "retryreads" => { + self.retry_reads = Some(get_bool!(value, k)); + } + "servermonitoringmode" => { + self.server_monitoring_mode = Some(match value.to_lowercase().as_str() { + "stream" => ServerMonitoringMode::Stream, + "poll" => ServerMonitoringMode::Poll, + "auto" => ServerMonitoringMode::Auto, + other => { + return Err(Error::invalid_argument(format!( + "{:?} is not a valid server monitoring mode", + other + ))); + } + }); + } + k @ "serverselectiontimeoutms" => { + self.server_selection_timeout = Some(Duration::from_millis(get_duration!(value, k))) + } + k @ "sockettimeoutms" => { + self.socket_timeout = Some(Duration::from_millis(get_duration!(value, k))); + } + k @ "srvmaxhosts" => { + self.srv_max_hosts = Some(get_u32!(value, k)); + } + "srvservicename" => { + self.srv_service_name = Some(value.to_string()); + } + k @ "tls" | k @ "ssl" => { + let tls = get_bool!(value, k); + + match (self.tls.as_ref(), tls) { + (Some(Tls::Disabled), true) | (Some(Tls::Enabled(..)), false) => { + return Err(ErrorKind::InvalidArgument { + message: "All instances of `tls` and `ssl` must have the same + value" + .to_string(), + } + .into()); + } + _ => {} + }; + + if self.tls.is_none() { + let tls = if tls { + Tls::Enabled(Default::default()) + } else { + Tls::Disabled + }; + + self.tls = Some(tls); + } + } + k @ "tlsinsecure" | k @ "tlsallowinvalidcertificates" => { + let val = get_bool!(value, k); + + let allow_invalid_certificates = if k == "tlsinsecure" { !val } else { val }; + + match self.tls { + Some(Tls::Disabled) => { + return Err(ErrorKind::InvalidArgument { + message: "'tlsInsecure' can't be set if tls=false".into(), + } + .into()) + } + Some(Tls::Enabled(ref options)) + if options.allow_invalid_certificates.is_some() + && options.allow_invalid_certificates + != Some(allow_invalid_certificates) => + { + return Err(ErrorKind::InvalidArgument { + message: "all instances of 'tlsInsecure' and \ + 'tlsAllowInvalidCertificates' must be consistent (e.g. \ + 'tlsInsecure' cannot be true when \ + 'tlsAllowInvalidCertificates' is false, or vice-versa)" + .into(), + } + .into()); + } + Some(Tls::Enabled(ref mut options)) => { + options.allow_invalid_certificates = Some(allow_invalid_certificates); + } + None => { + self.tls = Some(Tls::Enabled( + TlsOptions::builder() + .allow_invalid_certificates(allow_invalid_certificates) + .build(), + )) + } + } + } + "tlscafile" => match self.tls { + Some(Tls::Disabled) => { + return Err(ErrorKind::InvalidArgument { + message: "'tlsCAFile' can't be set if tls=false".into(), + } + .into()); + } + Some(Tls::Enabled(ref mut options)) => { + options.ca_file_path = Some(value.into()); + } + None => { + self.tls = Some(Tls::Enabled( + TlsOptions::builder() + .ca_file_path(PathBuf::from(value)) + .build(), + )) + } + }, + "tlscertificatekeyfile" => match self.tls { + Some(Tls::Disabled) => { + return Err(ErrorKind::InvalidArgument { + message: "'tlsCertificateKeyFile' can't be set if tls=false".into(), + } + .into()); + } + Some(Tls::Enabled(ref mut options)) => { + options.cert_key_file_path = Some(value.into()); + } + None => { + self.tls = Some(Tls::Enabled( + TlsOptions::builder() + .cert_key_file_path(PathBuf::from(value)) + .build(), + )) + } + }, + #[cfg(feature = "cert-key-password")] + "tlscertificatekeyfilepassword" => match &mut self.tls { + Some(Tls::Disabled) => { + return Err(ErrorKind::InvalidArgument { + message: "'tlsCertificateKeyFilePassword' can't be set if tls=false".into(), + } + .into()); + } + Some(Tls::Enabled(options)) => { + options.tls_certificate_key_file_password = Some(value.as_bytes().to_vec()); + } + None => { + self.tls = Some(Tls::Enabled( + TlsOptions::builder() + .tls_certificate_key_file_password(value.as_bytes().to_vec()) + .build(), + )) + } + }, + #[cfg(not(feature = "cert-key-password"))] + "tlscertificatekeyfilepassword" => { + return Err(Error::invalid_argument( + "the cert-key-password feature must be enabled to specify \ + tlsCertificateKeyFilePassword in the URI", + )); + } + "uuidrepresentation" => match value.to_lowercase().as_str() { + "csharplegacy" => self.uuid_representation = Some(UuidRepresentation::CSharpLegacy), + "javalegacy" => self.uuid_representation = Some(UuidRepresentation::JavaLegacy), + "pythonlegacy" => self.uuid_representation = Some(UuidRepresentation::PythonLegacy), + _ => { + return Err(ErrorKind::InvalidArgument { + message: format!( + "connection string `uuidRepresentation` option can be one of \ + `csharpLegacy`, `javaLegacy`, or `pythonLegacy`. Received invalid \ + `{}`", + value + ), + } + .into()) + } + }, + "w" => { + let write_concern = self.write_concern.get_or_insert_with(Default::default); + + match value.parse::() { + Ok(w) => match u32::try_from(w) { + Ok(uw) => write_concern.w = Some(Acknowledgment::from(uw)), + Err(_) => { + return Err(ErrorKind::InvalidArgument { + message: "connection string `w` option cannot be a negative \ + integer" + .to_string(), + } + .into()) + } + }, + Err(_) => { + write_concern.w = Some(Acknowledgment::from(value.to_string())); + } + }; + } + k @ "waitqueuetimeoutms" => { + self.wait_queue_timeout = Some(Duration::from_millis(get_duration!(value, k))); + } + k @ "wtimeoutms" => { + let write_concern = self.write_concern.get_or_insert_with(Default::default); + write_concern.w_timeout = Some(Duration::from_millis(get_duration!(value, k))); + } + k @ "zlibcompressionlevel" => { + let i = get_i32!(value, k); + if i < -1 { + return Err(ErrorKind::InvalidArgument { + message: "'zlibCompressionLevel' cannot be less than -1".to_string(), + } + .into()); + } + + if i > 9 { + return Err(ErrorKind::InvalidArgument { + message: "'zlibCompressionLevel' cannot be greater than 9".to_string(), + } + .into()); + } + + parts.zlib_compression = Some(i); + } + + other => { + let (jaro_winkler, option) = URI_OPTIONS.iter().fold((0.0, ""), |acc, option| { + let jaro_winkler = jaro_winkler(option, other).abs(); + if jaro_winkler > acc.0 { + return (jaro_winkler, option); + } + acc + }); + let mut message = format!("{} is an invalid option", other); + if jaro_winkler >= 0.84 { + let _ = write!( + message, + ". An option with a similar name exists: {}", + option + ); + } + return Err(ErrorKind::InvalidArgument { message }.into()); + } + } + + Ok(()) + } +} + +impl FromStr for ConnectionString { + type Err = Error; + fn from_str(s: &str) -> Result { + ConnectionString::parse(s) + } +} + +impl<'de> Deserialize<'de> for ConnectionString { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(ConnectionStringVisitor) + } +} + +struct ConnectionStringVisitor; + +impl serde::de::Visitor<'_> for ConnectionStringVisitor { + type Value = ConnectionString; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a MongoDB connection string") + } + + fn visit_str(self, v: &str) -> std::result::Result + where + E: serde::de::Error, + { + ConnectionString::parse(v).map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use pretty_assertions::assert_eq; + + use super::{ClientOptions, ServerAddress}; + use crate::{ + concern::{Acknowledgment, ReadConcernLevel, WriteConcern}, + selection_criteria::{ReadPreference, ReadPreferenceOptions}, + }; + + macro_rules! tag_set { + ( $($k:expr => $v:expr),* ) => { + #[allow(clippy::let_and_return)] + { + use std::collections::HashMap; + + #[allow(unused_mut)] + let mut ts = HashMap::new(); + $( + ts.insert($k.to_string(), $v.to_string()); + )* + + ts + } + } + } + + fn host_without_port(hostname: &str) -> ServerAddress { + ServerAddress::Tcp { + host: hostname.to_string(), + port: None, + } + } + + #[test] + fn test_parse_address_with_from_str() { + let x = "localhost:27017".parse::().unwrap(); + match x { + ServerAddress::Tcp { host, port } => { + assert_eq!(host, "localhost"); + assert_eq!(port, Some(27017)); + } + #[cfg(unix)] + _ => panic!("expected ServerAddress::Tcp"), + } + + // Port defaults to 27017 (so this doesn't fail) + let x = "localhost".parse::().unwrap(); + match x { + ServerAddress::Tcp { host, port } => { + assert_eq!(host, "localhost"); + assert_eq!(port, None); + } + #[cfg(unix)] + _ => panic!("expected ServerAddress::Tcp"), + } + + let x = "localhost:not a number".parse::(); + assert!(x.is_err()); + + #[cfg(unix)] + { + let x = "/path/to/socket.sock".parse::().unwrap(); + match x { + ServerAddress::Unix { path } => { + assert_eq!(path.to_str().unwrap(), "/path/to/socket.sock"); + } + _ => panic!("expected ServerAddress::Unix"), + } + } + } + + #[tokio::test] + async fn fails_without_scheme() { + assert!(ClientOptions::parse("localhost:27017").await.is_err()); + } + + #[tokio::test] + async fn fails_with_invalid_scheme() { + assert!(ClientOptions::parse("mangodb://localhost:27017") + .await + .is_err()); + } + + #[tokio::test] + async fn fails_with_nothing_after_scheme() { + assert!(ClientOptions::parse("mongodb://").await.is_err()); + } + + #[tokio::test] + async fn fails_with_only_slash_after_scheme() { + assert!(ClientOptions::parse("mongodb:///").await.is_err()); + } + + #[tokio::test] + async fn fails_with_no_host() { + assert!(ClientOptions::parse("mongodb://:27017").await.is_err()); + } + + #[tokio::test] + async fn no_port() { + let uri = "mongodb://localhost"; + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![host_without_port("localhost")], + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn no_port_trailing_slash() { + let uri = "mongodb://localhost/"; + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![host_without_port("localhost")], + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_port() { + let uri = "mongodb://localhost/"; + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_port_and_trailing_slash() { + let uri = "mongodb://localhost:27017/"; + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_read_concern() { + let uri = "mongodb://localhost:27017/?readConcernLevel=foo"; + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + read_concern: Some(ReadConcernLevel::Custom("foo".to_string()).into()), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_w_negative_int() { + assert!(ClientOptions::parse("mongodb://localhost:27017/?w=-1") + .await + .is_err()); + } + + #[tokio::test] + async fn with_w_non_negative_int() { + let uri = "mongodb://localhost:27017/?w=1"; + let write_concern = WriteConcern::builder().w(Acknowledgment::from(1)).build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + write_concern: Some(write_concern), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_w_string() { + let uri = "mongodb://localhost:27017/?w=foo"; + let write_concern = WriteConcern::builder() + .w(Acknowledgment::from("foo".to_string())) + .build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + write_concern: Some(write_concern), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_invalid_j() { + assert!( + ClientOptions::parse("mongodb://localhost:27017/?journal=foo") + .await + .is_err() + ); + } + + #[tokio::test] + async fn with_j() { + let uri = "mongodb://localhost:27017/?journal=true"; + let write_concern = WriteConcern::builder().journal(true).build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + write_concern: Some(write_concern), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_wtimeout_non_int() { + assert!( + ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=foo") + .await + .is_err() + ); + } + + #[tokio::test] + async fn with_wtimeout_negative_int() { + assert!( + ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=-1") + .await + .is_err() + ); + } + + #[tokio::test] + async fn with_wtimeout() { + let uri = "mongodb://localhost:27017/?wtimeoutMS=27"; + let write_concern = WriteConcern::builder() + .w_timeout(Duration::from_millis(27)) + .build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + write_concern: Some(write_concern), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_all_write_concern_options() { + let uri = "mongodb://localhost:27017/?w=majority&journal=false&wtimeoutMS=27"; + let write_concern = WriteConcern::builder() + .w(Acknowledgment::Majority) + .journal(false) + .w_timeout(Duration::from_millis(27)) + .build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27017), + }], + write_concern: Some(write_concern), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } + + #[tokio::test] + async fn with_mixed_options() { + let uri = "mongodb://localhost,localhost:27018/?w=majority&readConcernLevel=majority&\ + journal=false&wtimeoutMS=27&replicaSet=foo&heartbeatFrequencyMS=1000&\ + localThresholdMS=4000&readPreference=secondaryPreferred&readpreferencetags=dc:\ + ny,rack:1&serverselectiontimeoutms=2000&readpreferencetags=dc:ny&\ + readpreferencetags="; + let write_concern = WriteConcern::builder() + .w(Acknowledgment::Majority) + .journal(false) + .w_timeout(Duration::from_millis(27)) + .build(); + + assert_eq!( + ClientOptions::parse(uri).await.unwrap(), + ClientOptions { + hosts: vec![ + ServerAddress::Tcp { + host: "localhost".to_string(), + port: None, + }, + ServerAddress::Tcp { + host: "localhost".to_string(), + port: Some(27018), + }, + ], + selection_criteria: Some( + ReadPreference::SecondaryPreferred { + options: Some( + ReadPreferenceOptions::builder() + .tag_sets(vec![ + tag_set! { + "dc" => "ny", + "rack" => "1" + }, + tag_set! { + "dc" => "ny" + }, + tag_set! {}, + ]) + .build() + ) + } + .into() + ), + read_concern: Some(ReadConcernLevel::Majority.into()), + write_concern: Some(write_concern), + repl_set_name: Some("foo".to_string()), + heartbeat_freq: Some(Duration::from_millis(1000)), + local_threshold: Some(Duration::from_millis(4000)), + server_selection_timeout: Some(Duration::from_millis(2000)), + original_uri: Some(uri.into()), + ..Default::default() + } + ); + } +} + +/// Contains the options that can be used to create a new [`ClientSession`](crate::ClientSession). +#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +#[export_tokens] +pub struct SessionOptions { + /// The default options to use for transactions started on this session. + /// + /// If these options are not specified, they will be inherited from the + /// [`Client`](../struct.Client.html) associated with this session. They will not + /// be inherited from the options specified + /// on the [`Database`](../struct.Database.html) or [`Collection`](../struct.Collection.html) + /// associated with the operations within the transaction. + pub default_transaction_options: Option, + + /// If true, all operations performed in the context of this session + /// will be [causally consistent](https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/). + /// + /// Defaults to true if [`SessionOptions::snapshot`] is unspecified. + pub causal_consistency: Option, + + /// If true, all read operations performed using this client session will share the same + /// snapshot. Defaults to false. + pub snapshot: Option, +} + +impl SessionOptions { + pub(crate) fn validate(&self) -> Result<()> { + if let (Some(causal_consistency), Some(snapshot)) = (self.causal_consistency, self.snapshot) + { + if causal_consistency && snapshot { + return Err(ErrorKind::InvalidArgument { + message: "snapshot and causal consistency are mutually exclusive".to_string(), + } + .into()); + } + } + Ok(()) + } +} + +/// Contains the options that can be used for a transaction. +#[skip_serializing_none] +#[derive(Debug, Default, Serialize, Deserialize, TypedBuilder, Clone)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +#[export_tokens] +pub struct TransactionOptions { + /// The read concern to use for the transaction. + #[builder(default)] + #[serde(skip_serializing)] + pub read_concern: Option, + + /// The write concern to use when committing or aborting a transaction. + #[builder(default)] + #[serde(skip_serializing_if = "write_concern_is_empty")] + pub write_concern: Option, + + /// The selection criteria to use for all read operations in a transaction. + #[builder(default)] + #[serde(skip_serializing, rename = "readPreference")] + pub selection_criteria: Option, + + /// The maximum amount of time to allow a single commitTransaction to run. + #[builder(default)] + #[serde( + serialize_with = "serde_util::serialize_duration_option_as_int_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis", + rename(serialize = "maxTimeMS", deserialize = "maxCommitTimeMS"), + default + )] + pub max_commit_time: Option, +} + +/// Which server monitoring protocol to use. +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[non_exhaustive] +pub enum ServerMonitoringMode { + /// The client will use the streaming protocol when the server supports it and fall back to the + /// polling protocol otherwise. + Stream, + /// The client will use the polling protocol. + Poll, + /// The client will use the polling protocol when running on a FaaS platform and behave the + /// same as `Stream` otherwise. + Auto, +} diff --git a/src/client/options/bulk_write.rs b/src/client/options/bulk_write.rs new file mode 100644 index 000000000..7aaff23a2 --- /dev/null +++ b/src/client/options/bulk_write.rs @@ -0,0 +1,405 @@ +use std::borrow::Borrow; + +use macro_magic::export_tokens; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use typed_builder::TypedBuilder; + +use crate::{ + bson::{rawdoc, Array, Bson, Document, RawDocumentBuf}, + bson_compat::cstr, + bson_util::{get_or_prepend_id_field, replacement_document_check, update_document_check}, + error::Result, + options::{UpdateModifications, WriteConcern}, + serde_util::{serialize_bool_or_true, write_concern_is_empty}, + Collection, + Namespace, +}; + +/// The supported options for [`bulk_write`](crate::Client::bulk_write). +#[skip_serializing_none] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +#[export_tokens] +pub struct BulkWriteOptions { + /// Whether the operations should be performed in the order in which they were specified. If + /// true, no more writes will be performed if a single write fails. If false, writes will + /// continue to be attempted if a single write fails. + /// + /// Defaults to true. + #[serialize_always] + #[serde(serialize_with = "serialize_bool_or_true")] + pub ordered: Option, + + /// Whether document-level validation should be bypassed. + /// + /// Defaults to false. + pub bypass_document_validation: Option, + + /// An arbitrary comment to help trace the operation through the database profiler, currentOp + /// and logs. + pub comment: Option, + + /// A map of parameter names and values to apply to all operations within the bulk write. + /// Values must be constant or closed expressions that do not reference document fields. + /// Parameters can then be accessed as variables in an aggregate expression context (e.g. + /// "$$var"). + #[serde(rename = "let")] + pub let_vars: Option, + + /// The write concern to use for this operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] + pub write_concern: Option, +} + +/// A single write to be performed within a [`bulk_write`](crate::Client::bulk_write) operation. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize)] +#[serde(untagged)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum WriteModel { + InsertOne(InsertOneModel), + UpdateOne(UpdateOneModel), + UpdateMany(UpdateManyModel), + ReplaceOne(ReplaceOneModel), + DeleteOne(DeleteOneModel), + DeleteMany(DeleteManyModel), +} + +/// Inserts a single document. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct InsertOneModel { + /// The namespace on which the insert should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The document to insert. + #[builder(!default)] + pub document: Document, +} + +impl From for WriteModel { + fn from(model: InsertOneModel) -> Self { + Self::InsertOne(model) + } +} + +/// Updates a single document. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct UpdateOneModel { + /// The namespace on which the update should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The filter to use. The first document matching this filter will be updated. + #[builder(!default)] + pub filter: Document, + + /// The update to perform. + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub update: UpdateModifications, + + /// A set of filters specifying to which array elements an update should apply. + pub array_filters: Option, + + /// The collation to use. + pub collation: Option, + + /// The index to use. Specify either the index name as a string or the index key pattern. If + /// specified, then the query system will only consider plans using the hinted index. + pub hint: Option, + + /// Whether a new document should be created if no document matches the filter. + /// + /// Defaults to false. + pub upsert: Option, + + /// Specify which document the operation updates if the query matches multiple + /// documents. The first document matched by the sort order will be updated. + pub sort: Option, +} + +impl From for WriteModel { + fn from(model: UpdateOneModel) -> Self { + Self::UpdateOne(model) + } +} + +/// Updates multiple documents. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct UpdateManyModel { + /// The namespace on which the update should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The filter to use. All documents matching this filter will be updated. + #[builder(!default)] + pub filter: Document, + + /// The update to perform. + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub update: UpdateModifications, + + /// A set of filters specifying to which array elements an update should apply. + pub array_filters: Option, + + /// The collation to use. + pub collation: Option, + + /// The index to use. Specify either the index name as a string or the index key pattern. If + /// specified, then the query system will only consider plans using the hinted index. + pub hint: Option, + + /// Whether a new document should be created if no document matches the filter. + /// + /// Defaults to false. + pub upsert: Option, +} + +impl From for WriteModel { + fn from(model: UpdateManyModel) -> Self { + Self::UpdateMany(model) + } +} + +/// Replaces a single document. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct ReplaceOneModel { + /// The namespace on which the replace should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The filter to use. + #[builder(!default)] + pub filter: Document, + + /// The replacement document. + #[serde(rename(serialize = "updateMods"))] + #[builder(!default)] + pub replacement: Document, + + /// The collation to use. + pub collation: Option, + + /// The index to use. Specify either the index name as a string or the index key pattern. If + /// specified, then the query system will only consider plans using the hinted index. + pub hint: Option, + + /// Whether a new document should be created if no document matches the filter. + /// + /// Defaults to false. + pub upsert: Option, + + /// Specify which document the operation replaces if the query matches multiple + /// documents. The first document matched by the sort order will be replaced. + pub sort: Option, +} + +impl From for WriteModel { + fn from(model: ReplaceOneModel) -> Self { + Self::ReplaceOne(model) + } +} + +/// Deletes a single document. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct DeleteOneModel { + /// The namespace on which the delete should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The filter to use. The first document matching this filter will be deleted. + #[builder(!default)] + pub filter: Document, + + /// The collation to use. + pub collation: Option, + + /// The index to use. Specify either the index name as a string or the index key pattern. If + /// specified, then the query system will only consider plans using the hinted index. + pub hint: Option, +} + +impl From for WriteModel { + fn from(model: DeleteOneModel) -> Self { + Self::DeleteOne(model) + } +} + +/// Deletes multiple documents. +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, TypedBuilder)] +#[cfg_attr(test, derive(Deserialize))] +#[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct DeleteManyModel { + /// The namespace on which the delete should be performed. + #[serde(skip_serializing)] + #[builder(!default)] + pub namespace: Namespace, + + /// The filter to use. All documents matching this filter will be deleted. + pub filter: Document, + + /// The collation to use. + pub collation: Option, + + /// The index to use. Specify either the index name as a string or the index key pattern. If + /// specified, then the query system will only consider plans using the hinted index. + pub hint: Option, +} + +impl From for WriteModel { + fn from(model: DeleteManyModel) -> Self { + Self::DeleteMany(model) + } +} + +impl Collection +where + T: Send + Sync + Serialize, +{ + /// Constructs an [`InsertOneModel`] with this collection's namespace by serializing the + /// provided value into a [`Document`]. Returns an error if serialization fails. + /// + /// Note that the returned value must be provided to [`bulk_write`](crate::Client::bulk_write) + /// for the insert to be performed. + pub fn insert_one_model(&self, document: impl Borrow) -> Result { + let document = crate::bson_compat::serialize_to_document(document.borrow())?; + Ok(InsertOneModel::builder() + .namespace(self.namespace()) + .document(document) + .build()) + } + + /// Constructs a [`ReplaceOneModel`] with this collection's namespace by serializing the + /// provided value into a [`Document`]. Returns an error if serialization fails. + /// + /// Note that the returned value must be provided to [`bulk_write`](crate::Client::bulk_write) + /// for the replace to be performed. + pub fn replace_one_model( + &self, + filter: Document, + replacement: impl Borrow, + ) -> Result { + let replacement = crate::bson_compat::serialize_to_document(replacement.borrow())?; + Ok(ReplaceOneModel::builder() + .namespace(self.namespace()) + .filter(filter) + .replacement(replacement) + .build()) + } +} + +pub(crate) enum OperationType { + Insert, + Update, + Delete, +} + +impl WriteModel { + pub(crate) fn namespace(&self) -> &Namespace { + match self { + Self::InsertOne(model) => &model.namespace, + Self::UpdateOne(model) => &model.namespace, + Self::UpdateMany(model) => &model.namespace, + Self::ReplaceOne(model) => &model.namespace, + Self::DeleteOne(model) => &model.namespace, + Self::DeleteMany(model) => &model.namespace, + } + } + + pub(crate) fn operation_type(&self) -> OperationType { + match self { + Self::InsertOne(_) => OperationType::Insert, + Self::UpdateOne(_) | Self::UpdateMany(_) | Self::ReplaceOne(_) => OperationType::Update, + Self::DeleteOne(_) | Self::DeleteMany(_) => OperationType::Delete, + } + } + + /// Whether this operation should apply to all documents that match the filter. Returns None if + /// the operation does not use a filter. + pub(crate) fn multi(&self) -> Option { + match self { + Self::UpdateMany(_) | Self::DeleteMany(_) => Some(true), + Self::UpdateOne(_) | Self::ReplaceOne(_) | Self::DeleteOne(_) => Some(false), + Self::InsertOne(_) => None, + } + } + + pub(crate) fn operation_name(&self) -> &'static crate::bson_compat::CStr { + use crate::bson_compat::cstr; + match self.operation_type() { + OperationType::Insert => cstr!("insert"), + OperationType::Update => cstr!("update"), + OperationType::Delete => cstr!("delete"), + } + } + + /// Returns the operation-specific fields that should be included in this model's entry in the + /// ops array. Also returns an inserted ID if this is an insert operation. + pub(crate) fn get_ops_document_contents(&self) -> Result<(RawDocumentBuf, Option)> { + if let Self::UpdateOne(UpdateOneModel { update, .. }) + | Self::UpdateMany(UpdateManyModel { update, .. }) = self + { + if let UpdateModifications::Document(update_document) = update { + update_document_check(update_document)?; + } + } else if let Self::ReplaceOne(ReplaceOneModel { replacement, .. }) = self { + replacement_document_check(replacement)?; + } + + let (mut model_document, inserted_id) = match self { + Self::InsertOne(model) => { + let mut insert_document = RawDocumentBuf::from_document(&model.document)?; + let inserted_id = get_or_prepend_id_field(&mut insert_document)?; + (rawdoc! { "document": insert_document }, Some(inserted_id)) + } + _ => { + let model_document = crate::bson_compat::serialize_to_raw_document_buf(&self)?; + (model_document, None) + } + }; + + if let Some(multi) = self.multi() { + model_document.append(cstr!("multi"), multi); + } + + Ok((model_document, inserted_id)) + } +} diff --git a/src/client/options/mod.rs b/src/client/options/mod.rs deleted file mode 100644 index 8c101d1ae..000000000 --- a/src/client/options/mod.rs +++ /dev/null @@ -1,2696 +0,0 @@ -#[cfg(all(test, not(feature = "sync"), not(feature = "tokio-sync")))] -mod test; - -mod resolver_config; - -use std::{ - cmp::Ordering, - collections::HashSet, - convert::TryFrom, - fmt::{self, Display, Formatter, Write}, - hash::{Hash, Hasher}, - path::PathBuf, - str::FromStr, - sync::Arc, - time::Duration, -}; - -use bson::UuidRepresentation; -use derivative::Derivative; -use lazy_static::lazy_static; -use serde::{de::Unexpected, Deserialize, Deserializer, Serialize}; -use serde_with::skip_serializing_none; -use strsim::jaro_winkler; -use typed_builder::TypedBuilder; - -#[cfg(test)] -use crate::srv::LookupHosts; -use crate::{ - bson::{doc, Bson, Document}, - bson_util, - client::auth::{AuthMechanism, Credential}, - compression::Compressor, - concern::{Acknowledgment, ReadConcern, WriteConcern}, - error::{Error, ErrorKind, Result}, - event::{cmap::CmapEventHandler, command::CommandEventHandler, sdam::SdamEventHandler}, - options::ReadConcernLevel, - sdam::{verify_max_staleness, DEFAULT_HEARTBEAT_FREQUENCY, MIN_HEARTBEAT_FREQUENCY}, - selection_criteria::{ReadPreference, SelectionCriteria, TagSet}, - srv::{OriginalSrvInfo, SrvResolver}, -}; - -#[cfg(any(feature = "sync", feature = "tokio-sync"))] -use crate::runtime; - -pub use resolver_config::ResolverConfig; - -pub(crate) const DEFAULT_PORT: u16 = 27017; - -const URI_OPTIONS: &[&str] = &[ - "appname", - "authmechanism", - "authsource", - "authmechanismproperties", - "compressors", - "connecttimeoutms", - "directconnection", - "heartbeatfrequencyms", - "journal", - "localthresholdms", - "maxidletimems", - "maxstalenessseconds", - "maxpoolsize", - "minpoolsize", - "readconcernlevel", - "readpreference", - "readpreferencetags", - "replicaset", - "retrywrites", - "retryreads", - "serverselectiontimeoutms", - "sockettimeoutms", - "tls", - "ssl", - "tlsinsecure", - "tlsallowinvalidcertificates", - "tlscafile", - "tlscertificatekeyfile", - "uuidRepresentation", - "w", - "waitqueuetimeoutms", - "wtimeoutms", - "zlibcompressionlevel", -]; - -lazy_static! { - /// Reserved characters as defined by [Section 2.2 of RFC-3986](https://tools.ietf.org/html/rfc3986#section-2.2). - /// Usernames / passwords that contain these characters must instead include the URL encoded version of them when included - /// as part of the connection string. - static ref USERINFO_RESERVED_CHARACTERS: HashSet<&'static char> = { - [':', '/', '?', '#', '[', ']', '@'].iter().collect() - }; - - static ref ILLEGAL_DATABASE_CHARACTERS: HashSet<&'static char> = { - ['/', '\\', ' ', '"', '$', '.'].iter().collect() - }; -} - -/// An enum representing the address of a MongoDB server. -/// -/// Currently this just supports addresses that can be connected to over TCP, but alternative -/// address types may be supported in the future (e.g. Unix Domain Socket paths). -#[derive(Clone, Debug, Eq, Serialize)] -#[non_exhaustive] -pub enum ServerAddress { - /// A TCP/IP host and port combination. - Tcp { - /// The hostname or IP address where the MongoDB server can be found. - host: String, - - /// The TCP port that the MongoDB server is listening on. - /// - /// The default is 27017. - port: Option, - }, -} - -impl<'de> Deserialize<'de> for ServerAddress { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - let s: String = Deserialize::deserialize(deserializer)?; - Self::parse(s.as_str()) - .map_err(|e| ::custom(format!("{}", e))) - } -} - -impl Default for ServerAddress { - fn default() -> Self { - Self::Tcp { - host: "localhost".into(), - port: None, - } - } -} - -impl PartialEq for ServerAddress { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - ( - Self::Tcp { host, port }, - Self::Tcp { - host: other_host, - port: other_port, - }, - ) => host == other_host && port.unwrap_or(27017) == other_port.unwrap_or(27017), - } - } -} - -impl Hash for ServerAddress { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - match self { - Self::Tcp { host, port } => { - host.hash(state); - port.unwrap_or(27017).hash(state); - } - } - } -} - -impl FromStr for ServerAddress { - type Err = Error; - fn from_str(address: &str) -> Result { - ServerAddress::parse(address) - } -} - -impl ServerAddress { - /// Parses an address string into a `ServerAddress`. - pub fn parse(address: impl AsRef) -> Result { - let address = address.as_ref(); - let mut parts = address.split(':'); - let hostname = match parts.next() { - Some(part) => { - if part.is_empty() { - return Err(ErrorKind::InvalidArgument { - message: format!( - "invalid server address: \"{}\"; hostname cannot be empty", - address - ), - } - .into()); - } - part - } - None => { - return Err(ErrorKind::InvalidArgument { - message: format!("invalid server address: \"{}\"", address), - } - .into()) - } - }; - - let port = match parts.next() { - Some(part) => { - let port = u16::from_str(part).map_err(|_| ErrorKind::InvalidArgument { - message: format!( - "port must be valid 16-bit unsigned integer, instead got: {}", - part - ), - })?; - - if port == 0 { - return Err(ErrorKind::InvalidArgument { - message: format!( - "invalid server address: \"{}\"; port must be non-zero", - address - ), - } - .into()); - } - if parts.next().is_some() { - return Err(ErrorKind::InvalidArgument { - message: format!( - "address \"{}\" contains more than one unescaped ':'", - address - ), - } - .into()); - } - - Some(port) - } - None => None, - }; - - Ok(ServerAddress::Tcp { - host: hostname.to_lowercase(), - port, - }) - } - - #[cfg(all(test, not(feature = "sync"), not(feature = "tokio-sync")))] - pub(crate) fn into_document(self) -> Document { - match self { - Self::Tcp { host, port } => { - doc! { - "host": host, - "port": port.map(|i| Bson::Int32(i.into())).unwrap_or(Bson::Null) - } - } - } - } - - pub(crate) fn host(&self) -> &str { - match self { - Self::Tcp { host, .. } => host.as_str(), - } - } - - pub(crate) fn port(&self) -> Option { - match self { - Self::Tcp { port, .. } => *port, - } - } -} - -impl fmt::Display for ServerAddress { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Tcp { host, port } => { - write!(fmt, "{}:{}", host, port.unwrap_or(DEFAULT_PORT)) - } - } - } -} - -/// Specifies the server API version to declare -#[derive(Clone, Debug, PartialEq, Serialize)] -#[non_exhaustive] -pub enum ServerApiVersion { - /// Use API version 1. - #[serde(rename = "1")] - V1, -} - -impl FromStr for ServerApiVersion { - type Err = Error; - - fn from_str(str: &str) -> Result { - match str { - "1" => Ok(Self::V1), - _ => Err(ErrorKind::InvalidArgument { - message: format!("invalid server api version string: {}", str), - } - .into()), - } - } -} - -impl Display for ServerApiVersion { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::V1 => write!(f, "1"), - } - } -} - -impl<'de> Deserialize<'de> for ServerApiVersion { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - - ServerApiVersion::from_str(&s).map_err(|_| { - serde::de::Error::invalid_value(Unexpected::Str(&s), &"a valid version number") - }) - } -} - -/// Options used to declare a stable server API. For more information, see the [Stable API]( -/// https://www.mongodb.com/docs/v5.0/reference/stable-api/) manual page. -#[serde_with::skip_serializing_none] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)] -#[builder(field_defaults(setter(into)))] -#[non_exhaustive] -pub struct ServerApi { - /// The declared API version. - #[serde(rename = "apiVersion")] - pub version: ServerApiVersion, - - /// Whether the MongoDB server should reject all commands that are not part of the - /// declared API version. This includes command options and aggregation pipeline stages. - #[builder(default)] - #[serde(rename = "apiStrict")] - pub strict: Option, - - /// Whether the MongoDB server should return command failures when functionality that is - /// deprecated from the declared API version is used. - /// Note that at the time of this writing, no deprecations in version 1 exist. - #[builder(default)] - #[serde(rename = "apiDeprecationErrors")] - pub deprecation_errors: Option, -} - -/// Contains the options that can be used to create a new [`Client`](../struct.Client.html). -#[derive(Clone, Derivative, Deserialize, TypedBuilder)] -#[builder(field_defaults(setter(into)))] -#[derivative(Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ClientOptions { - /// The initial list of seeds that the Client should connect to. - /// - /// Note that by default, the driver will autodiscover other nodes in the cluster. To connect - /// directly to a single server (rather than autodiscovering the rest of the cluster), set the - /// `direct_connection` field to `true`. - #[builder(default_code = "vec![ServerAddress::Tcp { - host: \"localhost\".to_string(), - port: Some(27017), - }]")] - #[serde(default = "default_hosts")] - pub hosts: Vec, - - /// The application name that the Client will send to the server as part of the handshake. This - /// can be used in combination with the server logs to determine which Client is connected to a - /// server. - #[builder(default)] - pub app_name: Option, - - /// The compressors that the Client is willing to use in the order they are specified - /// in the configuration. The Client sends this list of compressors to the server. - /// The server responds with the intersection of its supported list of compressors. - /// The order of compressors indicates preference of compressors. - #[builder(default)] - #[serde(skip)] - pub compressors: Option>, - - /// The handler that should process all Connection Monitoring and Pooling events. See the - /// CmapEventHandler type documentation for more details. - #[derivative(Debug = "ignore", PartialEq = "ignore")] - #[builder(default)] - #[serde(skip)] - pub cmap_event_handler: Option>, - - /// The handler that should process all command-related events. See the CommandEventHandler - /// type documentation for more details. - /// - /// Note that monitoring command events may incur a performance penalty. - #[derivative(Debug = "ignore", PartialEq = "ignore")] - #[builder(default)] - #[serde(skip)] - pub command_event_handler: Option>, - - /// The connect timeout passed to each underlying TcpStream when attemtping to connect to the - /// server. - /// - /// The default value is 10 seconds. - #[builder(default)] - pub connect_timeout: Option, - - /// The credential to use for authenticating connections made by this client. - #[builder(default)] - pub credential: Option, - - /// Specifies whether the Client should directly connect to a single host rather than - /// autodiscover all servers in the cluster. - /// - /// The default value is false. - #[builder(default)] - pub direct_connection: Option, - - /// Extra information to append to the driver version in the metadata of the handshake with the - /// server. This should be used by libraries wrapping the driver, e.g. ODMs. - #[builder(default)] - pub driver_info: Option, - - /// The amount of time each monitoring thread should wait between performing server checks. - /// - /// The default value is 10 seconds. - #[builder(default)] - pub heartbeat_freq: Option, - - /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. - #[builder(default, setter(skip))] - #[serde(rename = "loadbalanced")] - pub load_balanced: Option, - - /// When running a read operation with a ReadPreference that allows selecting secondaries, - /// `local_threshold` is used to determine how much longer the average round trip time between - /// the driver and server is allowed compared to the least round trip time of all the suitable - /// servers. For example, if the average round trip times of the suitable servers are 5 ms, 10 - /// ms, and 15 ms, and the local threshold is 8 ms, then the first two servers are within the - /// latency window and could be chosen for the operation, but the last one is not. - /// - /// A value of zero indicates that there is no latency window, so only the server with the - /// lowest average round trip time is eligible. - /// - /// The default value is 15 ms. - #[builder(default)] - pub local_threshold: Option, - - /// The amount of time that a connection can remain idle in a connection pool before being - /// closed. A value of zero indicates that connections should not be closed due to being idle. - /// - /// By default, connections will not be closed due to being idle. - #[builder(default)] - pub max_idle_time: Option, - - /// The maximum amount of connections that the Client should allow to be created in a - /// connection pool for a given server. If an operation is attempted on a server while - /// `max_pool_size` connections are checked out, the operation will block until an in-progress - /// operation finishes and its connection is checked back into the pool. - /// - /// The default value is 10. - #[builder(default)] - pub max_pool_size: Option, - - /// The minimum number of connections that should be available in a server's connection pool at - /// a given time. If fewer than `min_pool_size` connections are in the pool, connections will - /// be added to the pool in the background until `min_pool_size` is reached. - /// - /// The default value is 0. - #[builder(default)] - pub min_pool_size: Option, - - /// Specifies the default read concern for operations performed on the Client. See the - /// ReadConcern type documentation for more details. - #[builder(default)] - pub read_concern: Option, - - /// The name of the replica set that the Client should connect to. - #[builder(default)] - pub repl_set_name: Option, - - /// Whether or not the client should retry a read operation if the operation fails. - /// - /// The default value is true. - #[builder(default)] - pub retry_reads: Option, - - /// Whether or not the client should retry a write operation if the operation fails. - /// - /// The default value is true. - #[builder(default)] - pub retry_writes: Option, - - /// The handler that should process all Server Discovery and Monitoring events. See the - /// [`SdamEventHandler`] type documentation for more details. - #[derivative(Debug = "ignore", PartialEq = "ignore")] - #[builder(default)] - #[serde(skip)] - pub sdam_event_handler: Option>, - - /// The default selection criteria for operations performed on the Client. See the - /// SelectionCriteria type documentation for more details. - #[builder(default)] - pub selection_criteria: Option, - - /// The declared API version for this client. - /// The declared API version is applied to all commands run through the client, including those - /// sent through any handle derived from the client. - /// - /// Specifying stable API options in the command document passed to `run_command` AND - /// declaring an API version on the client is not supported and is considered undefined - /// behaviour. To run any command with a different API version or without declaring one, create - /// a separate client that declares the appropriate API version. - /// - /// For more information, see the [Stable API]( - /// https://www.mongodb.com/docs/v5.0/reference/stable-api/) manual page. - #[builder(default)] - pub server_api: Option, - - /// The amount of time the Client should attempt to select a server for an operation before - /// timing outs - /// - /// The default value is 30 seconds. - #[builder(default)] - pub server_selection_timeout: Option, - - /// Default database for this client. - /// - /// By default, no default database is specified. - #[builder(default)] - pub default_database: Option, - - #[builder(default, setter(skip))] - #[derivative(Debug = "ignore")] - pub(crate) socket_timeout: Option, - - /// The TLS configuration for the Client to use in its connections with the server. - /// - /// By default, TLS is disabled. - #[builder(default)] - pub tls: Option, - - /// The maximum number of bytes that the driver should include in a tracing event - /// or log message's extended JSON string representation of a BSON document, e.g. a - /// command or reply from the server. - /// If truncation of a document at the exact specified length would occur in the middle - /// of a Unicode codepoint, the document will be truncated at the closest larger length - /// which falls on a boundary between codepoints. - /// Note that in cases where truncation occurs the output will not be valid JSON. - /// - /// The default value is 1000. - #[cfg(feature = "tracing-unstable")] - #[builder(default)] - pub tracing_max_document_length_bytes: Option, - - /// Specifies the default write concern for operations performed on the Client. See the - /// WriteConcern type documentation for more details. - #[builder(default)] - pub write_concern: Option, - - /// Information from the SRV URI that generated these client options, if applicable. - #[builder(default, setter(skip))] - #[serde(skip)] - #[derivative(Debug = "ignore")] - pub(crate) original_srv_info: Option, - - #[cfg(test)] - #[builder(default, setter(skip))] - #[derivative(Debug = "ignore")] - pub(crate) original_uri: Option, - - /// Configuration of the trust-dns resolver used for SRV and TXT lookups. - /// By default, the host system's resolver configuration will be used. - /// - /// On Windows, there is a known performance issue in trust-dns with using the default system - /// configuration, so a custom configuration is recommended. - #[builder(default, setter(skip))] - #[serde(skip)] - #[derivative(Debug = "ignore")] - pub(crate) resolver_config: Option, - - /// Control test behavior of the client. - #[cfg(test)] - #[builder(default, setter(skip))] - #[serde(skip)] - #[derivative(PartialEq = "ignore")] - pub(crate) test_options: Option, -} - -#[cfg(test)] -#[derive(Debug, Clone, Default)] -pub(crate) struct TestOptions { - /// Override MIN_HEARTBEAT_FREQUENCY. - pub(crate) min_heartbeat_freq: Option, - - /// Disable server and SRV-polling monitor threads. - pub(crate) disable_monitoring_threads: bool, - - /// Mock response for `SrvPollingMonitor::lookup_hosts`. - pub(crate) mock_lookup_hosts: Option>, -} - -fn default_hosts() -> Vec { - vec![ServerAddress::default()] -} - -impl Default for ClientOptions { - fn default() -> Self { - Self::builder().build() - } -} - -#[cfg(test)] -impl Serialize for ClientOptions { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - #[derive(Serialize)] - struct ClientOptionsHelper<'a> { - appname: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - connecttimeoutms: &'a Option, - - #[serde(flatten, serialize_with = "Credential::serialize_for_client_options")] - credential: &'a Option, - - directconnection: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - heartbeatfrequencyms: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - localthresholdms: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - maxidletimems: &'a Option, - - maxpoolsize: &'a Option, - - minpoolsize: &'a Option, - - #[serde(flatten, serialize_with = "ReadConcern::serialize_for_client_options")] - readconcern: &'a Option, - - replicaset: &'a Option, - - retryreads: &'a Option, - - retrywrites: &'a Option, - - #[serde( - flatten, - serialize_with = "SelectionCriteria::serialize_for_client_options" - )] - selectioncriteria: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - serverselectiontimeoutms: &'a Option, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - sockettimeoutms: &'a Option, - - #[serde(flatten, serialize_with = "Tls::serialize_for_client_options")] - tls: &'a Option, - - #[serde(flatten, serialize_with = "WriteConcern::serialize_for_client_options")] - writeconcern: &'a Option, - - zlibcompressionlevel: &'a Option, - - loadbalanced: &'a Option, - } - - let client_options = ClientOptionsHelper { - appname: &self.app_name, - connecttimeoutms: &self.connect_timeout, - credential: &self.credential, - directconnection: &self.direct_connection, - heartbeatfrequencyms: &self.heartbeat_freq, - localthresholdms: &self.local_threshold, - maxidletimems: &self.max_idle_time, - maxpoolsize: &self.max_pool_size, - minpoolsize: &self.min_pool_size, - readconcern: &self.read_concern, - replicaset: &self.repl_set_name, - retryreads: &self.retry_reads, - retrywrites: &self.retry_writes, - selectioncriteria: &self.selection_criteria, - serverselectiontimeoutms: &self.server_selection_timeout, - sockettimeoutms: &self.socket_timeout, - tls: &self.tls, - writeconcern: &self.write_concern, - loadbalanced: &self.load_balanced, - zlibcompressionlevel: &None, - }; - - client_options.serialize(serializer) - } -} - -/// Contains the options that can be set via a MongoDB connection string. -/// -/// The format of a MongoDB connection string is described [here](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats). -#[derive(Debug, Default, PartialEq)] -#[non_exhaustive] -pub struct ConnectionString { - /// The initial list of seeds that the Client should connect to, or a DNS name used for SRV - /// lookup of the initial seed list. - /// - /// Note that by default, the driver will autodiscover other nodes in the cluster. To connect - /// directly to a single server (rather than autodiscovering the rest of the cluster), set the - /// `direct_connection` field to `true`. - pub host_info: HostInfo, - - /// The application name that the Client will send to the server as part of the handshake. This - /// can be used in combination with the server logs to determine which Client is connected to a - /// server. - pub app_name: Option, - - /// The TLS configuration for the Client to use in its connections with the server. - /// - /// By default, TLS is disabled. - pub tls: Option, - - /// The amount of time each monitoring thread should wait between performing server checks. - /// - /// The default value is 10 seconds. - pub heartbeat_frequency: Option, - - /// When running a read operation with a ReadPreference that allows selecting secondaries, - /// `local_threshold` is used to determine how much longer the average round trip time between - /// the driver and server is allowed compared to the least round trip time of all the suitable - /// servers. For example, if the average round trip times of the suitable servers are 5 ms, 10 - /// ms, and 15 ms, and the local threshold is 8 ms, then the first two servers are within the - /// latency window and could be chosen for the operation, but the last one is not. - /// - /// A value of zero indicates that there is no latency window, so only the server with the - /// lowest average round trip time is eligible. - /// - /// The default value is 15 ms. - pub local_threshold: Option, - - /// Specifies the default read concern for operations performed on the Client. See the - /// ReadConcern type documentation for more details. - pub read_concern: Option, - - /// The name of the replica set that the Client should connect to. - pub replica_set: Option, - - /// Specifies the default write concern for operations performed on the Client. See the - /// WriteConcern type documentation for more details. - pub write_concern: Option, - - /// The amount of time the Client should attempt to select a server for an operation before - /// timing outs - /// - /// The default value is 30 seconds. - pub server_selection_timeout: Option, - - /// The maximum amount of connections that the Client should allow to be created in a - /// connection pool for a given server. If an operation is attempted on a server while - /// `max_pool_size` connections are checked out, the operation will block until an in-progress - /// operation finishes and its connection is checked back into the pool. - /// - /// The default value is 100. - pub max_pool_size: Option, - - /// The minimum number of connections that should be available in a server's connection pool at - /// a given time. If fewer than `min_pool_size` connections are in the pool, connections will - /// be added to the pool in the background until `min_pool_size` is reached. - /// - /// The default value is 0. - pub min_pool_size: Option, - - /// The amount of time that a connection can remain idle in a connection pool before being - /// closed. A value of zero indicates that connections should not be closed due to being idle. - /// - /// By default, connections will not be closed due to being idle. - pub max_idle_time: Option, - - /// The compressors that the Client is willing to use in the order they are specified - /// in the configuration. The Client sends this list of compressors to the server. - /// The server responds with the intersection of its supported list of compressors. - /// The order of compressors indicates preference of compressors. - pub compressors: Option>, - - /// The connect timeout passed to each underlying TcpStream when attempting to connect to the - /// server. - /// - /// The default value is 10 seconds. - pub connect_timeout: Option, - - /// Whether or not the client should retry a read operation if the operation fails. - /// - /// The default value is true. - pub retry_reads: Option, - - /// Whether or not the client should retry a write operation if the operation fails. - /// - /// The default value is true. - pub retry_writes: Option, - - /// Specifies whether the Client should directly connect to a single host rather than - /// autodiscover all servers in the cluster. - /// - /// The default value is false. - pub direct_connection: Option, - - /// The credential to use for authenticating connections made by this client. - pub credential: Option, - - /// Default database for this client. - /// - /// By default, no default database is specified. - pub default_database: Option, - - /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. - pub load_balanced: Option, - - /// Amount of time spent attempting to send or receive on a socket before timing out; note that - /// this only applies to application operations, not server discovery and monitoring. - pub socket_timeout: Option, - - /// Default read preference for the client. - pub read_preference: Option, - - /// The [`UuidRepresentation`] to use when decoding [`Binary`](bson::Binary) values with the - /// [`UuidOld`](bson::spec::BinarySubtype::UuidOld) subtype. This is not used by the - /// driver; client code can use this when deserializing relevant values with - /// [`Binary::to_uuid_with_representation`](bson::binary::Binary::to_uuid_with_representation). - pub uuid_representation: Option, - - wait_queue_timeout: Option, - tls_insecure: Option, - - #[cfg(test)] - original_uri: String, -} - -/// Elements from the connection string that are not top-level fields in `ConnectionString`. -#[derive(Default)] -struct ConnectionStringParts { - read_preference_tags: Option>, - max_staleness: Option, - auth_mechanism: Option, - auth_mechanism_properties: Option, - zlib_compression: Option, - auth_source: Option, -} - -/// Specification for mongodb server connections. -#[derive(Debug, PartialEq, Clone)] -#[non_exhaustive] -pub enum HostInfo { - /// A set of addresses. - HostIdentifiers(Vec), - /// A DNS record for SRV lookup. - DnsRecord(String), -} - -impl Default for HostInfo { - fn default() -> Self { - Self::HostIdentifiers(vec![]) - } -} - -impl HostInfo { - async fn resolve(self, resolver_config: Option) -> Result { - Ok(match self { - Self::HostIdentifiers(hosts) => ResolvedHostInfo::HostIdentifiers(hosts), - Self::DnsRecord(hostname) => { - let mut resolver = - SrvResolver::new(resolver_config.clone().map(|config| config.inner)).await?; - let config = resolver.resolve_client_options(&hostname).await?; - ResolvedHostInfo::DnsRecord { hostname, config } - } - }) - } -} - -enum ResolvedHostInfo { - HostIdentifiers(Vec), - DnsRecord { - hostname: String, - config: crate::srv::ResolvedConfig, - }, -} - -/// Specifies whether TLS configuration should be used with the operations that the -/// [`Client`](../struct.Client.html) performs. -#[derive(Clone, Debug, Deserialize, PartialEq)] -pub enum Tls { - /// Enable TLS with the specified options. - Enabled(TlsOptions), - - /// Disable TLS. - Disabled, -} - -impl From for Tls { - fn from(options: TlsOptions) -> Self { - Self::Enabled(options) - } -} - -impl From for Option { - fn from(options: TlsOptions) -> Self { - Some(Tls::Enabled(options)) - } -} - -impl Tls { - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - tls: &Option, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - match tls { - Some(Tls::Enabled(tls_options)) => { - TlsOptions::serialize_for_client_options(tls_options, serializer) - } - _ => serializer.serialize_none(), - } - } -} - -/// Specifies the TLS configuration that the [`Client`](../struct.Client.html) should use. -#[derive(Clone, Debug, Default, Deserialize, PartialEq, TypedBuilder)] -#[builder(field_defaults(default, setter(into)))] -#[non_exhaustive] -pub struct TlsOptions { - /// Whether or not the [`Client`](../struct.Client.html) should return an error if the server - /// presents an invalid certificate. This setting should _not_ be set to `true` in - /// production; it should only be used for testing. - /// - /// The default value is to error when the server presents an invalid certificate. - pub allow_invalid_certificates: Option, - - /// The path to the CA file that the [`Client`](../struct.Client.html) should use for TLS. If - /// none is specified, then the driver will use the Mozilla root certificates from the - /// `webpki-roots` crate. - pub ca_file_path: Option, - - /// The path to the certificate file that the [`Client`](../struct.Client.html) should present - /// to the server to verify its identify. If none is specified, then the - /// [`Client`](../struct.Client.html) will not attempt to verify its identity to the - /// server. - pub cert_key_file_path: Option, - - /// Whether or not the [`Client`](../struct.Client.html) should return an error if the hostname - /// is invalid. - /// - /// The default value is to error on invalid hostnames. - #[cfg(feature = "openssl-tls")] - pub allow_invalid_hostnames: Option, -} - -impl TlsOptions { - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - tls_options: &TlsOptions, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - #[derive(Serialize)] - struct TlsOptionsHelper<'a> { - tls: bool, - tlscafile: Option<&'a str>, - tlscertificatekeyfile: Option<&'a str>, - tlsallowinvalidcertificates: Option, - } - - let state = TlsOptionsHelper { - tls: true, - tlscafile: tls_options - .ca_file_path - .as_ref() - .map(|s| s.to_str().unwrap()), - tlscertificatekeyfile: tls_options - .cert_key_file_path - .as_ref() - .map(|s| s.to_str().unwrap()), - tlsallowinvalidcertificates: tls_options.allow_invalid_certificates, - }; - state.serialize(serializer) - } -} - -/// Extra information to append to the driver version in the metadata of the handshake with the -/// server. This should be used by libraries wrapping the driver, e.g. ODMs. -#[derive(Clone, Debug, Deserialize, TypedBuilder, PartialEq)] -#[builder(field_defaults(setter(into)))] -#[non_exhaustive] -pub struct DriverInfo { - /// The name of the library wrapping the driver. - pub name: String, - - /// The version of the library wrapping the driver. - #[builder(default)] - pub version: Option, - - /// Optional platform information for the wrapping driver. - #[builder(default)] - pub platform: Option, -} - -impl ClientOptions { - /// Creates a new ClientOptions with the `original_srv_hostname` field set to the testing value - /// used in the SRV tests. - #[cfg(test)] - pub(crate) fn new_srv() -> Self { - Self { - original_srv_info: Some(OriginalSrvInfo { - hostname: "localhost.test.test.build.10gen.cc".into(), - min_ttl: Duration::from_secs(60), - }), - ..Default::default() - } - } - - /// Parses a MongoDB connection string into a [`ClientOptions`] struct. If the string is - /// malformed or one of the options has an invalid value, an error will be returned. - /// - /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done as - /// part of this method. - /// - /// The format of a MongoDB connection string is described [here](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats). - /// - /// Note that [default_database](ClientOptions::default_database) will be set from - /// `/defaultauthdb` in connection string. - /// - /// The following options are supported in the options query string: - /// - /// * `appName`: maps to the `app_name` field - /// * `authMechanism`: maps to the `mechanism` field of the `credential` field - /// * `authSource`: maps to the `source` field of the `credential` field - /// * `authMechanismProperties`: maps to the `mechanism_properties` field of the `credential` - /// field - /// * `compressors`: maps to the `compressors` field - /// * `connectTimeoutMS`: maps to the `connect_timeout` field - /// * `direct`: maps to the `direct` field - /// * `heartbeatFrequencyMS`: maps to the `heartbeat_frequency` field - /// * `journal`: maps to the `journal` field of the `write_concern` field - /// * `localThresholdMS`: maps to the `local_threshold` field - /// * `maxIdleTimeMS`: maps to the `max_idle_time` field - /// * `maxStalenessSeconds`: maps to the `max_staleness` field of the `selection_criteria` - /// field - /// * `maxPoolSize`: maps to the `max_pool_size` field - /// * `minPoolSize`: maps to the `min_pool_size` field - /// * `readConcernLevel`: maps to the `read_concern` field - /// * `readPreferenceField`: maps to the ReadPreference enum variant of the - /// `selection_criteria` field - /// * `readPreferenceTags`: maps to the `tags` field of the `selection_criteria` field. Note - /// that this option can appear more than once; each instance will be mapped to a separate - /// tag set - /// * `replicaSet`: maps to the `repl_set_name` field - /// * `retryWrites`: not yet implemented - /// * `retryReads`: maps to the `retry_reads` field - /// * `serverSelectionTimeoutMS`: maps to the `server_selection_timeout` field - /// * `socketTimeoutMS`: unsupported, does not map to any field - /// * `ssl`: an alias of the `tls` option - /// * `tls`: maps to the TLS variant of the `tls` field`. - /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; currently is just - /// an alias of `tlsAllowInvalidCertificates`, but more behavior may be added to this option - /// in the future - /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalidCertificates` field of the - /// `tls` field - /// * `tlsCAFile`: maps to the `ca_file_path` field of the `tls` field - /// * `tlsCertificateKeyFile`: maps to the `cert_key_file_path` field of the `tls` field - /// * `w`: maps to the `w` field of the `write_concern` field - /// * `waitQueueTimeoutMS`: unsupported, does not map to any field - /// * `wTimeoutMS`: maps to the `w_timeout` field of the `write_concern` field - /// * `zlibCompressionLevel`: maps to the `level` field of the `Compressor::Zlib` variant - /// (which requires the `zlib-compression` feature flag) of the [`Compressor`] enum - /// - /// Note: if the `sync` feature is enabled, then this method will be replaced with [the sync - /// version](#method.parse-1). - #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] - pub async fn parse(s: impl AsRef) -> Result { - Self::parse_uri(s, None).await - } - - /// This method will be present if the `sync` feature is enabled. It's otherwise identical to - /// [the async version](#method.parse) - #[cfg(any(feature = "sync", feature = "tokio-sync"))] - pub fn parse(s: impl AsRef) -> Result { - runtime::block_on(Self::parse_uri(s.as_ref(), None)) - } - - /// This method is the same as `parse`, but is provided to make the async version available when - /// the `sync` feature is enabled. - pub async fn parse_async(s: impl AsRef) -> Result { - Self::parse_uri(s, None).await - } - - /// Parses a MongoDB connection string into a `ClientOptions` struct. - /// If the string is malformed or one of the options has an invalid value, an error will be - /// returned. - /// - /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the - /// provided `ResolverConfig` as part of this method. - /// - /// The format of a MongoDB connection string is described [here](https://www.mongodb.com/docs/manual/reference/connection-string/#connection-string-formats). - /// - /// See the docstring on `ClientOptions::parse` for information on how the various URI options - /// map to fields on `ClientOptions`. - /// - /// Note: if the `sync` feature is enabled, then this method will be replaced with [the sync - /// version](#method.parse_with_resolver_config-1). - #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] - pub async fn parse_with_resolver_config( - uri: impl AsRef, - resolver_config: ResolverConfig, - ) -> Result { - Self::parse_uri(uri, Some(resolver_config)).await - } - - /// This method will be present if the `sync` feature is enabled. It's otherwise identical to - /// [the async version](#method.parse_with_resolver_config) - #[cfg(any(feature = "sync", feature = "tokio-sync"))] - pub fn parse_with_resolver_config(uri: &str, resolver_config: ResolverConfig) -> Result { - runtime::block_on(Self::parse_uri(uri, Some(resolver_config))) - } - - /// Populate this `ClientOptions` from the given URI, optionally using the resolver config for - /// DNS lookups. - pub(crate) async fn parse_uri( - uri: impl AsRef, - resolver_config: Option, - ) -> Result { - Self::parse_connection_string_internal(ConnectionString::parse(uri)?, resolver_config).await - } - - /// Creates a `ClientOptions` from the given `ConnectionString`. - /// - /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the - /// provided `ResolverConfig` as part of this method. - pub async fn parse_connection_string_with_resolver_config( - conn_str: ConnectionString, - resolver_config: ResolverConfig, - ) -> Result { - Self::parse_connection_string_internal(conn_str, Some(resolver_config)).await - } - - /// Creates a `ClientOptions` from the given `ConnectionString`. - pub async fn parse_connection_string(conn_str: ConnectionString) -> Result { - Self::parse_connection_string_internal(conn_str, None).await - } - - async fn parse_connection_string_internal( - mut conn_str: ConnectionString, - resolver_config: Option, - ) -> Result { - let auth_source_present = conn_str - .credential - .as_ref() - .and_then(|cred| cred.source.as_ref()) - .is_some(); - let host_info = std::mem::take(&mut conn_str.host_info); - let mut options = Self::from_connection_string(conn_str); - options.resolver_config = resolver_config.clone(); - - let resolved = host_info.resolve(resolver_config).await?; - options.hosts = match resolved { - ResolvedHostInfo::HostIdentifiers(hosts) => hosts, - ResolvedHostInfo::DnsRecord { - hostname, - mut config, - } => { - // Save the original SRV info to allow mongos polling. - options.original_srv_info = OriginalSrvInfo { - hostname, - min_ttl: config.min_ttl, - } - .into(); - - // Enable TLS unless the user explicitly disabled it. - if options.tls.is_none() { - options.tls = Some(Tls::Enabled(Default::default())); - } - - // Set the authSource TXT option found during SRV lookup unless the user already set - // it. Note that this _does_ override the default database specified - // in the URI, since it is supposed to be overriden by authSource. - if !auth_source_present { - if let Some(auth_source) = config.auth_source.take() { - if let Some(ref mut credential) = options.credential { - credential.source = Some(auth_source); - } - } - } - - // Set the replica set name TXT option found during SRV lookup unless the user - // already set it. - if options.repl_set_name.is_none() { - if let Some(replica_set) = config.replica_set.take() { - options.repl_set_name = Some(replica_set); - } - } - - if options.load_balanced.is_none() { - options.load_balanced = config.load_balanced; - } - - // Set the ClientOptions hosts to those found during the SRV lookup. - config.hosts - } - }; - - options.validate()?; - Ok(options) - } - - /// Creates a `ClientOptions` from the given `ConnectionString`. - #[cfg(any(feature = "sync", feature = "tokio-sync"))] - pub fn parse_connection_string_sync(conn_str: ConnectionString) -> Result { - crate::runtime::block_on(Self::parse_connection_string_internal(conn_str, None)) - } - - /// Creates a `ClientOptions` from the given `ConnectionString`. - /// - /// In the case that "mongodb+srv" is used, SRV and TXT record lookups will be done using the - /// provided `ResolverConfig` as part of this method. - #[cfg(any(feature = "sync", feature = "tokio-sync"))] - pub fn parse_connection_string_with_resolver_config_sync( - conn_str: ConnectionString, - resolver_config: ResolverConfig, - ) -> Result { - crate::runtime::block_on(Self::parse_connection_string_internal( - conn_str, - Some(resolver_config), - )) - } - - fn from_connection_string(conn_str: ConnectionString) -> Self { - let mut credential = conn_str.credential; - // Populate default auth source, if needed. - let db = &conn_str.default_database; - if let Some(credential) = credential.as_mut() { - if credential.source.is_none() { - credential.source = match &credential.mechanism { - Some(mechanism) => Some(mechanism.default_source(db.as_deref()).into()), - None => { - // If credentials exist (i.e. username is specified) but no mechanism, the - // default source is chosen from the following list in - // order (skipping null ones): authSource option, connection string db, - // SCRAM default (i.e. "admin"). - db.clone().or_else(|| Some("admin".into())) - } - }; - } - } - Self { - hosts: vec![], - app_name: conn_str.app_name, - tls: conn_str.tls, - heartbeat_freq: conn_str.heartbeat_frequency, - local_threshold: conn_str.local_threshold, - read_concern: conn_str.read_concern, - selection_criteria: conn_str.read_preference.map(Into::into), - repl_set_name: conn_str.replica_set, - write_concern: conn_str.write_concern, - max_pool_size: conn_str.max_pool_size, - min_pool_size: conn_str.min_pool_size, - max_idle_time: conn_str.max_idle_time, - server_selection_timeout: conn_str.server_selection_timeout, - compressors: conn_str.compressors, - connect_timeout: conn_str.connect_timeout, - retry_reads: conn_str.retry_reads, - retry_writes: conn_str.retry_writes, - socket_timeout: conn_str.socket_timeout, - direct_connection: conn_str.direct_connection, - default_database: conn_str.default_database, - driver_info: None, - credential, - cmap_event_handler: None, - command_event_handler: None, - original_srv_info: None, - #[cfg(test)] - original_uri: Some(conn_str.original_uri), - resolver_config: None, - server_api: None, - load_balanced: conn_str.load_balanced, - sdam_event_handler: None, - #[cfg(test)] - test_options: None, - #[cfg(feature = "tracing-unstable")] - tracing_max_document_length_bytes: None, - } - } - - pub(crate) fn tls_options(&self) -> Option { - match self.tls { - Some(Tls::Enabled(ref opts)) => Some(opts.clone()), - _ => None, - } - } - - /// Ensure the options set are valid, returning an error describing the problem if they are not. - pub(crate) fn validate(&self) -> Result<()> { - if let Some(true) = self.direct_connection { - if self.hosts.len() > 1 { - return Err(ErrorKind::InvalidArgument { - message: "cannot specify multiple seeds with directConnection=true".to_string(), - } - .into()); - } - } - - if let Some(ref write_concern) = self.write_concern { - write_concern.validate()?; - } - - if self.load_balanced.unwrap_or(false) { - if self.hosts.len() > 1 { - return Err(ErrorKind::InvalidArgument { - message: "cannot specify multiple seeds with loadBalanced=true".to_string(), - } - .into()); - } - if self.repl_set_name.is_some() { - return Err(ErrorKind::InvalidArgument { - message: "cannot specify replicaSet with loadBalanced=true".to_string(), - } - .into()); - } - if self.direct_connection == Some(true) { - return Err(ErrorKind::InvalidArgument { - message: "cannot specify directConnection=true with loadBalanced=true" - .to_string(), - } - .into()); - } - } - - if let Some(ref compressors) = self.compressors { - for compressor in compressors { - compressor.validate()?; - } - } - - if let Some(0) = self.max_pool_size { - return Err(Error::invalid_argument("cannot specify maxPoolSize=0")); - } - - if let Some(SelectionCriteria::ReadPreference(ref rp)) = self.selection_criteria { - if let Some(max_staleness) = rp.max_staleness() { - verify_max_staleness( - max_staleness, - self.heartbeat_freq.unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY), - )?; - } - } - - if let Some(heartbeat_frequency) = self.heartbeat_freq { - if heartbeat_frequency < self.min_heartbeat_frequency() { - return Err(ErrorKind::InvalidArgument { - message: format!( - "'heartbeat_freq' must be at least {}ms, but {}ms was given", - self.min_heartbeat_frequency().as_millis(), - heartbeat_frequency.as_millis() - ), - } - .into()); - } - } - - Ok(()) - } - - /// Applies the options in other to these options if a value is not already present - #[cfg(test)] - pub(crate) fn merge(&mut self, other: ClientOptions) { - if self.hosts.is_empty() { - self.hosts = other.hosts; - } - merge_options!( - other, - self, - [ - app_name, - compressors, - cmap_event_handler, - command_event_handler, - connect_timeout, - credential, - direct_connection, - driver_info, - heartbeat_freq, - load_balanced, - local_threshold, - max_idle_time, - max_pool_size, - min_pool_size, - read_concern, - repl_set_name, - retry_reads, - retry_writes, - selection_criteria, - server_api, - server_selection_timeout, - socket_timeout, - test_options, - tls, - write_concern, - original_srv_info, - original_uri - ] - ); - } - - #[cfg(test)] - pub(crate) fn test_options_mut(&mut self) -> &mut TestOptions { - self.test_options.get_or_insert_with(Default::default) - } - - pub(crate) fn min_heartbeat_frequency(&self) -> Duration { - #[cfg(test)] - { - self.test_options - .as_ref() - .and_then(|to| to.min_heartbeat_freq) - .unwrap_or(MIN_HEARTBEAT_FREQUENCY) - } - - #[cfg(not(test))] - { - MIN_HEARTBEAT_FREQUENCY - } - } -} - -/// Splits a string into a section before a given index and a section exclusively after the index. -/// Empty portions are returned as `None`. -fn exclusive_split_at(s: &str, i: usize) -> (Option<&str>, Option<&str>) { - let (l, r) = s.split_at(i); - - let lout = if !l.is_empty() { Some(l) } else { None }; - let rout = if r.len() > 1 { Some(&r[1..]) } else { None }; - - (lout, rout) -} - -fn percent_decode(s: &str, err_message: &str) -> Result { - match percent_encoding::percent_decode_str(s).decode_utf8() { - Ok(result) => Ok(result.to_string()), - Err(_) => Err(ErrorKind::InvalidArgument { - message: err_message.to_string(), - } - .into()), - } -} - -fn validate_userinfo(s: &str, userinfo_type: &str) -> Result<()> { - if s.chars().any(|c| USERINFO_RESERVED_CHARACTERS.contains(&c)) { - return Err(ErrorKind::InvalidArgument { - message: format!("{} must be URL encoded", userinfo_type), - } - .into()); - } - - // All instances of '%' in the username must be part of an percent-encoded substring. This means - // that there must be two hexidecimal digits following any '%' in the username. - if s.split('%') - .skip(1) - .any(|part| part.len() < 2 || part[0..2].chars().any(|c| !c.is_ascii_hexdigit())) - { - return Err(ErrorKind::InvalidArgument { - message: "username/password cannot contain unescaped %".to_string(), - } - .into()); - } - - Ok(()) -} - -impl ConnectionString { - /// Parses a MongoDB connection string into a [`ConnectionString`] struct. If the string is - /// malformed or one of the options has an invalid value, an error will be returned. - pub fn parse(s: impl AsRef) -> Result { - let s = s.as_ref(); - let end_of_scheme = match s.find("://") { - Some(index) => index, - None => { - return Err(ErrorKind::InvalidArgument { - message: "connection string contains no scheme".to_string(), - } - .into()) - } - }; - - let srv = match &s[..end_of_scheme] { - "mongodb" => false, - "mongodb+srv" => true, - _ => { - return Err(ErrorKind::InvalidArgument { - message: format!("invalid connection string scheme: {}", &s[..end_of_scheme]), - } - .into()) - } - }; - - let after_scheme = &s[end_of_scheme + 3..]; - - let (pre_slash, post_slash) = match after_scheme.find('/') { - Some(slash_index) => match exclusive_split_at(after_scheme, slash_index) { - (Some(section), o) => (section, o), - (None, _) => { - return Err(ErrorKind::InvalidArgument { - message: "missing hosts".to_string(), - } - .into()) - } - }, - None => { - if after_scheme.find('?').is_some() { - return Err(ErrorKind::InvalidArgument { - message: "Missing delimiting slash between hosts and options".to_string(), - } - .into()); - } - (after_scheme, None) - } - }; - - let (database, options_section) = match post_slash { - Some(section) => match section.find('?') { - Some(index) => exclusive_split_at(section, index), - None => (post_slash, None), - }, - None => (None, None), - }; - - let db = match database { - Some(db) => { - let decoded = percent_decode(db, "database name must be URL encoded")?; - if decoded - .chars() - .any(|c| ILLEGAL_DATABASE_CHARACTERS.contains(&c)) - { - return Err(ErrorKind::InvalidArgument { - message: "illegal character in database name".to_string(), - } - .into()); - } - Some(decoded) - } - None => None, - }; - - let (authentication_requested, cred_section, hosts_section) = match pre_slash.rfind('@') { - Some(index) => { - // if '@' is in the host section, it MUST be interpreted as a request for - // authentication, even if the credentials are empty. - let (creds, hosts) = exclusive_split_at(pre_slash, index); - match hosts { - Some(hs) => (true, creds, hs), - None => { - return Err(ErrorKind::InvalidArgument { - message: "missing hosts".to_string(), - } - .into()) - } - } - } - None => (false, None, pre_slash), - }; - - let (username, password) = match cred_section { - Some(creds) => match creds.find(':') { - Some(index) => match exclusive_split_at(creds, index) { - (username, None) => (username, Some("")), - (username, password) => (username, password), - }, - None => (Some(creds), None), // Lack of ":" implies whole string is username - }, - None => (None, None), - }; - - let host_list: Result> = - hosts_section.split(',').map(ServerAddress::parse).collect(); - - let host_list = host_list?; - - let hosts = if srv { - if host_list.len() != 1 { - return Err(ErrorKind::InvalidArgument { - message: "exactly one host must be specified with 'mongodb+srv'".into(), - } - .into()); - } - // Unwrap safety: the `len` check above guarantees this can't fail. - let ServerAddress::Tcp { host, port } = host_list.into_iter().next().unwrap(); - - if port.is_some() { - return Err(ErrorKind::InvalidArgument { - message: "a port cannot be specified with 'mongodb+srv'".into(), - } - .into()); - } - HostInfo::DnsRecord(host) - } else { - HostInfo::HostIdentifiers(host_list) - }; - - let mut conn_str = ConnectionString { - host_info: hosts, - #[cfg(test)] - original_uri: s.into(), - ..Default::default() - }; - - let mut parts = if let Some(opts) = options_section { - conn_str.parse_options(opts)? - } else { - ConnectionStringParts::default() - }; - - // Set username and password. - if let Some(u) = username { - let mut credential = conn_str.credential.get_or_insert_with(Default::default); - validate_userinfo(u, "username")?; - let decoded_u = percent_decode(u, "username must be URL encoded")?; - - credential.username = Some(decoded_u); - - if let Some(pass) = password { - validate_userinfo(pass, "password")?; - let decoded_p = percent_decode(pass, "password must be URL encoded")?; - credential.password = Some(decoded_p) - } - } - - if parts.auth_source.as_deref() == Some("") { - return Err(ErrorKind::InvalidArgument { - message: "empty authSource provided".to_string(), - } - .into()); - } - - match parts.auth_mechanism { - Some(ref mechanism) => { - let mut credential = conn_str.credential.get_or_insert_with(Default::default); - credential.source = parts.auth_source; - - if let Some(mut doc) = parts.auth_mechanism_properties.take() { - match doc.remove("CANONICALIZE_HOST_NAME") { - Some(Bson::String(s)) => { - let val = match &s.to_lowercase()[..] { - "true" => Bson::Boolean(true), - "false" => Bson::Boolean(false), - _ => Bson::String(s), - }; - doc.insert("CANONICALIZE_HOST_NAME", val); - } - Some(val) => { - doc.insert("CANONICALIZE_HOST_NAME", val); - } - None => {} - } - - credential.mechanism_properties = Some(doc); - } - - mechanism.validate_credential(credential)?; - credential.mechanism = parts.auth_mechanism.take(); - } - None => { - if let Some(ref mut credential) = conn_str.credential { - credential.source = parts.auth_source; - } else if authentication_requested { - return Err(ErrorKind::InvalidArgument { - message: "username and mechanism both not provided, but authentication \ - was requested" - .to_string(), - } - .into()); - } - } - }; - - // set default database. - conn_str.default_database = db; - - if conn_str.tls.is_none() && conn_str.is_srv() { - conn_str.tls = Some(Tls::Enabled(Default::default())); - } - - Ok(conn_str) - } - - /// Amount of time spent attempting to check out a connection from a server's connection pool - /// before timing out. Not supported by the Rust driver. - pub fn wait_queue_timeout(&self) -> Option { - self.wait_queue_timeout - } - - /// Relax TLS constraints as much as possible (e.g. allowing invalid certificates or hostname - /// mismatches). Not supported by the Rust driver. - pub fn tls_insecure(&self) -> Option { - self.tls_insecure - } - - fn is_srv(&self) -> bool { - matches!(self.host_info, HostInfo::DnsRecord(_)) - } - - fn parse_options(&mut self, options: &str) -> Result { - let mut parts = ConnectionStringParts::default(); - if options.is_empty() { - return Ok(parts); - } - - let mut keys: Vec<&str> = Vec::new(); - - for option_pair in options.split('&') { - let (key, value) = match option_pair.find('=') { - Some(index) => option_pair.split_at(index), - None => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string options is not a `key=value` pair: {}", - option_pair, - ), - } - .into()) - } - }; - - if key.to_lowercase() != "readpreferencetags" && keys.contains(&key) { - return Err(ErrorKind::InvalidArgument { - message: "repeated options are not allowed in the connection string" - .to_string(), - } - .into()); - } else { - keys.push(key); - } - - // Skip leading '=' in value. - self.parse_option_pair( - &mut parts, - &key.to_lowercase(), - percent_encoding::percent_decode(&value.as_bytes()[1..]) - .decode_utf8_lossy() - .as_ref(), - )?; - } - - if let Some(tags) = parts.read_preference_tags.take() { - self.read_preference = match self.read_preference.take() { - Some(read_pref) => Some(read_pref.with_tags(tags)?), - None => { - return Err(ErrorKind::InvalidArgument { - message: "cannot set read preference tags without also setting read \ - preference mode" - .to_string(), - } - .into()) - } - }; - } - - if let Some(max_staleness) = parts.max_staleness.take() { - self.read_preference = match self.read_preference.take() { - Some(read_pref) => Some(read_pref.with_max_staleness(max_staleness)?), - None => { - return Err(ErrorKind::InvalidArgument { - message: "cannot set max staleness without also setting read preference \ - mode" - .to_string(), - } - .into()) - } - }; - } - - if let Some(true) = self.direct_connection { - if self.is_srv() { - return Err(ErrorKind::InvalidArgument { - message: "cannot use SRV-style URI with directConnection=true".to_string(), - } - .into()); - } - } - - // If zlib and zlib_compression_level are specified then write zlib_compression_level into - // zlib enum - if let (Some(compressors), Some(zlib_compression_level)) = - (self.compressors.as_mut(), parts.zlib_compression) - { - for compressor in compressors { - compressor.write_zlib_level(zlib_compression_level) - } - } - - Ok(parts) - } - - fn parse_option_pair( - &mut self, - parts: &mut ConnectionStringParts, - key: &str, - value: &str, - ) -> Result<()> { - macro_rules! get_bool { - ($value:expr, $option:expr) => { - match $value { - "true" => true, - "false" => false, - _ => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string `{}` option must be a boolean", - $option, - ), - } - .into()) - } - } - }; - } - - macro_rules! get_duration { - ($value:expr, $option:expr) => { - match $value.parse::() { - Ok(i) => i, - _ => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string `{}` option must be a non-negative integer", - $option - ), - } - .into()) - } - } - }; - } - - macro_rules! get_u32 { - ($value:expr, $option:expr) => { - match value.parse::() { - Ok(u) => u, - Err(_) => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string `{}` argument must be a positive integer", - $option, - ), - } - .into()) - } - } - }; - } - - macro_rules! get_i32 { - ($value:expr, $option:expr) => { - match value.parse::() { - Ok(u) => u, - Err(_) => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string `{}` argument must be an integer", - $option - ), - } - .into()) - } - } - }; - } - - match key { - "appname" => { - self.app_name = Some(value.into()); - } - "authmechanism" => { - parts.auth_mechanism = Some(AuthMechanism::from_str(value)?); - } - "authsource" => parts.auth_source = Some(value.to_string()), - "authmechanismproperties" => { - let mut doc = Document::new(); - let err_func = || { - ErrorKind::InvalidArgument { - message: "improperly formatted authMechanismProperties".to_string(), - } - .into() - }; - - for kvp in value.split(',') { - match kvp.find(':') { - Some(index) => { - let (k, v) = exclusive_split_at(kvp, index); - let key = k.ok_or_else(err_func)?; - let value = v.ok_or_else(err_func)?; - doc.insert(key, value); - } - None => return Err(err_func()), - }; - } - parts.auth_mechanism_properties = Some(doc); - } - "compressors" => { - let compressors = value - .split(',') - .filter_map(|x| Compressor::parse_str(x).ok()) - .collect::>(); - self.compressors = if compressors.is_empty() { - None - } else { - Some(compressors) - } - } - k @ "connecttimeoutms" => { - self.connect_timeout = Some(Duration::from_millis(get_duration!(value, k))); - } - k @ "directconnection" => { - self.direct_connection = Some(get_bool!(value, k)); - } - k @ "heartbeatfrequencyms" => { - self.heartbeat_frequency = Some(Duration::from_millis(get_duration!(value, k))); - } - k @ "journal" => { - let mut write_concern = self.write_concern.get_or_insert_with(Default::default); - write_concern.journal = Some(get_bool!(value, k)); - } - k @ "loadbalanced" => { - self.load_balanced = Some(get_bool!(value, k)); - } - k @ "localthresholdms" => { - self.local_threshold = Some(Duration::from_millis(get_duration!(value, k))) - } - k @ "maxidletimems" => { - self.max_idle_time = Some(Duration::from_millis(get_duration!(value, k))); - } - "maxstalenessseconds" => { - let max_staleness_seconds = value.parse::().map_err(|e| { - Error::invalid_argument(format!("invalid maxStalenessSeconds value: {}", e)) - })?; - - let max_staleness = match max_staleness_seconds.cmp(&-1) { - Ordering::Less => { - return Err(Error::invalid_argument(format!( - "maxStalenessSeconds must be -1 or positive, instead got {}", - max_staleness_seconds - ))); - } - Ordering::Equal => { - // -1 maxStaleness means no maxStaleness, which is the default - return Ok(()); - } - Ordering::Greater => Duration::from_secs(max_staleness_seconds as u64), - }; - - parts.max_staleness = Some(max_staleness); - } - k @ "maxpoolsize" => { - self.max_pool_size = Some(get_u32!(value, k)); - } - k @ "minpoolsize" => { - self.min_pool_size = Some(get_u32!(value, k)); - } - "readconcernlevel" => { - self.read_concern = Some(ReadConcernLevel::from_str(value).into()); - } - "readpreference" => { - self.read_preference = Some(match &value.to_lowercase()[..] { - "primary" => ReadPreference::Primary, - "secondary" => ReadPreference::Secondary { - options: Default::default(), - }, - "primarypreferred" => ReadPreference::PrimaryPreferred { - options: Default::default(), - }, - "secondarypreferred" => ReadPreference::SecondaryPreferred { - options: Default::default(), - }, - "nearest" => ReadPreference::Nearest { - options: Default::default(), - }, - other => { - return Err(ErrorKind::InvalidArgument { - message: format!("'{}' is not a valid read preference", other), - } - .into()) - } - }); - } - "readpreferencetags" => { - let tags: Result = if value.is_empty() { - Ok(TagSet::new()) - } else { - value - .split(',') - .map(|tag| { - let mut values = tag.split(':'); - - match (values.next(), values.next()) { - (Some(key), Some(value)) => { - Ok((key.to_string(), value.to_string())) - } - _ => Err(ErrorKind::InvalidArgument { - message: format!( - "'{}' is not a valid read preference tag (which must be \ - of the form 'key:value'", - value, - ), - } - .into()), - } - }) - .collect() - }; - - parts - .read_preference_tags - .get_or_insert_with(Vec::new) - .push(tags?); - } - "replicaset" => { - self.replica_set = Some(value.to_string()); - } - k @ "retrywrites" => { - self.retry_writes = Some(get_bool!(value, k)); - } - k @ "retryreads" => { - self.retry_reads = Some(get_bool!(value, k)); - } - k @ "serverselectiontimeoutms" => { - self.server_selection_timeout = Some(Duration::from_millis(get_duration!(value, k))) - } - k @ "sockettimeoutms" => { - self.socket_timeout = Some(Duration::from_millis(get_duration!(value, k))); - } - k @ "tls" | k @ "ssl" => { - let tls = get_bool!(value, k); - - match (self.tls.as_ref(), tls) { - (Some(Tls::Disabled), true) | (Some(Tls::Enabled(..)), false) => { - return Err(ErrorKind::InvalidArgument { - message: "All instances of `tls` and `ssl` must have the same - value" - .to_string(), - } - .into()); - } - _ => {} - }; - - if self.tls.is_none() { - let tls = if tls { - Tls::Enabled(Default::default()) - } else { - Tls::Disabled - }; - - self.tls = Some(tls); - } - } - k @ "tlsinsecure" | k @ "tlsallowinvalidcertificates" => { - let val = get_bool!(value, k); - - let allow_invalid_certificates = if k == "tlsinsecure" { !val } else { val }; - - match self.tls { - Some(Tls::Disabled) => { - return Err(ErrorKind::InvalidArgument { - message: "'tlsInsecure' can't be set if tls=false".into(), - } - .into()) - } - Some(Tls::Enabled(ref options)) - if options.allow_invalid_certificates.is_some() - && options.allow_invalid_certificates - != Some(allow_invalid_certificates) => - { - return Err(ErrorKind::InvalidArgument { - message: "all instances of 'tlsInsecure' and \ - 'tlsAllowInvalidCertificates' must be consistent (e.g. \ - 'tlsInsecure' cannot be true when \ - 'tlsAllowInvalidCertificates' is false, or vice-versa)" - .into(), - } - .into()); - } - Some(Tls::Enabled(ref mut options)) => { - options.allow_invalid_certificates = Some(allow_invalid_certificates); - } - None => { - self.tls = Some(Tls::Enabled( - TlsOptions::builder() - .allow_invalid_certificates(allow_invalid_certificates) - .build(), - )) - } - } - } - "tlscafile" => match self.tls { - Some(Tls::Disabled) => { - return Err(ErrorKind::InvalidArgument { - message: "'tlsCAFile' can't be set if tls=false".into(), - } - .into()); - } - Some(Tls::Enabled(ref mut options)) => { - options.ca_file_path = Some(value.into()); - } - None => { - self.tls = Some(Tls::Enabled( - TlsOptions::builder() - .ca_file_path(PathBuf::from(value)) - .build(), - )) - } - }, - "tlscertificatekeyfile" => match self.tls { - Some(Tls::Disabled) => { - return Err(ErrorKind::InvalidArgument { - message: "'tlsCertificateKeyFile' can't be set if tls=false".into(), - } - .into()); - } - Some(Tls::Enabled(ref mut options)) => { - options.cert_key_file_path = Some(value.into()); - } - None => { - self.tls = Some(Tls::Enabled( - TlsOptions::builder() - .cert_key_file_path(PathBuf::from(value)) - .build(), - )) - } - }, - "uuidrepresentation" => match value.to_lowercase().as_str() { - "csharplegacy" => self.uuid_representation = Some(UuidRepresentation::CSharpLegacy), - "javalegacy" => self.uuid_representation = Some(UuidRepresentation::JavaLegacy), - "pythonlegacy" => self.uuid_representation = Some(UuidRepresentation::PythonLegacy), - _ => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string `uuidRepresentation` option can be one of \ - `csharpLegacy`, `javaLegacy`, or `pythonLegacy`. Received invalid \ - `{}`", - value - ), - } - .into()) - } - }, - "w" => { - let mut write_concern = self.write_concern.get_or_insert_with(Default::default); - - match value.parse::() { - Ok(w) => match u32::try_from(w) { - Ok(uw) => write_concern.w = Some(Acknowledgment::from(uw)), - Err(_) => { - return Err(ErrorKind::InvalidArgument { - message: "connection string `w` option cannot be a negative \ - integer" - .to_string(), - } - .into()) - } - }, - Err(_) => { - write_concern.w = Some(Acknowledgment::from(value.to_string())); - } - }; - } - k @ "waitqueuetimeoutms" => { - self.wait_queue_timeout = Some(Duration::from_millis(get_duration!(value, k))); - } - k @ "wtimeoutms" => { - let write_concern = self.write_concern.get_or_insert_with(Default::default); - write_concern.w_timeout = Some(Duration::from_millis(get_duration!(value, k))); - } - k @ "zlibcompressionlevel" => { - let i = get_i32!(value, k); - if i < -1 { - return Err(ErrorKind::InvalidArgument { - message: "'zlibCompressionLevel' cannot be less than -1".to_string(), - } - .into()); - } - - if i > 9 { - return Err(ErrorKind::InvalidArgument { - message: "'zlibCompressionLevel' cannot be greater than 9".to_string(), - } - .into()); - } - - parts.zlib_compression = Some(i); - } - - other => { - let (jaro_winkler, option) = URI_OPTIONS.iter().fold((0.0, ""), |acc, option| { - let jaro_winkler = jaro_winkler(option, other).abs(); - if jaro_winkler > acc.0 { - return (jaro_winkler, option); - } - acc - }); - let mut message = format!("{} is an invalid option", other); - if jaro_winkler >= 0.84 { - let _ = write!( - message, - ". An option with a similar name exists: {}", - option - ); - } - return Err(ErrorKind::InvalidArgument { message }.into()); - } - } - - Ok(()) - } -} - -impl FromStr for ConnectionString { - type Err = Error; - fn from_str(s: &str) -> Result { - ConnectionString::parse(s) - } -} - -impl<'de> Deserialize<'de> for ConnectionString { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(ConnectionStringVisitor) - } -} - -struct ConnectionStringVisitor; - -impl<'de> serde::de::Visitor<'de> for ConnectionStringVisitor { - type Value = ConnectionString; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a MongoDB connection string") - } - - fn visit_str(self, v: &str) -> std::result::Result - where - E: serde::de::Error, - { - ConnectionString::parse(v).map_err(serde::de::Error::custom) - } -} - -#[cfg(all(test, not(feature = "sync"), not(feature = "tokio-sync")))] -mod tests { - use std::time::Duration; - - use pretty_assertions::assert_eq; - - use super::{ClientOptions, ServerAddress}; - use crate::{ - concern::{Acknowledgment, ReadConcernLevel, WriteConcern}, - selection_criteria::{ReadPreference, ReadPreferenceOptions}, - }; - - macro_rules! tag_set { - ( $($k:expr => $v:expr),* ) => { - #[allow(clippy::let_and_return)] - { - use std::collections::HashMap; - - #[allow(unused_mut)] - let mut ts = HashMap::new(); - $( - ts.insert($k.to_string(), $v.to_string()); - )* - - ts - } - } - } - - fn host_without_port(hostname: &str) -> ServerAddress { - ServerAddress::Tcp { - host: hostname.to_string(), - port: None, - } - } - - #[test] - fn test_parse_address_with_from_str() { - let x = "localhost:27017".parse::().unwrap(); - let ServerAddress::Tcp { host, port } = x; - assert_eq!(host, "localhost"); - assert_eq!(port, Some(27017)); - - // Port defaults to 27017 (so this doesn't fail) - let x = "localhost".parse::().unwrap(); - let ServerAddress::Tcp { host, port } = x; - assert_eq!(host, "localhost"); - assert_eq!(port, None); - - let x = "localhost:not a number".parse::(); - assert!(x.is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn fails_without_scheme() { - assert!(ClientOptions::parse("localhost:27017").await.is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn fails_with_invalid_scheme() { - assert!(ClientOptions::parse("mangodb://localhost:27017") - .await - .is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn fails_with_nothing_after_scheme() { - assert!(ClientOptions::parse("mongodb://").await.is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn fails_with_only_slash_after_scheme() { - assert!(ClientOptions::parse("mongodb:///").await.is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn fails_with_no_host() { - assert!(ClientOptions::parse("mongodb://:27017").await.is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn no_port() { - let uri = "mongodb://localhost"; - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![host_without_port("localhost")], - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn no_port_trailing_slash() { - let uri = "mongodb://localhost/"; - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![host_without_port("localhost")], - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_port() { - let uri = "mongodb://localhost/"; - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_port_and_trailing_slash() { - let uri = "mongodb://localhost:27017/"; - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_read_concern() { - let uri = "mongodb://localhost:27017/?readConcernLevel=foo"; - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - read_concern: Some(ReadConcernLevel::Custom("foo".to_string()).into()), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_w_negative_int() { - assert!(ClientOptions::parse("mongodb://localhost:27017/?w=-1") - .await - .is_err()); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_w_non_negative_int() { - let uri = "mongodb://localhost:27017/?w=1"; - let write_concern = WriteConcern::builder().w(Acknowledgment::from(1)).build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - write_concern: Some(write_concern), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_w_string() { - let uri = "mongodb://localhost:27017/?w=foo"; - let write_concern = WriteConcern::builder() - .w(Acknowledgment::from("foo".to_string())) - .build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - write_concern: Some(write_concern), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_invalid_j() { - assert!( - ClientOptions::parse("mongodb://localhost:27017/?journal=foo") - .await - .is_err() - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_j() { - let uri = "mongodb://localhost:27017/?journal=true"; - let write_concern = WriteConcern::builder().journal(true).build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - write_concern: Some(write_concern), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_wtimeout_non_int() { - assert!( - ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=foo") - .await - .is_err() - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_wtimeout_negative_int() { - assert!( - ClientOptions::parse("mongodb://localhost:27017/?wtimeoutMS=-1") - .await - .is_err() - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_wtimeout() { - let uri = "mongodb://localhost:27017/?wtimeoutMS=27"; - let write_concern = WriteConcern::builder() - .w_timeout(Duration::from_millis(27)) - .build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - write_concern: Some(write_concern), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_all_write_concern_options() { - let uri = "mongodb://localhost:27017/?w=majority&journal=false&wtimeoutMS=27"; - let write_concern = WriteConcern::builder() - .w(Acknowledgment::Majority) - .journal(false) - .w_timeout(Duration::from_millis(27)) - .build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27017), - }], - write_concern: Some(write_concern), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } - - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn with_mixed_options() { - let uri = "mongodb://localhost,localhost:27018/?w=majority&readConcernLevel=majority&\ - journal=false&wtimeoutMS=27&replicaSet=foo&heartbeatFrequencyMS=1000&\ - localThresholdMS=4000&readPreference=secondaryPreferred&readpreferencetags=dc:\ - ny,rack:1&serverselectiontimeoutms=2000&readpreferencetags=dc:ny&\ - readpreferencetags="; - let write_concern = WriteConcern::builder() - .w(Acknowledgment::Majority) - .journal(false) - .w_timeout(Duration::from_millis(27)) - .build(); - - assert_eq!( - ClientOptions::parse(uri).await.unwrap(), - ClientOptions { - hosts: vec![ - ServerAddress::Tcp { - host: "localhost".to_string(), - port: None, - }, - ServerAddress::Tcp { - host: "localhost".to_string(), - port: Some(27018), - }, - ], - selection_criteria: Some( - ReadPreference::SecondaryPreferred { - options: ReadPreferenceOptions::builder() - .tag_sets(vec![ - tag_set! { - "dc" => "ny", - "rack" => "1" - }, - tag_set! { - "dc" => "ny" - }, - tag_set! {}, - ]) - .build() - } - .into() - ), - read_concern: Some(ReadConcernLevel::Majority.into()), - write_concern: Some(write_concern), - repl_set_name: Some("foo".to_string()), - heartbeat_freq: Some(Duration::from_millis(1000)), - local_threshold: Some(Duration::from_millis(4000)), - server_selection_timeout: Some(Duration::from_millis(2000)), - original_uri: Some(uri.into()), - ..Default::default() - } - ); - } -} - -/// Contains the options that can be used to create a new -/// [`ClientSession`](../struct.ClientSession.html). -#[derive(Clone, Debug, Deserialize, TypedBuilder)] -#[builder(field_defaults(default, setter(into)))] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct SessionOptions { - /// The default options to use for transactions started on this session. - /// - /// If these options are not specified, they will be inherited from the - /// [`Client`](../struct.Client.html) associated with this session. They will not - /// be inherited from the options specified - /// on the [`Database`](../struct.Database.html) or [`Collection`](../struct.Collection.html) - /// associated with the operations within the transaction. - pub default_transaction_options: Option, - - /// If true, all operations performed in the context of this session - /// will be [causally consistent](https://www.mongodb.com/docs/manual/core/causal-consistency-read-write-concerns/). - /// - /// Defaults to true if [`SessionOptions::snapshot`] is unspecified. - pub causal_consistency: Option, - - /// If true, all read operations performed using this client session will share the same - /// snapshot. Defaults to false. - pub snapshot: Option, -} - -impl SessionOptions { - pub(crate) fn validate(&self) -> Result<()> { - if let (Some(causal_consistency), Some(snapshot)) = (self.causal_consistency, self.snapshot) - { - if causal_consistency && snapshot { - return Err(ErrorKind::InvalidArgument { - message: "snapshot and causal consistency are mutually exclusive".to_string(), - } - .into()); - } - } - Ok(()) - } -} - -/// Contains the options that can be used for a transaction. -#[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, TypedBuilder, Clone)] -#[builder(field_defaults(default, setter(into)))] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct TransactionOptions { - /// The read concern to use for the transaction. - #[builder(default)] - #[serde(skip_serializing)] - pub read_concern: Option, - - /// The write concern to use when committing or aborting a transaction. - #[builder(default)] - pub write_concern: Option, - - /// The selection criteria to use for all read operations in a transaction. - #[builder(default)] - #[serde(skip_serializing, rename = "readPreference")] - pub selection_criteria: Option, - - /// The maximum amount of time to allow a single commitTransaction to run. - #[builder(default)] - #[serde( - serialize_with = "bson_util::serialize_duration_option_as_int_millis", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis", - rename(serialize = "maxTimeMS", deserialize = "maxCommitTimeMS"), - default - )] - pub max_commit_time: Option, -} diff --git a/src/client/options/parse.rs b/src/client/options/parse.rs new file mode 100644 index 000000000..8aed36ae4 --- /dev/null +++ b/src/client/options/parse.rs @@ -0,0 +1,167 @@ +use crate::{ + action::{action_impl, ParseConnectionString}, + error::{Error, Result}, + srv::OriginalSrvInfo, +}; + +use super::{ClientOptions, ConnectionString, ResolvedHostInfo, Tls}; + +#[action_impl] +impl Action for ParseConnectionString { + type Future = ParseFuture; + + async fn execute(self) -> Result { + let mut conn_str = self.conn_str?; + let auth_source_present = conn_str + .credential + .as_ref() + .and_then(|cred| cred.source.as_ref()) + .is_some(); + let host_info = std::mem::take(&mut conn_str.host_info); + let mut options = ClientOptions::from_connection_string(conn_str); + #[cfg(feature = "dns-resolver")] + { + options.resolver_config.clone_from(&self.resolver_config); + } + + let resolved = host_info + .resolve(self.resolver_config, options.srv_service_name.clone()) + .await?; + options.hosts = match resolved { + ResolvedHostInfo::HostIdentifiers(hosts) => hosts, + ResolvedHostInfo::DnsRecord { + hostname, + mut config, + } => { + // Save the original SRV info to allow mongos polling. + options.original_srv_info = OriginalSrvInfo { + hostname, + min_ttl: config.min_ttl, + } + .into(); + + // Enable TLS unless the user explicitly disabled it. + if options.tls.is_none() { + options.tls = Some(Tls::Enabled(Default::default())); + } + + // Set the authSource TXT option found during SRV lookup unless the user already set + // it. Note that this _does_ override the default database specified + // in the URI, since it is supposed to be overriden by authSource. + if !auth_source_present { + if let Some(auth_source) = config.auth_source.take() { + if let Some(ref mut credential) = options.credential { + credential.source = Some(auth_source); + } + } + } + + // Set the replica set name TXT option found during SRV lookup unless the user + // already set it. + if options.repl_set_name.is_none() { + if let Some(replica_set) = config.replica_set.take() { + options.repl_set_name = Some(replica_set); + } + } + + if options.load_balanced.is_none() { + options.load_balanced = config.load_balanced; + } + + if let Some(max) = options.srv_max_hosts { + if max > 0 { + if options.repl_set_name.is_some() { + return Err(Error::invalid_argument( + "srvMaxHosts and replicaSet cannot both be present", + )); + } + if options.load_balanced == Some(true) { + return Err(Error::invalid_argument( + "srvMaxHosts and loadBalanced=true cannot both be present", + )); + } + config.hosts = crate::sdam::choose_n(&config.hosts, max as usize) + .cloned() + .collect(); + } + } + + // Set the ClientOptions hosts to those found during the SRV lookup. + config.hosts + } + }; + + options.validate()?; + Ok(options) + } +} + +impl ClientOptions { + fn from_connection_string(conn_str: ConnectionString) -> Self { + let mut credential = conn_str.credential; + // Populate default auth source, if needed. + let db = &conn_str.default_database; + if let Some(credential) = credential.as_mut() { + if credential.source.is_none() { + credential.source = match &credential.mechanism { + Some(mechanism) => Some(mechanism.default_source(db.as_deref()).into()), + None => { + // If credentials exist (i.e. username is specified) but no mechanism, the + // default source is chosen from the following list in + // order (skipping null ones): authSource option, connection string db, + // SCRAM default (i.e. "admin"). + db.clone().or_else(|| Some("admin".into())) + } + }; + } + } + + Self { + hosts: vec![], + app_name: conn_str.app_name, + tls: conn_str.tls, + heartbeat_freq: conn_str.heartbeat_frequency, + local_threshold: conn_str.local_threshold, + read_concern: conn_str.read_concern, + selection_criteria: conn_str.read_preference.map(Into::into), + repl_set_name: conn_str.replica_set, + write_concern: conn_str.write_concern, + max_pool_size: conn_str.max_pool_size, + min_pool_size: conn_str.min_pool_size, + max_idle_time: conn_str.max_idle_time, + max_connecting: conn_str.max_connecting, + server_selection_timeout: conn_str.server_selection_timeout, + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressors: conn_str.compressors, + connect_timeout: conn_str.connect_timeout, + retry_reads: conn_str.retry_reads, + retry_writes: conn_str.retry_writes, + server_monitoring_mode: conn_str.server_monitoring_mode, + socket_timeout: conn_str.socket_timeout, + direct_connection: conn_str.direct_connection, + default_database: conn_str.default_database, + driver_info: None, + credential, + cmap_event_handler: None, + command_event_handler: None, + original_srv_info: None, + #[cfg(test)] + original_uri: Some(conn_str.original_uri), + #[cfg(feature = "dns-resolver")] + resolver_config: None, + server_api: None, + load_balanced: conn_str.load_balanced, + sdam_event_handler: None, + #[cfg(test)] + test_options: None, + #[cfg(feature = "tracing-unstable")] + tracing_max_document_length_bytes: None, + srv_max_hosts: conn_str.srv_max_hosts, + srv_service_name: conn_str.srv_service_name, + } + } +} diff --git a/src/client/options/resolver_config.rs b/src/client/options/resolver_config.rs index 67d556c2a..3c7b20da6 100644 --- a/src/client/options/resolver_config.rs +++ b/src/client/options/resolver_config.rs @@ -1,14 +1,17 @@ -use trust_dns_resolver::config::ResolverConfig as TrustDnsResolverConfig; +#[cfg(feature = "dns-resolver")] +use hickory_resolver::config::ResolverConfig as HickoryResolverConfig; /// Configuration for the upstream nameservers to use for resolution. /// -/// This is a thin wrapper around a `trust_dns_resolver::config::ResolverConfig` provided to ensure +/// This is a thin wrapper around a `hickory_resolver::config::ResolverConfig` provided to ensure /// API stability. #[derive(Clone, Debug, PartialEq)] pub struct ResolverConfig { - pub(crate) inner: TrustDnsResolverConfig, + #[cfg(feature = "dns-resolver")] + pub(crate) inner: HickoryResolverConfig, } +#[cfg(feature = "dns-resolver")] impl ResolverConfig { /// Creates a default configuration, using 1.1.1.1, 1.0.0.1 and 2606:4700:4700::1111, /// 2606:4700:4700::1001 (thank you, Cloudflare). @@ -16,7 +19,7 @@ impl ResolverConfig { /// Please see: pub fn cloudflare() -> Self { ResolverConfig { - inner: TrustDnsResolverConfig::cloudflare(), + inner: HickoryResolverConfig::cloudflare(), } } @@ -27,7 +30,7 @@ impl ResolverConfig { /// ISP’s track similar information in DNS. pub fn google() -> Self { ResolverConfig { - inner: TrustDnsResolverConfig::google(), + inner: HickoryResolverConfig::google(), } } @@ -37,7 +40,7 @@ impl ResolverConfig { /// Please see: pub fn quad9() -> Self { ResolverConfig { - inner: TrustDnsResolverConfig::quad9(), + inner: HickoryResolverConfig::quad9(), } } } diff --git a/src/client/options/test.rs b/src/client/options/test.rs index e2dee4361..86159e3cf 100644 --- a/src/client/options/test.rs +++ b/src/client/options/test.rs @@ -1,17 +1,59 @@ use std::time::Duration; -use bson::UuidRepresentation; +use crate::bson::UuidRepresentation; +use once_cell::sync::Lazy; use pretty_assertions::assert_eq; use serde::Deserialize; use crate::{ bson::{Bson, Document}, + bson_util::get_int, client::options::{ClientOptions, ConnectionString, ServerAddress}, error::ErrorKind, - options::Compressor, - test::run_spec_test, + test::spec::deserialize_spec_tests, Client, }; + +static SKIPPED_TESTS: Lazy> = Lazy::new(|| { + let mut skipped_tests = vec![ + // TODO RUST-1309: unskip this test + "tlsInsecure is parsed correctly", + // The driver does not support maxPoolSize=0 + "maxPoolSize=0 does not error", + #[cfg(not(feature = "cert-key-password"))] + "Valid tlsCertificateKeyFilePassword is parsed correctly", + // The driver does not support OCSP (see RUST-361) + "tlsDisableCertificateRevocationCheck can be set to true", + "tlsDisableCertificateRevocationCheck can be set to false", + "tlsDisableOCSPEndpointCheck can be set to true", + "tlsDisableOCSPEndpointCheck can be set to false", + // TODO RUST-582: unskip these tests + "Valid connection and timeout options are parsed correctly", + "timeoutMS=0", + ]; + + // TODO RUST-1896: unskip this test when openssl-tls is enabled + // if cfg!(not(feature = "openssl-tls")) + skipped_tests.push("tlsAllowInvalidHostnames is parsed correctly"); + // } + + if cfg!(not(feature = "zlib-compression")) { + skipped_tests.push("Valid compression options are parsed correctly"); + skipped_tests.push("Non-numeric zlibCompressionLevel causes a warning"); + skipped_tests.push("Too low zlibCompressionLevel causes a warning"); + skipped_tests.push("Too high zlibCompressionLevel causes a warning"); + } + + if cfg!(not(all( + feature = "zlib-compression", + feature = "snappy-compression" + ))) { + skipped_tests.push("Multiple compressors are parsed correctly"); + } + + skipped_tests +}); + #[derive(Debug, Deserialize)] struct TestFile { pub tests: Vec, @@ -20,207 +62,189 @@ struct TestFile { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct TestCase { - pub description: String, - pub uri: String, - pub valid: bool, - pub warning: Option, - pub hosts: Option>, - pub auth: Option, - pub options: Option, + description: String, + uri: String, + valid: bool, + warning: Option, + hosts: Option>, + auth: Option, + options: Option, } -async fn run_test(test_file: TestFile) { - for mut test_case in test_file.tests { - if - // TODO: RUST-229: Implement IPv6 Support - test_case.description.contains("ipv6") - || test_case.description.contains("IP literal") - // TODO: RUST-226: Investigate whether tlsCertificateKeyFilePassword is supported in rustls - || test_case - .description - .contains("tlsCertificateKeyFilePassword") - // Not Implementing - || test_case.description.contains("tlsAllowInvalidHostnames") - || test_case.description.contains("single-threaded") - || test_case.description.contains("serverSelectionTryOnce") - || test_case.description.contains("Unix") - || test_case.description.contains("relative path") - // Compression is implemented but will only pass the tests if all - // the appropriate feature flags are set. That is because - // valid compressors are only parsed correctly if the corresponding feature flag is set. - // (otherwise they are treated as invalid, and hence ignored) - || (test_case.description.contains("compress") && - !cfg!( - all(features = "zlib-compression", - features = "zstd-compression", - features = "snappy-compression" - ) - ) - ) - // The Rust driver disallows `maxPoolSize=0`. - || test_case.description.contains("maxPoolSize=0 does not error") - { - continue; - } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct TestAuth { + username: Option, + password: Option, + db: Option, +} - let warning = test_case.warning.take().unwrap_or(false); - - if test_case.valid && !warning { - let mut is_unsupported_host_type = false; - // hosts - if let Some(mut json_hosts) = test_case.hosts.take() { - // skip over unsupported host types - is_unsupported_host_type = json_hosts.iter_mut().any(|h_json| { - matches!( - h_json.remove("type").as_ref().and_then(Bson::as_str), - Some("ip_literal") | Some("unix") - ) - }); - - if !is_unsupported_host_type { - let options = ClientOptions::parse(&test_case.uri).await.unwrap(); - let hosts: Vec<_> = options - .hosts - .into_iter() - .map(ServerAddress::into_document) - .collect(); - - assert_eq!(hosts, json_hosts); - } +impl TestAuth { + fn assert_matches_client_options(&self, options: &ClientOptions, description: &str) { + let credential = options.credential.as_ref(); + assert_eq!( + self.username.as_ref(), + credential.and_then(|c| c.username.as_ref()), + "{}", + description + ); + assert_eq!( + self.password.as_ref(), + credential.and_then(|c| c.password.as_ref()), + "{}", + description + ); + assert_eq!( + self.db.as_ref(), + options.default_database.as_ref(), + "{}", + description + ); + } +} + +async fn run_tests(path: &[&str], skipped_files: &[&str]) { + let test_files = deserialize_spec_tests::(path, Some(skipped_files)) + .into_iter() + .map(|(test_file, _)| test_file); + + for test_file in test_files { + for test_case in test_file.tests { + if SKIPPED_TESTS.contains(&test_case.description.as_str()) { + continue; } - if !is_unsupported_host_type { - // options - let options = ClientOptions::parse(&test_case.uri) - .await + + let client_options_result = ClientOptions::parse(&test_case.uri).await; + + // The driver does not log warnings for unsupported or incorrect connection string + // values, so expect an error when warning is set to true. + if test_case.valid && test_case.warning != Some(true) { + let client_options = client_options_result.expect(&test_case.description); + + if let Some(ref expected_hosts) = test_case.hosts { + assert_eq!( + &client_options.hosts, expected_hosts, + "{}", + test_case.description + ); + } + + let mut actual_options = crate::bson_compat::serialize_to_document(&client_options) .expect(&test_case.description); - let mut options_doc = bson::to_document(&options).unwrap_or_else(|_| { - panic!( - "{}: Failed to serialize ClientOptions", - &test_case.description - ) - }); - if let Some(json_options) = test_case.options { - let mut json_options: Document = json_options - .into_iter() - .filter_map(|(k, v)| { - if let Bson::Null = v { - None - } else { - Some((k.to_lowercase(), v)) - } - }) - .collect(); - // tlsallowinvalidcertificates and tlsinsecure must be inverse of each other - if !json_options.contains_key("tlsallowinvalidcertificates") { - if let Some(val) = json_options.remove("tlsinsecure") { - json_options - .insert("tlsallowinvalidcertificates", !val.as_bool().unwrap()); - } - } + if let Some(mode) = actual_options.remove("mode") { + actual_options.insert("readPreference", mode); + } - // The default types parsed from the test file don't match those serialized - // from the `ClientOptions` struct. - if let Ok(min) = json_options.get_i32("minpoolsize") { - json_options.insert("minpoolsize", Bson::Int64(min.into())); - } - if let Ok(max) = json_options.get_i32("maxpoolsize") { - json_options.insert("maxpoolsize", Bson::Int64(max.into())); + if let Some(tags) = actual_options.remove("tagSets") { + actual_options.insert("readPreferenceTags", tags); + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + if let Some(ref compressors) = client_options.compressors { + use crate::options::Compressor; + + actual_options.insert( + "compressors", + compressors + .iter() + .map(Compressor::name) + .collect::>(), + ); + + #[cfg(feature = "zlib-compression")] + if let Some(zlib_compression_level) = compressors + .iter() + .filter_map(|compressor| match compressor { + Compressor::Zlib { level } => *level, + _ => None, + }) + .next() + { + actual_options.insert("zlibcompressionlevel", zlib_compression_level); } + } - options_doc = options_doc - .into_iter() - .filter(|(ref key, _)| json_options.contains_key(key)) - .collect(); - - // This is required because compressor is not serialize, but the spec tests - // still expect to see serialized compressors. - // This hardcodes the compressors into the options. - if let Some(compressors) = options.compressors { - options_doc.insert( - "compressors", - compressors - .iter() - .map(Compressor::name) - .collect::>(), - ); - #[cfg(feature = "zlib-compression")] - for compressor in compressors { - if let Compressor::Zlib { level: Some(level) } = compressor { - options_doc.insert("zlibcompressionlevel", level); - } + if let Some(ref expected_options) = test_case.options { + for (expected_key, expected_value) in expected_options { + if expected_value == &Bson::Null { + continue; } - } - assert_eq!(options_doc, json_options, "{}", test_case.description) + let (_, actual_value) = actual_options + .iter() + .find(|(actual_key, _)| actual_key.eq_ignore_ascii_case(expected_key)) + .unwrap_or_else(|| { + panic!( + "{}: parsed options missing {} key", + test_case.description, expected_key + ) + }); + + if let Some(expected_number) = get_int(expected_value) { + let actual_number = get_int(actual_value).unwrap_or_else(|| { + panic!( + "{}: {} should be a numeric value but got {}", + &test_case.description, expected_key, actual_value + ) + }); + assert_eq!(actual_number, expected_number, "{}", test_case.description); + } else { + assert_eq!(actual_value, expected_value, "{}", test_case.description); + } + } } - // auth - if let Some(json_auth) = test_case.auth { - let json_auth: Document = json_auth - .into_iter() - .filter_map(|(k, v)| { - if let Bson::Null = v { - None - } else { - Some((k.to_lowercase(), v)) - } - }) - .collect(); - let options = ClientOptions::parse(&test_case.uri).await.unwrap(); - let mut expected_auth = options.credential.unwrap_or_default().into_document(); - expected_auth = expected_auth - .into_iter() - .filter(|(ref key, _)| json_auth.contains_key(key)) - .collect(); - - assert_eq!(expected_auth, json_auth); + if let Some(test_auth) = test_case.auth { + test_auth + .assert_matches_client_options(&client_options, &test_case.description); } - } - } else { - let expected_type = if warning { "warning" } else { "error" }; - - match ClientOptions::parse(&test_case.uri) - .await - .map_err(|e| *e.kind) - { - Ok(_) => panic!("expected {}", expected_type), - Err(ErrorKind::InvalidArgument { .. }) => {} - Err(e) => panic!("expected InvalidArgument, but got {:?}", e), + } else { + let error = client_options_result.expect_err(&test_case.description); + assert!( + matches!(*error.kind, ErrorKind::InvalidArgument { .. }), + "{}", + &test_case.description + ); } } } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run_uri_options_spec_tests() { - run_spec_test(&["uri-options"], run_test).await; + let mut skipped_files = vec![ + "single-threaded-options.json", + // TODO RUST-1054 unskip this file + "proxy-options.json", + ]; + if cfg!(not(feature = "gssapi-auth")) { + skipped_files.push("auth-options.json"); + } + run_tests(&["uri-options"], &skipped_files).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run_connection_string_spec_tests() { - run_spec_test(&["connection-string"], run_test).await; -} - -async fn parse_uri(option: &str, suggestion: Option<&str>) { - match ConnectionString::parse(format!("mongodb://host:27017/?{}=test", option)) - .map_err(|e| *e.kind) - { - Ok(_) => panic!("expected error for option {}", option), - Err(ErrorKind::InvalidArgument { message, .. }) => { - match suggestion { - Some(s) => assert!(message.contains(s)), - None => assert!(!message.contains("similar")), - }; - } - Err(e) => panic!("expected InvalidArgument, but got {:?}", e), + let mut skipped_files = Vec::new(); + if cfg!(not(feature = "gssapi-auth")) { + skipped_files.push("valid-auth.json"); } + if cfg!(not(unix)) { + skipped_files.push("valid-unix_socket-absolute.json"); + skipped_files.push("valid-unix_socket-relative.json"); + // All the tests in this file use unix domain sockets + skipped_files.push("valid-db-with-dotted-name.json"); + } + + run_tests(&["connection-string"], &skipped_files).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn uuid_representations() { let mut uuid_repr = parse_uri_with_uuid_representation("csharpLegacy") .await @@ -248,7 +272,9 @@ async fn uuid_representations() { ); } -async fn parse_uri_with_uuid_representation(uuid_repr: &str) -> Result { +async fn parse_uri_with_uuid_representation( + uuid_repr: &str, +) -> std::result::Result { match ConnectionString::parse(format!( "mongodb://localhost:27017/?uuidRepresentation={}", uuid_repr @@ -260,45 +286,34 @@ async fn parse_uri_with_uuid_representation(uuid_repr: &str) -> Result) { + match ConnectionString::parse(format!("mongodb://host:27017/?{}=test", option)) + .map_err(|e| *e.kind) + { + Ok(_) => panic!("expected error for option {}", option), + Err(ErrorKind::InvalidArgument { message, .. }) => { + match suggestion { + Some(s) => assert!(message.contains(s)), + None => assert!(!message.contains("similar")), + }; + } + Err(e) => panic!("expected InvalidArgument, but got {:?}", e), } - ); + } + + parse_uri("invalidoption", None); + parse_uri("x", None); + parse_uri("max", None); + parse_uri("tlstimeout", None); + parse_uri("waitqueuetimeout", Some("waitqueuetimeoutms")); + parse_uri("retry_reads", Some("retryreads")); + parse_uri("poolsize", Some("maxpoolsize")); + parse_uri("maxstalenessms", Some("maxstalenessseconds")); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn parse_with_no_default_database() { let uri = "mongodb://localhost/"; @@ -316,8 +331,7 @@ async fn parse_with_no_default_database() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn options_debug_omits_uri() { let uri = "mongodb://username:password@localhost/"; let options = ClientOptions::parse(uri).await.unwrap(); @@ -328,8 +342,7 @@ async fn options_debug_omits_uri() { assert!(!debug_output.contains("uri")); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn options_enforce_min_heartbeat_frequency() { let options = ClientOptions::builder() .hosts(vec![ServerAddress::parse("a:123").unwrap()]) @@ -338,3 +351,81 @@ async fn options_enforce_min_heartbeat_frequency() { Client::with_options(options).unwrap_err(); } + +#[test] +fn invalid_ipv6() { + // invalid hostname for ipv6 + let address = "[localhost]:27017"; + let error = ServerAddress::parse(address).unwrap_err(); + let message = error.message().unwrap(); + assert!(message.contains("invalid IPv6 address syntax"), "{message}"); + + // invalid character after hostname + let address = "[::1]a"; + let error = ServerAddress::parse(address).unwrap_err(); + let message = error.message().unwrap(); + assert!( + message.contains("the hostname can only be followed by a port"), + "{message}" + ); + + // missing bracket + let address = "[::1:27017"; + let error = ServerAddress::parse(address).unwrap_err(); + let message = error.message().unwrap(); + assert!(message.contains("missing closing ']'"), "{message}"); + + // extraneous bracket + let address = "[::1]:27017]"; + let error = ServerAddress::parse(address).unwrap_err(); + let message = error.message().unwrap(); + assert!(message.contains("the port must be an integer"), "{message}"); +} + +#[cfg(not(unix))] +#[test] +fn unix_domain_socket_not_allowed() { + let address = "address.sock"; + let error = ServerAddress::parse(address).unwrap_err(); + let message = error.message().unwrap(); + assert!( + message.contains("not supported on this platform"), + "{message}" + ); +} + +#[cfg(feature = "cert-key-password")] +#[tokio::test] +async fn tls_cert_key_password_connect() { + use std::path::PathBuf; + + use crate::bson::doc; + + use crate::{ + options::TlsOptions, + test::{get_client_options, log_uncaptured}, + }; + + use super::Tls; + + let mut options = get_client_options().await.clone(); + if !matches!(options.tls, Some(Tls::Enabled(_))) { + log_uncaptured("Skipping tls_cert_key_password_connect: tls not enabled"); + return; + } + let mut certpath = PathBuf::from(std::env::var("DRIVERS_TOOLS").unwrap()); + certpath.push(".evergreen/x509gen"); + options.tls = Some(Tls::Enabled( + TlsOptions::builder() + .ca_file_path(certpath.join("ca.pem")) + .cert_key_file_path(certpath.join("client-pkcs8-encrypted.pem")) + .tls_certificate_key_file_password(b"password".to_vec()) + .build(), + )); + let client = Client::with_options(options).unwrap(); + client + .database("test") + .run_command(doc! {"ping": 1}) + .await + .unwrap(); +} diff --git a/src/client/session.rs b/src/client/session.rs new file mode 100644 index 000000000..b87a26207 --- /dev/null +++ b/src/client/session.rs @@ -0,0 +1,457 @@ +mod action; +mod cluster_time; +mod pool; +#[cfg(test)] +mod test; + +use std::{ + collections::HashSet, + sync::Arc, + time::{Duration, Instant}, +}; + +use once_cell::sync::Lazy; +use uuid::Uuid; + +use crate::{ + bson::{doc, spec::BinarySubtype, Binary, Bson, Document, Timestamp}, + cmap::conn::PinnedConnectionHandle, + operation::Retryability, + options::{SessionOptions, TransactionOptions}, + sdam::ServerInfo, + selection_criteria::SelectionCriteria, + Client, +}; +pub use cluster_time::ClusterTime; +pub(super) use pool::ServerSessionPool; + +use super::{options::ServerAddress, AsyncDropToken}; + +pub(crate) static SESSIONS_UNSUPPORTED_COMMANDS: Lazy> = Lazy::new(|| { + let mut hash_set = HashSet::new(); + hash_set.insert("killcursors"); + hash_set.insert("parallelcollectionscan"); + hash_set +}); + +/// A MongoDB client session. This struct represents a logical session used for ordering sequential +/// operations. To create a `ClientSession`, call `start_session` on a `Client`. +/// +/// `ClientSession` instances are not thread safe or fork safe. They can only be used by one thread +/// or process at a time. +/// +/// ## Transactions +/// Transactions are used to execute a series of operations across multiple documents and +/// collections atomically. For more information about when and how to use transactions in MongoDB, +/// see the [manual](https://www.mongodb.com/docs/manual/core/transactions/). +/// +/// Replica set transactions are supported on MongoDB 4.0+. Sharded transactions are supported on +/// MongoDDB 4.2+. Transactions are associated with a `ClientSession`. To begin a transaction, call +/// [`ClientSession::start_transaction`] on a `ClientSession`. The `ClientSession` must be passed to +/// operations to be executed within the transaction. +/// +/// ```rust +/// use mongodb::{ +/// bson::{doc, Document}, +/// error::{Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}, +/// options::{Acknowledgment, ReadConcern, TransactionOptions, WriteConcern}, +/// # Client, +/// ClientSession, +/// Collection, +/// }; +/// +/// # async fn do_stuff() -> Result<()> { +/// # let client = Client::with_uri_str("mongodb://example.com").await?; +/// # let coll: Collection = client.database("foo").collection("bar"); +/// let mut session = client.start_session().await?; +/// session +/// .start_transaction() +/// .read_concern(ReadConcern::majority()) +/// .write_concern(WriteConcern::majority()) +/// .await?; +/// // A "TransientTransactionError" label indicates that the entire transaction can be retried +/// // with a reasonable expectation that it will succeed. +/// while let Err(error) = execute_transaction(&coll, &mut session).await { +/// if !error.contains_label(TRANSIENT_TRANSACTION_ERROR) { +/// break; +/// } +/// } +/// # Ok(()) +/// # } +/// +/// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> { +/// coll.insert_one(doc! { "x": 1 }).session(&mut *session).await?; +/// coll.delete_one(doc! { "y": 2 }).session(&mut *session).await?; +/// // An "UnknownTransactionCommitResult" label indicates that it is unknown whether the +/// // commit has satisfied the write concern associated with the transaction. If an error +/// // with this label is returned, it is safe to retry the commit until the write concern is +/// // satisfied or an error without the label is returned. +/// loop { +/// let result = session.commit_transaction().await; +/// if let Err(ref error) = result { +/// if error.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) { +/// continue; +/// } +/// } +/// result? +/// } +/// } +/// ``` +#[derive(Debug)] +pub struct ClientSession { + cluster_time: Option, + server_session: ServerSession, + client: Client, + is_implicit: bool, + options: Option, + drop_token: AsyncDropToken, + pub(crate) transaction: Transaction, + pub(crate) snapshot_time: Option, + pub(crate) operation_time: Option, + #[cfg(test)] + pub(crate) convenient_transaction_timeout: Option, +} + +#[derive(Debug)] +pub(crate) struct Transaction { + pub(crate) state: TransactionState, + pub(crate) options: Option, + pub(crate) pinned: Option, + pub(crate) recovery_token: Option, +} + +impl Transaction { + pub(crate) fn start(&mut self, options: Option) { + self.state = TransactionState::Starting; + self.options = options; + self.recovery_token = None; + } + + pub(crate) fn commit(&mut self, data_committed: bool) { + self.state = TransactionState::Committed { data_committed }; + } + + pub(crate) fn abort(&mut self) { + self.state = TransactionState::Aborted; + self.options = None; + self.pinned = None; + } + + pub(crate) fn reset(&mut self) { + self.state = TransactionState::None; + self.options = None; + self.pinned = None; + self.recovery_token = None; + } + + #[cfg(test)] + pub(crate) fn is_pinned(&self) -> bool { + self.pinned.is_some() + } + + pub(crate) fn pinned_mongos(&self) -> Option<&SelectionCriteria> { + match &self.pinned { + Some(TransactionPin::Mongos(s)) => Some(s), + _ => None, + } + } + + pub(crate) fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + match &self.pinned { + Some(TransactionPin::Connection(c)) => Some(c), + _ => None, + } + } + + fn take(&mut self) -> Self { + Transaction { + state: self.state.clone(), + options: self.options.take(), + pinned: self.pinned.take(), + recovery_token: self.recovery_token.take(), + } + } +} + +impl Default for Transaction { + fn default() -> Self { + Self { + state: TransactionState::None, + options: None, + pinned: None, + recovery_token: None, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum TransactionState { + None, + Starting, + InProgress, + Committed { + /// Whether any data was committed when commit_transaction was initially called. This is + /// required to determine whether a commitTransaction command should be run if the user + /// calls commit_transaction again. + data_committed: bool, + }, + Aborted, +} + +#[derive(Debug)] +pub(crate) enum TransactionPin { + Mongos(SelectionCriteria), + Connection(PinnedConnectionHandle), +} + +impl ClientSession { + /// Creates a new `ClientSession` by checking out a corresponding `ServerSession` from the + /// provided client's session pool. + pub(crate) async fn new( + client: Client, + options: Option, + is_implicit: bool, + ) -> Self { + let timeout = client.inner.topology.logical_session_timeout(); + let server_session = client.inner.session_pool.check_out(timeout).await; + Self { + drop_token: client.register_async_drop(), + client, + server_session, + cluster_time: None, + is_implicit, + options, + transaction: Default::default(), + snapshot_time: None, + operation_time: None, + #[cfg(test)] + convenient_transaction_timeout: None, + } + } + + /// The client used to create this session. + pub fn client(&self) -> Client { + self.client.clone() + } + + /// The id of this session. + pub fn id(&self) -> &Document { + &self.server_session.id + } + + /// Whether this session was created implicitly by the driver or explcitly by the user. + pub(crate) fn is_implicit(&self) -> bool { + self.is_implicit + } + + /// Whether this session is currently in a transaction. + pub(crate) fn in_transaction(&self) -> bool { + self.transaction.state == TransactionState::Starting + || self.transaction.state == TransactionState::InProgress + } + + /// The highest seen cluster time this session has seen so far. + /// This will be `None` if this session has not been used in an operation yet. + pub fn cluster_time(&self) -> Option<&ClusterTime> { + self.cluster_time.as_ref() + } + + /// The options used to create this session. + pub(crate) fn options(&self) -> Option<&SessionOptions> { + self.options.as_ref() + } + + /// Set the cluster time to the provided one if it is greater than this session's highest seen + /// cluster time or if this session's cluster time is `None`. + pub fn advance_cluster_time(&mut self, to: &ClusterTime) { + if self.cluster_time().map(|ct| ct < to).unwrap_or(true) { + self.cluster_time = Some(to.clone()); + } + } + + /// Advance operation time for this session. If the provided timestamp is earlier than this + /// session's current operation time, then the operation time is unchanged. + pub fn advance_operation_time(&mut self, ts: Timestamp) { + self.operation_time = match self.operation_time { + Some(current_op_time) if current_op_time < ts => Some(ts), + None => Some(ts), + _ => self.operation_time, + } + } + + /// The operation time returned by the last operation executed in this session. + pub fn operation_time(&self) -> Option { + self.operation_time + } + + pub(crate) fn causal_consistency(&self) -> bool { + self.options() + .and_then(|opts| opts.causal_consistency) + .unwrap_or(!self.is_implicit()) + } + + /// Mark this session (and the underlying server session) as dirty. + pub(crate) fn mark_dirty(&mut self) { + self.server_session.dirty = true; + } + + /// Updates the date that the underlying server session was last used as part of an operation + /// sent to the server. + pub(crate) fn update_last_use(&mut self) { + self.server_session.last_use = Instant::now(); + } + + /// Gets the current txn_number. + pub(crate) fn txn_number(&self) -> i64 { + self.server_session.txn_number + } + + /// Increments the txn_number. + pub(crate) fn increment_txn_number(&mut self) { + self.server_session.txn_number += 1; + } + + /// Gets the txn_number to use for an operation based on the current transaction status and the + /// operation's retryability. + pub(crate) fn get_txn_number_for_operation( + &mut self, + retryability: Retryability, + ) -> Option { + if self.transaction.state != TransactionState::None { + Some(self.txn_number()) + } else if retryability == Retryability::Write { + self.increment_txn_number(); + Some(self.txn_number()) + } else { + None + } + } + + /// Pin mongos to session. + pub(crate) fn pin_mongos(&mut self, address: ServerAddress) { + self.transaction.pinned = Some(TransactionPin::Mongos(SelectionCriteria::Predicate( + Arc::new(move |server_info: &ServerInfo| *server_info.address() == address), + ))); + } + + /// Pin the connection to the session. + pub(crate) fn pin_connection(&mut self, handle: PinnedConnectionHandle) { + self.transaction.pinned = Some(TransactionPin::Connection(handle)); + } + + pub(crate) fn unpin(&mut self) { + self.transaction.pinned = None; + } + + /// Whether this session is dirty. + #[cfg(test)] + pub(crate) fn is_dirty(&self) -> bool { + self.server_session.dirty + } + + fn default_transaction_options(&self) -> Option<&TransactionOptions> { + self.options + .as_ref() + .and_then(|options| options.default_transaction_options.as_ref()) + } +} + +struct DroppedClientSession { + cluster_time: Option, + server_session: ServerSession, + client: Client, + is_implicit: bool, + options: Option, + transaction: Transaction, + snapshot_time: Option, + operation_time: Option, +} + +impl From for ClientSession { + fn from(dropped_session: DroppedClientSession) -> Self { + Self { + cluster_time: dropped_session.cluster_time, + server_session: dropped_session.server_session, + drop_token: dropped_session.client.register_async_drop(), + client: dropped_session.client, + is_implicit: dropped_session.is_implicit, + options: dropped_session.options, + transaction: dropped_session.transaction, + snapshot_time: dropped_session.snapshot_time, + operation_time: dropped_session.operation_time, + #[cfg(test)] + convenient_transaction_timeout: None, + } + } +} + +impl Drop for ClientSession { + fn drop(&mut self) { + if self.transaction.state == TransactionState::InProgress { + let dropped_session = DroppedClientSession { + cluster_time: self.cluster_time.clone(), + server_session: self.server_session.clone(), + client: self.client.clone(), + is_implicit: self.is_implicit, + options: self.options.clone(), + transaction: self.transaction.take(), + snapshot_time: self.snapshot_time, + operation_time: self.operation_time, + }; + self.drop_token.spawn(async move { + let mut session: ClientSession = dropped_session.into(); + let _result = session.abort_transaction().await; + }); + } else { + let client = self.client.clone(); + let server_session = self.server_session.clone(); + self.drop_token.spawn(async move { + client.check_in_server_session(server_session).await; + }); + } + } +} + +/// Client side abstraction of a server session. These are pooled and may be associated with +/// multiple `ClientSession`s over the course of their lifetime. +#[derive(Clone, Debug)] +pub(crate) struct ServerSession { + /// The id of the server session to which this corresponds. + pub(crate) id: Document, + + /// The last time an operation was executed with this session. + last_use: std::time::Instant, + + /// Whether a network error was encountered while using this session. + dirty: bool, + + /// A monotonically increasing transaction number for this session. + txn_number: i64, +} + +impl ServerSession { + /// Creates a new session, generating the id client side. + fn new() -> Self { + let binary = Bson::Binary(Binary { + subtype: BinarySubtype::Uuid, + bytes: Uuid::new_v4().as_bytes().to_vec(), + }); + + Self { + id: doc! { "id": binary }, + last_use: Instant::now(), + dirty: false, + txn_number: 0, + } + } + + /// Determines if this server session is about to expire in a short amount of time (1 minute). + fn is_about_to_expire(&self, logical_session_timeout: Option) -> bool { + let timeout = match logical_session_timeout { + Some(t) => t, + None => return false, + }; + let expiration_date = self.last_use + timeout; + expiration_date < Instant::now() + Duration::from_secs(60) + } +} diff --git a/src/client/session/action.rs b/src/client/session/action.rs new file mode 100644 index 000000000..c480e0829 --- /dev/null +++ b/src/client/session/action.rs @@ -0,0 +1,426 @@ +use std::time::{Duration, Instant}; + +use crate::{ + action::{action_impl, AbortTransaction, CommitTransaction, StartTransaction}, + client::options::TransactionOptions, + error::{ErrorKind, Result}, + operation::{self, Operation}, + sdam::TransactionSupportStatus, + BoxFuture, + ClientSession, +}; + +use super::TransactionState; + +impl ClientSession { + async fn start_transaction_impl(&mut self, options: Option) -> Result<()> { + if self + .options + .as_ref() + .and_then(|o| o.snapshot) + .unwrap_or(false) + { + return Err(ErrorKind::Transaction { + message: "Transactions are not supported in snapshot sessions".into(), + } + .into()); + } + match self.transaction.state { + TransactionState::Starting | TransactionState::InProgress => { + return Err(ErrorKind::Transaction { + message: "transaction already in progress".into(), + } + .into()); + } + TransactionState::Committed { .. } => { + self.unpin(); // Unpin session if previous transaction is committed. + } + _ => {} + } + match self.client.transaction_support_status().await? { + TransactionSupportStatus::Supported => { + let mut options = match options { + Some(mut options) => { + if let Some(defaults) = self.default_transaction_options() { + merge_options!( + defaults, + options, + [ + read_concern, + write_concern, + selection_criteria, + max_commit_time + ] + ); + } + Some(options) + } + None => self.default_transaction_options().cloned(), + }; + resolve_options!( + self.client, + options, + [read_concern, write_concern, selection_criteria] + ); + + if let Some(ref options) = options { + if !options + .write_concern + .as_ref() + .map(|wc| wc.is_acknowledged()) + .unwrap_or(true) + { + return Err(ErrorKind::Transaction { + message: "transactions do not support unacknowledged write concerns" + .into(), + } + .into()); + } + } + + self.increment_txn_number(); + self.transaction.start(options); + Ok(()) + } + _ => Err(ErrorKind::Transaction { + message: "Transactions are not supported by this deployment".into(), + } + .into()), + } + } +} + +#[action_impl] +impl<'a> Action for StartTransaction<&'a mut ClientSession> { + type Future = StartTransactionFuture; + + async fn execute(self) -> Result<()> { + self.session.start_transaction_impl(self.options).await + } +} + +macro_rules! convenient_run { + ( + $session:expr, + $start_transaction:expr, + $callback:expr, + $abort_transaction:expr, + $commit_transaction:expr, + ) => {{ + let timeout = Duration::from_secs(120); + #[cfg(test)] + let timeout = $session.convenient_transaction_timeout.unwrap_or(timeout); + let start = Instant::now(); + + use crate::error::{TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}; + + 'transaction: loop { + $start_transaction?; + let ret = match $callback { + Ok(v) => v, + Err(e) => { + if matches!( + $session.transaction.state, + TransactionState::Starting | TransactionState::InProgress + ) { + $abort_transaction?; + } + if e.contains_label(TRANSIENT_TRANSACTION_ERROR) && start.elapsed() < timeout { + continue 'transaction; + } + return Err(e); + } + }; + if matches!( + $session.transaction.state, + TransactionState::None + | TransactionState::Aborted + | TransactionState::Committed { .. } + ) { + return Ok(ret); + } + 'commit: loop { + match $commit_transaction { + Ok(()) => return Ok(ret), + Err(e) => { + if e.is_max_time_ms_expired_error() || start.elapsed() >= timeout { + return Err(e); + } + if e.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) { + continue 'commit; + } + if e.contains_label(TRANSIENT_TRANSACTION_ERROR) { + continue 'transaction; + } + return Err(e); + } + } + } + } + }}; +} + +impl StartTransaction<&mut ClientSession> { + /// Starts a transaction, runs the given callback, and commits or aborts the transaction. + /// Transient transaction errors will cause the callback or the commit to be retried; + /// other errors will cause the transaction to be aborted and the error returned to the + /// caller. If the callback needs to provide its own error information, the + /// [`Error::custom`](crate::error::Error::custom) method can accept an arbitrary payload that + /// can be retrieved via [`Error::get_custom`](crate::error::Error::get_custom). + /// + /// If a command inside the callback fails, it may cause the transaction on the server to be + /// aborted. This situation is normally handled transparently by the driver. However, if the + /// application does not return that error from the callback, the driver will not be able to + /// determine whether the transaction was aborted or not. The driver will then retry the + /// callback indefinitely. To avoid this situation, the application MUST NOT silently handle + /// errors within the callback. If the application needs to handle errors within the + /// callback, it MUST return them after doing so. + /// + /// Because the callback can be repeatedly executed and because it returns a future, the rust + /// closure borrowing rules for captured values can be overly restrictive. As a + /// convenience, `and_run` accepts a context argument that will be passed to the + /// callback along with the session: + /// + /// ```no_run + /// # use mongodb::{bson::{doc, Document}, error::Result, Client}; + /// # use futures::FutureExt; + /// # async fn wrapper() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let mut session = client.start_session().await?; + /// let coll = client.database("mydb").collection::("mycoll"); + /// let my_data = "my data".to_string(); + /// // This works: + /// session.start_transaction().and_run( + /// (&coll, &my_data), + /// |session, (coll, my_data)| async move { + /// coll.insert_one(doc! { "data": *my_data }).session(session).await + /// }.boxed() + /// ).await?; + /// /* This will not compile with a "variable moved due to use in generator" error: + /// session.start_transaction().and_run( + /// (), + /// |session, _| async move { + /// coll.insert_one(doc! { "data": my_data }).session(session).await + /// }.boxed() + /// ).await?; + /// */ + /// # Ok(()) + /// # } + /// ``` + #[rustversion::attr(since(1.85), deprecated = "use and_run2")] + pub async fn and_run(self, mut context: C, mut callback: F) -> Result + where + F: for<'b> FnMut(&'b mut ClientSession, &'b mut C) -> BoxFuture<'b, Result>, + { + convenient_run!( + self.session, + self.session + .start_transaction() + .with_options(self.options.clone()) + .await, + callback(self.session, &mut context).await, + self.session.abort_transaction().await, + self.session.commit_transaction().await, + ) + } + + /// Starts a transaction, runs the given callback, and commits or aborts the transaction. + /// Transient transaction errors will cause the callback or the commit to be retried; + /// other errors will cause the transaction to be aborted and the error returned to the + /// caller. If the callback needs to provide its own error information, the + /// [`Error::custom`](crate::error::Error::custom) method can accept an arbitrary payload that + /// can be retrieved via [`Error::get_custom`](crate::error::Error::get_custom). + /// + /// If a command inside the callback fails, it may cause the transaction on the server to be + /// aborted. This situation is normally handled transparently by the driver. However, if the + /// application does not return that error from the callback, the driver will not be able to + /// determine whether the transaction was aborted or not. The driver will then retry the + /// callback indefinitely. To avoid this situation, the application MUST NOT silently handle + /// errors within the callback. If the application needs to handle errors within the + /// callback, it MUST return them after doing so. + /// + /// This version of the method uses an async closure, which means it's both more convenient and + /// avoids the lifetime issues of `and_run`, but is only available in Rust versions 1.85 and + /// above. + /// + /// Because the callback can be repeatedly executed, code within the callback cannot consume + /// owned values, even values owned by the callback itself: + /// + /// ```no_run + /// # use mongodb::{bson::{doc, Document}, error::Result, Client}; + /// # use futures::FutureExt; + /// # async fn wrapper() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://example.com").await?; + /// # let mut session = client.start_session().await?; + /// let coll = client.database("mydb").collection::("mycoll"); + /// let my_data = "my data".to_string(); + /// // This works: + /// session.start_transaction().and_run2( + /// async move |session| { + /// coll.insert_one(doc! { "data": my_data.clone() }).session(session).await + /// } + /// ).await?; + /// /* This will not compile: + /// session.start_transaction().and_run2( + /// async move |session| { + /// coll.insert_one(doc! { "data": my_data }).session(session).await + /// } + /// ).await?; + /// */ + /// # Ok(()) + /// # } + /// ``` + #[rustversion::since(1.85)] + pub async fn and_run2(self, mut callback: F) -> Result + where + F: for<'b> AsyncFnMut(&'b mut ClientSession) -> Result, + { + convenient_run!( + self.session, + self.session + .start_transaction() + .with_options(self.options.clone()) + .await, + callback(self.session).await, + self.session.abort_transaction().await, + self.session.commit_transaction().await, + ) + } +} + +#[cfg(feature = "sync")] +impl StartTransaction<&mut crate::sync::ClientSession> { + /// Synchronously execute this action. + pub fn run(self) -> Result<()> { + crate::sync::TOKIO_RUNTIME.block_on( + self.session + .async_client_session + .start_transaction_impl(self.options), + ) + } + + /// Starts a transaction, runs the given callback, and commits or aborts the transaction. + /// Transient transaction errors will cause the callback or the commit to be retried; + /// other errors will cause the transaction to be aborted and the error returned to the + /// caller. If the callback needs to provide its own error information, the + /// [`Error::custom`](crate::error::Error::custom) method can accept an arbitrary payload that + /// can be retrieved via [`Error::get_custom`](crate::error::Error::get_custom). + /// + /// If a command inside the callback fails, it may cause the transaction on the server to be + /// aborted. This situation is normally handled transparently by the driver. However, if the + /// application does not return that error from the callback, the driver will not be able to + /// determine whether the transaction was aborted or not. The driver will then retry the + /// callback indefinitely. To avoid this situation, the application MUST NOT silently handle + /// errors within the callback. If the application needs to handle errors within the + /// callback, it MUST return them after doing so. + pub fn and_run(self, mut callback: F) -> Result + where + F: for<'b> FnMut(&'b mut crate::sync::ClientSession) -> Result, + { + convenient_run!( + self.session.async_client_session, + self.session + .start_transaction() + .with_options(self.options.clone()) + .run(), + callback(self.session), + self.session.abort_transaction().run(), + self.session.commit_transaction().run(), + ) + } +} + +#[action_impl] +impl<'a> Action for CommitTransaction<'a> { + type Future = CommitTransactionFuture; + + async fn execute(self) -> Result<()> { + match &mut self.session.transaction.state { + TransactionState::None => Err(ErrorKind::Transaction { + message: "no transaction started".into(), + } + .into()), + TransactionState::Aborted => Err(ErrorKind::Transaction { + message: "Cannot call commitTransaction after calling abortTransaction".into(), + } + .into()), + TransactionState::Starting => { + self.session.transaction.commit(false); + Ok(()) + } + TransactionState::InProgress => { + let commit_transaction = + operation::CommitTransaction::new(self.session.transaction.options.clone()); + self.session.transaction.commit(true); + self.session + .client + .clone() + .execute_operation(commit_transaction, self.session) + .await + } + TransactionState::Committed { + data_committed: true, + } => { + let mut commit_transaction = + operation::CommitTransaction::new(self.session.transaction.options.clone()); + commit_transaction.update_for_retry(); + self.session + .client + .clone() + .execute_operation(commit_transaction, self.session) + .await + } + TransactionState::Committed { + data_committed: false, + } => Ok(()), + } + } +} + +#[action_impl] +impl<'a> Action for AbortTransaction<'a> { + type Future = AbortTransactionFuture; + + async fn execute(self) -> Result<()> { + match self.session.transaction.state { + TransactionState::None => Err(ErrorKind::Transaction { + message: "no transaction started".into(), + } + .into()), + TransactionState::Committed { .. } => Err(ErrorKind::Transaction { + message: "Cannot call abortTransaction after calling commitTransaction".into(), + } + .into()), + TransactionState::Aborted => Err(ErrorKind::Transaction { + message: "cannot call abortTransaction twice".into(), + } + .into()), + TransactionState::Starting => { + self.session.transaction.abort(); + Ok(()) + } + TransactionState::InProgress => { + let write_concern = self + .session + .transaction + .options + .as_ref() + .and_then(|options| options.write_concern.as_ref()) + .cloned(); + let abort_transaction = operation::AbortTransaction::new( + write_concern, + self.session.transaction.pinned.take(), + ); + self.session.transaction.abort(); + // Errors returned from running an abortTransaction command should be ignored. + let _result = self + .session + .client + .clone() + .execute_operation(abort_transaction, &mut *self.session) + .await; + Ok(()) + } + } + } +} diff --git a/src/client/session/cluster_time.rs b/src/client/session/cluster_time.rs index 33699be15..30bf5ea03 100644 --- a/src/client/session/cluster_time.rs +++ b/src/client/session/cluster_time.rs @@ -1,4 +1,4 @@ -use derivative::Derivative; +use derive_where::derive_where; use serde::{Deserialize, Serialize}; use crate::bson::{Document, Timestamp}; @@ -7,13 +7,13 @@ use crate::bson::{Document, Timestamp}; /// /// See [the MongoDB documentation](https://www.mongodb.com/docs/manual/core/read-isolation-consistency-recency/) /// for more information. -#[derive(Debug, Deserialize, Clone, Serialize, Derivative)] -#[derivative(PartialEq, Eq)] +#[derive(Debug, Deserialize, Clone, Serialize)] +#[derive_where(PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct ClusterTime { pub(crate) cluster_time: Timestamp, - #[derivative(PartialEq = "ignore")] + #[derive_where(skip)] pub(crate) signature: Document, } diff --git a/src/client/session/mod.rs b/src/client/session/mod.rs deleted file mode 100644 index 1b2ba0052..000000000 --- a/src/client/session/mod.rs +++ /dev/null @@ -1,778 +0,0 @@ -mod cluster_time; -mod pool; -#[cfg(test)] -mod test; - -use std::{ - collections::HashSet, - sync::Arc, - time::{Duration, Instant}, -}; - -use lazy_static::lazy_static; -use uuid::Uuid; - -use crate::{ - bson::{doc, spec::BinarySubtype, Binary, Bson, Document, Timestamp}, - cmap::conn::PinnedConnectionHandle, - error::{ErrorKind, Result}, - operation::{AbortTransaction, CommitTransaction, Operation}, - options::{SessionOptions, TransactionOptions}, - runtime, - sdam::{ServerInfo, TransactionSupportStatus}, - selection_criteria::SelectionCriteria, - Client, -}; -pub use cluster_time::ClusterTime; -pub(super) use pool::ServerSessionPool; - -use super::options::ServerAddress; - -lazy_static! { - pub(crate) static ref SESSIONS_UNSUPPORTED_COMMANDS: HashSet<&'static str> = { - let mut hash_set = HashSet::new(); - hash_set.insert("killcursors"); - hash_set.insert("parallelcollectionscan"); - hash_set - }; -} - -/// A MongoDB client session. This struct represents a logical session used for ordering sequential -/// operations. To create a `ClientSession`, call `start_session` on a `Client`. -/// -/// `ClientSession` instances are not thread safe or fork safe. They can only be used by one thread -/// or process at a time. -/// -/// ## Transactions -/// Transactions are used to execute a series of operations across multiple documents and -/// collections atomically. For more information about when and how to use transactions in MongoDB, -/// see the [manual](https://www.mongodb.com/docs/manual/core/transactions/). -/// -/// Replica set transactions are supported on MongoDB 4.0+. Sharded transactions are supported on -/// MongoDDB 4.2+. Transactions are associated with a `ClientSession`. To begin a transaction, call -/// [`ClientSession::start_transaction`] on a `ClientSession`. The `ClientSession` must be passed to -/// operations to be executed within the transaction. -/// -/// ```rust -/// use mongodb::{ -/// bson::{doc, Document}, -/// error::{Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}, -/// options::{Acknowledgment, ReadConcern, TransactionOptions, WriteConcern}, -/// # Client, -/// ClientSession, -/// Collection, -/// }; -/// -/// # async fn do_stuff() -> Result<()> { -/// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// # let coll: Collection = client.database("foo").collection("bar"); -/// let mut session = client.start_session(None).await?; -/// let options = TransactionOptions::builder() -/// .read_concern(ReadConcern::majority()) -/// .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build()) -/// .build(); -/// session.start_transaction(options).await?; -/// // A "TransientTransactionError" label indicates that the entire transaction can be retried -/// // with a reasonable expectation that it will succeed. -/// while let Err(error) = execute_transaction(&coll, &mut session).await { -/// if !error.contains_label(TRANSIENT_TRANSACTION_ERROR) { -/// break; -/// } -/// } -/// # Ok(()) -/// # } -/// -/// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> { -/// coll.insert_one_with_session(doc! { "x": 1 }, None, session).await?; -/// coll.delete_one_with_session(doc! { "y": 2 }, None, session).await?; -/// // An "UnknownTransactionCommitResult" label indicates that it is unknown whether the -/// // commit has satisfied the write concern associated with the transaction. If an error -/// // with this label is returned, it is safe to retry the commit until the write concern is -/// // satisfied or an error without the label is returned. -/// loop { -/// let result = session.commit_transaction().await; -/// if let Err(ref error) = result { -/// if error.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) { -/// continue; -/// } -/// } -/// result? -/// } -/// } -/// ``` -#[derive(Debug)] -pub struct ClientSession { - cluster_time: Option, - server_session: ServerSession, - client: Client, - is_implicit: bool, - options: Option, - pub(crate) transaction: Transaction, - pub(crate) snapshot_time: Option, - pub(crate) operation_time: Option, - #[cfg(test)] - pub(crate) convenient_transaction_timeout: Option, -} - -#[derive(Debug)] -pub(crate) struct Transaction { - pub(crate) state: TransactionState, - pub(crate) options: Option, - pub(crate) pinned: Option, - pub(crate) recovery_token: Option, -} - -impl Transaction { - pub(crate) fn start(&mut self, options: Option) { - self.state = TransactionState::Starting; - self.options = options; - self.recovery_token = None; - } - - pub(crate) fn commit(&mut self, data_committed: bool) { - self.state = TransactionState::Committed { data_committed }; - } - - pub(crate) fn abort(&mut self) { - self.state = TransactionState::Aborted; - self.options = None; - self.pinned = None; - } - - pub(crate) fn reset(&mut self) { - self.state = TransactionState::None; - self.options = None; - self.pinned = None; - self.recovery_token = None; - } - - pub(crate) fn pinned_mongos(&self) -> Option<&SelectionCriteria> { - match &self.pinned { - Some(TransactionPin::Mongos(s)) => Some(s), - _ => None, - } - } - - pub(crate) fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - match &self.pinned { - Some(TransactionPin::Connection(c)) => Some(c), - _ => None, - } - } - - fn take(&mut self) -> Self { - Transaction { - state: self.state.clone(), - options: self.options.take(), - pinned: self.pinned.take(), - recovery_token: self.recovery_token.take(), - } - } -} - -impl Default for Transaction { - fn default() -> Self { - Self { - state: TransactionState::None, - options: None, - pinned: None, - recovery_token: None, - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum TransactionState { - None, - Starting, - InProgress, - Committed { - /// Whether any data was committed when commit_transaction was initially called. This is - /// required to determine whether a commitTransaction command should be run if the user - /// calls commit_transaction again. - data_committed: bool, - }, - Aborted, -} - -#[derive(Debug)] -pub(crate) enum TransactionPin { - Mongos(SelectionCriteria), - Connection(PinnedConnectionHandle), -} - -impl ClientSession { - /// Creates a new `ClientSession` by checking out a corresponding `ServerSession` from the - /// provided client's session pool. - pub(crate) async fn new( - client: Client, - options: Option, - is_implicit: bool, - ) -> Self { - let timeout = client.inner.topology.logical_session_timeout(); - let server_session = client.inner.session_pool.check_out(timeout).await; - Self { - client, - server_session, - cluster_time: None, - is_implicit, - options, - transaction: Default::default(), - snapshot_time: None, - operation_time: None, - #[cfg(test)] - convenient_transaction_timeout: None, - } - } - - /// The client used to create this session. - pub fn client(&self) -> Client { - self.client.clone() - } - - /// The id of this session. - pub fn id(&self) -> &Document { - &self.server_session.id - } - - /// Whether this session was created implicitly by the driver or explcitly by the user. - pub(crate) fn is_implicit(&self) -> bool { - self.is_implicit - } - - /// Whether this session is currently in a transaction. - pub(crate) fn in_transaction(&self) -> bool { - self.transaction.state == TransactionState::Starting - || self.transaction.state == TransactionState::InProgress - } - - /// The highest seen cluster time this session has seen so far. - /// This will be `None` if this session has not been used in an operation yet. - pub fn cluster_time(&self) -> Option<&ClusterTime> { - self.cluster_time.as_ref() - } - - /// The options used to create this session. - pub fn options(&self) -> Option<&SessionOptions> { - self.options.as_ref() - } - - /// Set the cluster time to the provided one if it is greater than this session's highest seen - /// cluster time or if this session's cluster time is `None`. - pub fn advance_cluster_time(&mut self, to: &ClusterTime) { - if self.cluster_time().map(|ct| ct < to).unwrap_or(true) { - self.cluster_time = Some(to.clone()); - } - } - - /// Advance operation time for this session. If the provided timestamp is earlier than this - /// session's current operation time, then the operation time is unchanged. - pub fn advance_operation_time(&mut self, ts: Timestamp) { - self.operation_time = match self.operation_time { - Some(current_op_time) if current_op_time < ts => Some(ts), - None => Some(ts), - _ => self.operation_time, - } - } - - /// The operation time returned by the last operation executed in this session. - pub fn operation_time(&self) -> Option { - self.operation_time - } - - /// Mark this session (and the underlying server session) as dirty. - pub(crate) fn mark_dirty(&mut self) { - self.server_session.dirty = true; - } - - /// Updates the date that the underlying server session was last used as part of an operation - /// sent to the server. - pub(crate) fn update_last_use(&mut self) { - self.server_session.last_use = Instant::now(); - } - - /// Gets the current txn_number. - pub(crate) fn txn_number(&self) -> i64 { - self.server_session.txn_number - } - - /// Increments the txn_number. - pub(crate) fn increment_txn_number(&mut self) { - self.server_session.txn_number += 1; - } - - /// Increments the txn_number and returns the new value. - pub(crate) fn get_and_increment_txn_number(&mut self) -> i64 { - self.increment_txn_number(); - self.server_session.txn_number - } - - /// Pin mongos to session. - pub(crate) fn pin_mongos(&mut self, address: ServerAddress) { - self.transaction.pinned = Some(TransactionPin::Mongos(SelectionCriteria::Predicate( - Arc::new(move |server_info: &ServerInfo| *server_info.address() == address), - ))); - } - - /// Pin the connection to the session. - pub(crate) fn pin_connection(&mut self, handle: PinnedConnectionHandle) { - self.transaction.pinned = Some(TransactionPin::Connection(handle)); - } - - pub(crate) fn unpin(&mut self) { - self.transaction.pinned = None; - } - - /// Whether this session is dirty. - #[cfg(test)] - pub(crate) fn is_dirty(&self) -> bool { - self.server_session.dirty - } - - /// Starts a new transaction on this session with the given `TransactionOptions`. If no options - /// are provided, the session's `defaultTransactionOptions` will be used. This session must - /// be passed into each operation within the transaction; otherwise, the operation will be - /// executed outside of the transaction. - /// - /// Errors returned from operations executed within a transaction may include a - /// [`crate::error::TRANSIENT_TRANSACTION_ERROR`] label. This label indicates that the entire - /// transaction can be retried with a reasonable expectation that it will succeed. - /// - /// Transactions are supported on MongoDB 4.0+. The Rust driver currently only supports - /// transactions on replica sets. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com").await?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None).await?; - /// session.start_transaction(None).await?; - /// let result = coll.insert_one_with_session(doc! { "x": 1 }, None, &mut session).await?; - /// session.commit_transaction().await?; - /// # Ok(()) - /// # } - /// ``` - pub async fn start_transaction( - &mut self, - options: impl Into>, - ) -> Result<()> { - if self - .options - .as_ref() - .and_then(|o| o.snapshot) - .unwrap_or(false) - { - return Err(ErrorKind::Transaction { - message: "Transactions are not supported in snapshot sessions".into(), - } - .into()); - } - match self.transaction.state { - TransactionState::Starting | TransactionState::InProgress => { - return Err(ErrorKind::Transaction { - message: "transaction already in progress".into(), - } - .into()); - } - TransactionState::Committed { .. } => { - self.unpin(); // Unpin session if previous transaction is committed. - } - _ => {} - } - match self.client.transaction_support_status().await? { - TransactionSupportStatus::Supported => { - let mut options = match options.into() { - Some(mut options) => { - if let Some(defaults) = self.default_transaction_options() { - merge_options!( - defaults, - options, - [ - read_concern, - write_concern, - selection_criteria, - max_commit_time - ] - ); - } - Some(options) - } - None => self.default_transaction_options().cloned(), - }; - resolve_options!( - self.client, - options, - [read_concern, write_concern, selection_criteria] - ); - - if let Some(ref options) = options { - if !options - .write_concern - .as_ref() - .map(|wc| wc.is_acknowledged()) - .unwrap_or(true) - { - return Err(ErrorKind::Transaction { - message: "transactions do not support unacknowledged write concerns" - .into(), - } - .into()); - } - } - - self.increment_txn_number(); - self.transaction.start(options); - Ok(()) - } - _ => Err(ErrorKind::Transaction { - message: "Transactions are not supported by this deployment".into(), - } - .into()), - } - } - - /// Commits the transaction that is currently active on this session. - /// - /// - /// This method may return an error with a [`crate::error::UNKNOWN_TRANSACTION_COMMIT_RESULT`] - /// label. This label indicates that it is unknown whether the commit has satisfied the write - /// concern associated with the transaction. If an error with this label is returned, it is - /// safe to retry the commit until the write concern is satisfied or an error without the label - /// is returned. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com").await?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None).await?; - /// session.start_transaction(None).await?; - /// let result = coll.insert_one_with_session(doc! { "x": 1 }, None, &mut session).await?; - /// session.commit_transaction().await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn commit_transaction(&mut self) -> Result<()> { - match &mut self.transaction.state { - TransactionState::None => Err(ErrorKind::Transaction { - message: "no transaction started".into(), - } - .into()), - TransactionState::Aborted => Err(ErrorKind::Transaction { - message: "Cannot call commitTransaction after calling abortTransaction".into(), - } - .into()), - TransactionState::Starting => { - self.transaction.commit(false); - Ok(()) - } - TransactionState::InProgress => { - let commit_transaction = CommitTransaction::new(self.transaction.options.clone()); - self.transaction.commit(true); - self.client - .clone() - .execute_operation(commit_transaction, self) - .await - } - TransactionState::Committed { - data_committed: true, - } => { - let mut commit_transaction = - CommitTransaction::new(self.transaction.options.clone()); - commit_transaction.update_for_retry(); - self.client - .clone() - .execute_operation(commit_transaction, self) - .await - } - TransactionState::Committed { - data_committed: false, - } => Ok(()), - } - } - - /// Aborts the transaction that is currently active on this session. Any open transaction will - /// be aborted automatically in the `Drop` implementation of `ClientSession`. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession, Collection}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com").await?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None).await?; - /// session.start_transaction(None).await?; - /// match execute_transaction(&coll, &mut session).await { - /// Ok(_) => session.commit_transaction().await?, - /// Err(_) => session.abort_transaction().await?, - /// } - /// # Ok(()) - /// # } - /// - /// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> { - /// coll.insert_one_with_session(doc! { "x": 1 }, None, session).await?; - /// coll.delete_one_with_session(doc! { "y": 2 }, None, session).await?; - /// Ok(()) - /// } - /// ``` - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn abort_transaction(&mut self) -> Result<()> { - match self.transaction.state { - TransactionState::None => Err(ErrorKind::Transaction { - message: "no transaction started".into(), - } - .into()), - TransactionState::Committed { .. } => Err(ErrorKind::Transaction { - message: "Cannot call abortTransaction after calling commitTransaction".into(), - } - .into()), - TransactionState::Aborted => Err(ErrorKind::Transaction { - message: "cannot call abortTransaction twice".into(), - } - .into()), - TransactionState::Starting => { - self.transaction.abort(); - Ok(()) - } - TransactionState::InProgress => { - let write_concern = self - .transaction - .options - .as_ref() - .and_then(|options| options.write_concern.as_ref()) - .cloned(); - let abort_transaction = - AbortTransaction::new(write_concern, self.transaction.pinned.take()); - self.transaction.abort(); - // Errors returned from running an abortTransaction command should be ignored. - let _result = self - .client - .clone() - .execute_operation(abort_transaction, &mut *self) - .await; - Ok(()) - } - } - } - - /// Starts a transaction, runs the given callback, and commits or aborts the transaction. - /// Transient transaction errors will cause the callback or the commit to be retried; - /// other errors will cause the transaction to be aborted and the error returned to the - /// caller. If the callback needs to provide its own error information, the - /// [`Error::custom`](crate::error::Error::custom) method can accept an arbitrary payload that - /// can be retrieved via [`Error::get_custom`](crate::error::Error::get_custom). - /// - /// Because the callback can be repeatedly executed and because it returns a future, the rust - /// closure borrowing rules for captured values can be overly restrictive. As a - /// convenience, `with_transaction` accepts a context argument that will be passed to the - /// callback along with the session: - /// - /// ```no_run - /// # use mongodb::{bson::{doc, Document}, error::Result, Client}; - /// # use futures::FutureExt; - /// # async fn wrapper() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com").await?; - /// # let mut session = client.start_session(None).await?; - /// let coll = client.database("mydb").collection::("mycoll"); - /// let my_data = "my data".to_string(); - /// // This works: - /// session.with_transaction( - /// (&coll, &my_data), - /// |session, (coll, my_data)| async move { - /// coll.insert_one_with_session(doc! { "data": *my_data }, None, session).await - /// }.boxed(), - /// None, - /// ).await?; - /// /* This will not compile with a "variable moved due to use in generator" error: - /// session.with_transaction( - /// (), - /// |session, _| async move { - /// coll.insert_one_with_session(doc! { "data": my_data }, None, session).await - /// }.boxed(), - /// None, - /// ).await?; - /// */ - /// # Ok(()) - /// # } - /// ``` - pub async fn with_transaction( - &mut self, - mut context: C, - mut callback: F, - options: impl Into>, - ) -> Result - where - F: for<'a> FnMut(&'a mut ClientSession, &'a mut C) -> BoxFuture<'a, Result>, - { - let options = options.into(); - let timeout = Duration::from_secs(120); - #[cfg(test)] - let timeout = self.convenient_transaction_timeout.unwrap_or(timeout); - let start = Instant::now(); - - use crate::error::{TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}; - - 'transaction: loop { - self.start_transaction(options.clone()).await?; - let ret = match callback(self, &mut context).await { - Ok(v) => v, - Err(e) => { - if matches!( - self.transaction.state, - TransactionState::Starting | TransactionState::InProgress - ) { - self.abort_transaction().await?; - } - if e.contains_label(TRANSIENT_TRANSACTION_ERROR) && start.elapsed() < timeout { - continue 'transaction; - } - return Err(e); - } - }; - if matches!( - self.transaction.state, - TransactionState::None - | TransactionState::Aborted - | TransactionState::Committed { .. } - ) { - return Ok(ret); - } - 'commit: loop { - match self.commit_transaction().await { - Ok(()) => return Ok(ret), - Err(e) => { - if e.is_max_time_ms_expired_error() || start.elapsed() >= timeout { - return Err(e); - } - if e.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) { - continue 'commit; - } - if e.contains_label(TRANSIENT_TRANSACTION_ERROR) { - continue 'transaction; - } - return Err(e); - } - } - } - } - } - - fn default_transaction_options(&self) -> Option<&TransactionOptions> { - self.options - .as_ref() - .and_then(|options| options.default_transaction_options.as_ref()) - } -} - -pub type BoxFuture<'a, T> = std::pin::Pin + Send + 'a>>; - -struct DroppedClientSession { - cluster_time: Option, - server_session: ServerSession, - client: Client, - is_implicit: bool, - options: Option, - transaction: Transaction, - snapshot_time: Option, - operation_time: Option, -} - -impl From for ClientSession { - fn from(dropped_session: DroppedClientSession) -> Self { - Self { - cluster_time: dropped_session.cluster_time, - server_session: dropped_session.server_session, - client: dropped_session.client, - is_implicit: dropped_session.is_implicit, - options: dropped_session.options, - transaction: dropped_session.transaction, - snapshot_time: dropped_session.snapshot_time, - operation_time: dropped_session.operation_time, - #[cfg(test)] - convenient_transaction_timeout: None, - } - } -} - -impl Drop for ClientSession { - fn drop(&mut self) { - if self.transaction.state == TransactionState::InProgress { - let dropped_session = DroppedClientSession { - cluster_time: self.cluster_time.clone(), - server_session: self.server_session.clone(), - client: self.client.clone(), - is_implicit: self.is_implicit, - options: self.options.clone(), - transaction: self.transaction.take(), - snapshot_time: self.snapshot_time, - operation_time: self.operation_time, - }; - runtime::execute(async move { - let mut session: ClientSession = dropped_session.into(); - let _result = session.abort_transaction().await; - }); - } else { - let client = self.client.clone(); - let server_session = self.server_session.clone(); - runtime::execute(async move { - client.check_in_server_session(server_session).await; - }); - } - } -} - -/// Client side abstraction of a server session. These are pooled and may be associated with -/// multiple `ClientSession`s over the course of their lifetime. -#[derive(Clone, Debug)] -pub(crate) struct ServerSession { - /// The id of the server session to which this corresponds. - id: Document, - - /// The last time an operation was executed with this session. - last_use: std::time::Instant, - - /// Whether a network error was encountered while using this session. - dirty: bool, - - /// A monotonically increasing transaction number for this session. - txn_number: i64, -} - -impl ServerSession { - /// Creates a new session, generating the id client side. - fn new() -> Self { - let binary = Bson::Binary(Binary { - subtype: BinarySubtype::Uuid, - bytes: Uuid::new_v4().as_bytes().to_vec(), - }); - - Self { - id: doc! { "id": binary }, - last_use: Instant::now(), - dirty: false, - txn_number: 0, - } - } - - /// Determines if this server session is about to expire in a short amount of time (1 minute). - fn is_about_to_expire(&self, logical_session_timeout: Option) -> bool { - let timeout = match logical_session_timeout { - Some(t) => t, - None => return false, - }; - let expiration_date = self.last_use + timeout; - expiration_date < Instant::now() + Duration::from_secs(60) - } -} diff --git a/src/client/session/pool.rs b/src/client/session/pool.rs index 34c9990b2..3980d214e 100644 --- a/src/client/session/pool.rs +++ b/src/client/session/pool.rs @@ -3,7 +3,6 @@ use std::{collections::VecDeque, time::Duration}; use tokio::sync::Mutex; use super::ServerSession; -#[cfg(test)] use crate::bson::Document; #[derive(Debug)] @@ -68,4 +67,10 @@ impl ServerSessionPool { pub(crate) async fn contains(&self, id: &Document) -> bool { self.pool.lock().await.iter().any(|s| &s.id == id) } + + /// Returns a list of the IDs of the sessions contained in the pool. + pub(crate) async fn get_session_ids(&self) -> Vec { + let sessions = self.pool.lock().await; + sessions.iter().map(|session| session.id.clone()).collect() + } } diff --git a/src/client/session/test.rs b/src/client/session/test.rs new file mode 100644 index 000000000..94024077c --- /dev/null +++ b/src/client/session/test.rs @@ -0,0 +1,613 @@ +mod causal_consistency; + +use std::{future::Future, sync::Arc, time::Duration}; + +use crate::bson::Document; +use futures::stream::StreamExt; + +use crate::{ + bson::{doc, Bson}, + coll::options::CountOptions, + error::Result, + event::sdam::SdamEvent, + options::{FindOptions, ReadConcern, ReadPreference, WriteConcern}, + sdam::ServerInfo, + selection_criteria::SelectionCriteria, + test::{ + get_client_options, + get_primary, + log_uncaptured, + topology_is_standalone, + util::event_buffer::EventBuffer, + Event, + EventClient, + }, + Client, + Collection, +}; + +/// Macro defining a closure that returns a future populated by an operation on the +/// provided client identifier. +macro_rules! client_op { + ($client:ident, $body:expr) => { + |$client| async move { + $body.await.unwrap(); + } + }; +} + +/// Macro defining a closure that returns a future populated by an operation on the +/// provided database identifier. +macro_rules! db_op { + ($test_name:expr, $db:ident, $body:expr) => { + |client| async move { + let $db = client.database($test_name); + $body.await.unwrap(); + } + }; +} + +/// Macro defining a closure that returns a future populated by an operation on the +/// provided collection identifier. +macro_rules! collection_op { + ($test_name:expr, $coll:ident, $body:expr) => { + |client| async move { + let $coll = client + .database($test_name) + .collection::($test_name); + $body.await.unwrap(); + } + }; +} + +/// Macro that runs the provided function with each operation that uses a session. +macro_rules! for_each_op { + ($test_name:expr, $test_func:ident) => {{ + // collection operations + $test_func( + "insert", + collection_op!($test_name, coll, coll.insert_one(doc! { "x": 1 })), + ) + .await; + $test_func( + "insert", + collection_op!($test_name, coll, coll.insert_many(vec![doc! { "x": 1 }])), + ) + .await; + $test_func( + "update", + collection_op!( + $test_name, + coll, + coll.replace_one(doc! { "x": 1 }, doc! { "x": 2 }) + ), + ) + .await; + $test_func( + "update", + collection_op!( + $test_name, + coll, + coll.update_one(doc! {}, doc! { "$inc": {"x": 5 } }) + ), + ) + .await; + $test_func( + "update", + collection_op!( + $test_name, + coll, + coll.update_many(doc! {}, doc! { "$inc": {"x": 5 } }) + ), + ) + .await; + $test_func( + "delete", + collection_op!($test_name, coll, coll.delete_one(doc! { "x": 1 })), + ) + .await; + $test_func( + "delete", + collection_op!($test_name, coll, coll.delete_many(doc! { "x": 1 })), + ) + .await; + $test_func( + "findAndModify", + collection_op!($test_name, coll, coll.find_one_and_delete(doc! { "x": 1 })), + ) + .await; + $test_func( + "findAndModify", + collection_op!( + $test_name, + coll, + coll.find_one_and_update(doc! {}, doc! { "$inc": { "x": 1 } }) + ), + ) + .await; + $test_func( + "findAndModify", + collection_op!( + $test_name, + coll, + coll.find_one_and_replace(doc! {}, doc! {"x": 1}) + ), + ) + .await; + $test_func( + "aggregate", + collection_op!( + $test_name, + coll, + coll.aggregate(vec![doc! { "$match": { "x": 1 } }]) + ), + ) + .await; + $test_func( + "find", + collection_op!($test_name, coll, coll.find(doc! { "x": 1 })), + ) + .await; + $test_func( + "find", + collection_op!($test_name, coll, coll.find_one(doc! { "x": 1 })), + ) + .await; + $test_func( + "distinct", + collection_op!($test_name, coll, coll.distinct("x", doc! {})), + ) + .await; + $test_func( + "aggregate", + collection_op!($test_name, coll, coll.count_documents(doc! {})), + ) + .await; + $test_func("drop", collection_op!($test_name, coll, coll.drop())).await; + + // db operations + $test_func( + "listCollections", + db_op!($test_name, db, db.list_collections()), + ) + .await; + $test_func( + "ping", + db_op!($test_name, db, db.run_command(doc! { "ping": 1 })), + ) + .await; + $test_func( + "create", + db_op!($test_name, db, db.create_collection("sessionopcoll")), + ) + .await; + $test_func("dropDatabase", db_op!($test_name, db, db.drop())).await; + + // client operations + $test_func("listDatabases", client_op!(client, client.list_databases())).await; + $test_func( + "listDatabases", + client_op!(client, client.list_database_names()), + ) + .await; + }}; +} + +/// Prose test 1 from sessions spec. +/// This test also satisifies the `endSession` testing requirement of prose test 5. +#[tokio::test] +async fn pool_is_lifo() { + if topology_is_standalone().await { + return; + } + + let client = Client::for_test().await; + // Wait for the implicit sessions created in TestClient::new to be returned to the pool. + tokio::time::sleep(Duration::from_millis(500)).await; + + let a = client.start_session().await.unwrap(); + let b = client.start_session().await.unwrap(); + + let a_id = a.id().clone(); + let b_id = b.id().clone(); + + // End both sessions, waiting after each to ensure the background task got scheduled + // in the Drop impls. + drop(a); + tokio::time::sleep(Duration::from_millis(250)).await; + + drop(b); + tokio::time::sleep(Duration::from_millis(250)).await; + + let s1 = client.start_session().await.unwrap(); + assert_eq!(s1.id(), &b_id); + + let s2 = client.start_session().await.unwrap(); + assert_eq!(s2.id(), &a_id); +} + +/// Prose test 2 from sessions spec. +#[tokio::test] +#[function_name::named] +async fn cluster_time_in_commands() { + if topology_is_standalone().await { + log_uncaptured("skipping cluster_time_in_commands test due to standalone topology"); + return; + } + + async fn cluster_time_test( + command_name: &str, + client: &Client, + event_buffer: &EventBuffer, + operation: F, + ) where + F: Fn(Client) -> G, + G: Future>, + { + let mut event_stream = event_buffer.stream(); + + operation(client.clone()) + .await + .expect("operation should succeed"); + + operation(client.clone()) + .await + .expect("operation should succeed"); + + let (first_command_started, first_command_succeeded) = event_stream + .next_successful_command_execution(Duration::from_secs(5), command_name) + .await + .unwrap_or_else(|| { + panic!( + "did not see command started and succeeded events for {}", + command_name + ) + }); + + assert!(first_command_started.command.get("$clusterTime").is_some()); + let response_cluster_time = first_command_succeeded + .reply + .get("$clusterTime") + .expect("should get cluster time from command response"); + + let (second_command_started, _) = event_stream + .next_successful_command_execution(Duration::from_secs(5), command_name) + .await + .unwrap_or_else(|| { + panic!( + "did not see command started and succeeded events for {}", + command_name + ) + }); + + assert_eq!( + response_cluster_time, + second_command_started + .command + .get("$clusterTime") + .expect("second command should contain cluster time"), + "cluster time not equal for {}", + command_name + ); + } + + let buffer = EventBuffer::new(); + let mut options = get_client_options().await.clone(); + options.heartbeat_freq = Some(Duration::from_secs(1000)); + options.command_event_handler = Some(buffer.handler()); + options.sdam_event_handler = Some(buffer.handler()); + + // Ensure we only connect to one server so the monitor checks from other servers + // don't affect the TopologyDescription's clusterTime value between commands. + if options.load_balanced != Some(true) { + options.direct_connection = Some(true); + + // Since we need to run an insert below, ensure the single host is a primary + // if we're connected to a replica set. + if let Some(primary) = get_primary().await { + options.hosts = vec![primary]; + } else { + options.hosts.drain(1..); + } + } + + let mut event_stream = buffer.stream(); + + let client = Client::with_options(options).unwrap(); + + // Wait for initial monitor check to complete and discover the server. + event_stream + .next_match(Duration::from_secs(5), |event| match event { + Event::Sdam(SdamEvent::ServerDescriptionChanged(e)) => { + !e.previous_description.server_type().is_available() + && e.new_description.server_type().is_available() + } + _ => false, + }) + .await + .expect("server should be discovered"); + + // LoadBalanced topologies don't have monitors, so the client needs to get a clusterTime from + // a command invocation. + client + .database("admin") + .run_command(doc! { "ping": 1 }) + .await + .unwrap(); + + cluster_time_test("ping", &client, &buffer, |client| async move { + client + .database(function_name!()) + .run_command(doc! { "ping": 1 }) + .await + }) + .await; + + cluster_time_test("aggregate", &client, &buffer, |client| async move { + client + .database(function_name!()) + .collection::(function_name!()) + .aggregate(vec![doc! { "$match": { "x": 1 } }]) + .await + }) + .await; + + cluster_time_test("find", &client, &buffer, |client| async move { + client + .database(function_name!()) + .collection::(function_name!()) + .find(doc! {}) + .await + }) + .await; + + cluster_time_test("insert", &client, &buffer, |client| async move { + client + .database(function_name!()) + .collection::(function_name!()) + .insert_one(doc! {}) + .await + }) + .await; +} + +/// Prose test 3 from sessions spec. +#[tokio::test] +#[function_name::named] +async fn session_usage() { + if topology_is_standalone().await { + return; + } + + async fn session_usage_test(command_name: &str, operation: F) + where + F: Fn(EventClient) -> G, + G: Future, + { + let client = Client::for_test().monitor_events().await; + operation(client.clone()).await; + let (command_started, _) = client.events.get_successful_command_execution(command_name); + assert!( + command_started.command.get("lsid").is_some(), + "implicit session not passed to {}", + command_name + ); + } + + for_each_op!(function_name!(), session_usage_test) +} + +/// Prose test 7 from sessions spec. +#[tokio::test] +#[function_name::named] +async fn implicit_session_returned_after_immediate_exhaust() { + if topology_is_standalone().await { + return; + } + + let client = Client::for_test().monitor_events().await; + + let coll = client + .init_db_and_coll(function_name!(), function_name!()) + .await; + coll.insert_many(vec![doc! {}, doc! {}]) + .await + .expect("insert should succeed"); + + // wait for sessions to be returned to the pool and clear them out. + tokio::time::sleep(Duration::from_millis(250)).await; + client.clear_session_pool().await; + + let mut cursor = coll.find(doc! {}).await.expect("find should succeed"); + assert!(matches!(cursor.next().await, Some(Ok(_)))); + + let (find_started, _) = client.events.get_successful_command_execution("find"); + let session_id = find_started + .command + .get("lsid") + .expect("find should use implicit session") + .as_document() + .expect("session id should be a document"); + + tokio::time::sleep(Duration::from_millis(250)).await; + assert!( + client.is_session_checked_in(session_id).await, + "session not checked back in" + ); + + assert!(matches!(cursor.next().await, Some(Ok(_)))); +} + +/// Prose test 8 from sessions spec. +#[tokio::test] +#[function_name::named] +async fn implicit_session_returned_after_exhaust_by_get_more() { + if topology_is_standalone().await { + return; + } + + let client = Client::for_test().monitor_events().await; + let coll = client + .init_db_and_coll(function_name!(), function_name!()) + .await; + for _ in 0..5 { + coll.insert_one(doc! {}) + .await + .expect("insert should succeed"); + } + + // wait for sessions to be returned to the pool and clear them out. + tokio::time::sleep(Duration::from_millis(250)).await; + client.clear_session_pool().await; + + let mut cursor = coll + .find(doc! {}) + .batch_size(3) + .await + .expect("find should succeed"); + + for _ in 0..4 { + assert!(matches!(cursor.next().await, Some(Ok(_)))); + } + + let (find_started, _) = client.events.get_successful_command_execution("find"); + + let session_id = find_started + .command + .get("lsid") + .expect("find should use implicit session") + .as_document() + .expect("session id should be a document"); + + tokio::time::sleep(Duration::from_millis(250)).await; + assert!( + client.is_session_checked_in(session_id).await, + "session not checked back in" + ); + + assert!(matches!(cursor.next().await, Some(Ok(_)))); +} + +/// Prose test 10 from sessions spec. +#[tokio::test] +#[function_name::named] +async fn find_and_getmore_share_session() { + if topology_is_standalone().await { + log_uncaptured( + "skipping find_and_getmore_share_session due to unsupported topology: Standalone", + ); + return; + } + + let client = Client::for_test().monitor_events().await; + + let coll = client + .init_db_and_coll(function_name!(), function_name!()) + .await; + + coll.insert_many(vec![doc! {}; 3]) + .write_concern(WriteConcern::majority()) + .await + .unwrap(); + + let read_preferences: Vec = vec![ + ReadPreference::Primary, + ReadPreference::PrimaryPreferred { + options: Default::default(), + }, + ReadPreference::Secondary { + options: Default::default(), + }, + ReadPreference::SecondaryPreferred { + options: Default::default(), + }, + ReadPreference::Nearest { + options: Default::default(), + }, + ]; + + async fn run_test( + client: &EventClient, + coll: &Collection, + read_preference: ReadPreference, + ) { + client.events.clone().clear_cached_events(); + + let options = FindOptions::builder() + .batch_size(2) + .selection_criteria(SelectionCriteria::ReadPreference(read_preference.clone())) + .read_concern(ReadConcern::local()) + .build(); + + // Loop until data is found to avoid racing with replication. + let mut cursor; + loop { + cursor = coll + .find(doc! {}) + .with_options(options.clone()) + .await + .expect("find should succeed"); + if cursor.has_next() { + break; + } + } + + for _ in 0..3 { + cursor + .next() + .await + .unwrap_or_else(|| { + panic!( + "should get result with read preference {:?}", + read_preference + ) + }) + .unwrap_or_else(|e| { + panic!( + "result should not be error with read preference {:?}, but got {:?}", + read_preference, e + ) + }); + } + + let (find_started, _) = client.events.get_successful_command_execution("find"); + let session_id = find_started + .command + .get("lsid") + .expect("find should use implicit session"); + assert!(session_id != &Bson::Null); + + let (command_started, _) = client.events.get_successful_command_execution("getMore"); + let getmore_session_id = command_started + .command + .get("lsid") + .expect("count documents should use implicit session"); + assert_eq!(getmore_session_id, session_id); + } + + let topology_description = client.topology_description(); + for (addr, server) in topology_description.servers { + if !server.server_type.is_data_bearing() { + continue; + } + + let a = addr.clone(); + let rp = Arc::new(move |si: &ServerInfo| si.address() == &a); + let options = CountOptions::builder() + .selection_criteria(SelectionCriteria::Predicate(rp)) + .read_concern(ReadConcern::local()) + .build(); + + while coll + .count_documents(doc! {}) + .with_options(options.clone()) + .await + .unwrap() + != 3 + {} + } + + for read_pref in read_preferences { + run_test(&client, &coll, read_pref).await; + } +} diff --git a/src/client/session/test/causal_consistency.rs b/src/client/session/test/causal_consistency.rs index 180cb5f77..6778a2cf0 100644 --- a/src/client/session/test/causal_consistency.rs +++ b/src/client/session/test/causal_consistency.rs @@ -1,14 +1,13 @@ -use bson::{doc, Document}; +use crate::bson::{doc, Document}; use futures::{future::BoxFuture, FutureExt}; -use tokio::sync::RwLockReadGuard; use crate::{ - client::options::SessionOptions, coll::options::CollectionOptions, error::Result, event::command::CommandEvent, options::ReadConcern, - test::{log_uncaptured, EventClient, LOCK}, + test::{log_uncaptured, topology_is_standalone}, + Client, ClientSession, Collection, }; @@ -47,140 +46,107 @@ fn all_session_ops() -> impl Iterator { let mut ops = vec![]; ops.push(op!("insert", false, |coll, session| { - coll.insert_one_with_session(doc! { "x": 1 }, None, session) + coll.insert_one(doc! { "x": 1 }).session(session) })); ops.push(op!("insert", false, |coll, session| { - coll.insert_many_with_session(vec![doc! { "x": 1 }], None, session) + coll.insert_many(vec![doc! { "x": 1 }]).session(session) })); ops.push(op!("find", true, |coll, session| coll - .find_one_with_session(doc! { "x": 1 }, None, session))); + .find_one(doc! { "x": 1 }) + .session(session))); - ops.push(op!("find", true, |coll, session| coll.find_with_session( - doc! { "x": 1 }, - None, - session - ))); + ops.push(op!("find", true, |coll, session| coll + .find(doc! { "x": 1 }) + .session(session))); ops.push(op!("update", false, |coll, s| coll - .update_one_with_session( - doc! { "x": 1 }, - doc! { "$inc": { "x": 1 } }, - None, - s, - ))); + .update_one(doc! { "x": 1 }, doc! { "$inc": { "x": 1 } },) + .session(s))); ops.push(op!("update", false, |coll, s| coll - .update_many_with_session( - doc! { "x": 1 }, - doc! { "$inc": { "x": 1 } }, - None, - s, - ))); + .update_many(doc! { "x": 1 }, doc! { "$inc": { "x": 1 } },) + .session(s))); ops.push(op!("update", false, |coll, s| coll - .replace_one_with_session( - doc! { "x": 1 }, - doc! { "x": 2 }, - None, - s, - ))); + .replace_one(doc! { "x": 1 }, doc! { "x": 2 },) + .session(s))); ops.push(op!("delete", false, |coll, s| coll - .delete_one_with_session(doc! { "x": 1 }, None, s,))); + .delete_one(doc! { "x": 1 }) + .session(s))); ops.push(op!("delete", false, |coll, s| coll - .delete_many_with_session(doc! { "x": 1 }, None, s,))); + .delete_many(doc! { "x": 1 }) + .session(s))); ops.push(op!("findAndModify", false, |coll, s| coll - .find_one_and_update_with_session( - doc! { "x": 1 }, - doc! { "$inc": { "x": 1 } }, - None, - s, - ))); + .find_one_and_update(doc! { "x": 1 }, doc! { "$inc": { "x": 1 } },) + .session(s))); ops.push(op!("findAndModify", false, |coll, s| coll - .find_one_and_replace_with_session( - doc! { "x": 1 }, - doc! { "x": 1 }, - None, - s, - ))); + .find_one_and_replace(doc! { "x": 1 }, doc! { "x": 1 },) + .session(s))); ops.push(op!("findAndModify", false, |coll, s| coll - .find_one_and_delete_with_session(doc! { "x": 1 }, None, s,))); + .find_one_and_delete(doc! { "x": 1 }) + .session(s))); ops.push(op!("aggregate", true, |coll, s| coll - .count_documents_with_session(doc! { "x": 1 }, None, s,))); + .count_documents(doc! { "x": 1 }) + .session(s))); ops.push(op!("aggregate", true, |coll, s| coll - .aggregate_with_session( - vec![doc! { "$match": { "x": 1 } }], - None, - s, - ))); + .aggregate(vec![doc! { "$match": { "x": 1 } }]) + .session(s))); ops.push(op!("aggregate", false, |coll, s| coll - .aggregate_with_session( - vec![ - doc! { "$match": { "x": 1 } }, - doc! { "$out": "some_other_coll" }, - ], - None, - s, - ))); - - ops.push(op!("distinct", true, |coll, s| coll.distinct_with_session( - "x", - doc! {}, - None, - s, - ))); + .aggregate(vec![ + doc! { "$match": { "x": 1 } }, + doc! { "$out": "some_other_coll" }, + ]) + .session(s))); + + ops.push(op!("distinct", true, |coll, s| coll + .distinct("x", doc! {},) + .session(s))); ops.into_iter() } /// Test 1 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn new_session_operation_time_null() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping new_session_operation_time_null due to unsupported topology: standalone", ); return; } - let session = client.start_session(None).await.unwrap(); + let client = Client::for_test().monitor_events().await; + let session = client.start_session().await.unwrap(); assert!(session.operation_time().is_none()); } /// Test 2 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn first_read_no_after_cluser_time() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { +#[tokio::test] +async fn first_read_no_after_cluster_time() { + if topology_is_standalone().await { log_uncaptured( "skipping first_read_no_after_cluser_time due to unsupported topology: standalone", ); return; } + let client = Client::for_test().monitor_events().await; for op in all_session_ops().filter(|o| o.is_read) { + client.events.clone().clear_cached_events(); + let mut session = client - .start_session(Some( - SessionOptions::builder().causal_consistency(true).build(), - )) + .start_session() + .causal_consistency(true) .await .unwrap(); assert!(session.operation_time().is_none()); @@ -193,7 +159,8 @@ async fn first_read_no_after_cluser_time() { ) .await .unwrap_or_else(|e| panic!("{} failed: {}", name, e)); - let (started, _) = client.get_successful_command_execution(name); + + let (started, _) = client.events.get_successful_command_execution(name); // assert that no read concern was set. started.command.get_document("readConcern").unwrap_err(); @@ -201,23 +168,20 @@ async fn first_read_no_after_cluser_time() { } /// Test 3 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn first_op_update_op_time() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured("skipping first_op_update_op_time due to unsupported topology: standalone"); return; } + let client = Client::for_test().monitor_events().await; for op in all_session_ops() { + client.events.clone().clear_cached_events(); + let mut session = client - .start_session(Some( - SessionOptions::builder().causal_consistency(true).build(), - )) + .start_session() + .causal_consistency(true) .await .unwrap(); assert!(session.operation_time().is_none()); @@ -232,6 +196,7 @@ async fn first_op_update_op_time() { .unwrap(); let event = client + .events .get_command_events(&[name]) .into_iter() .find(|e| matches!(e, CommandEvent::Succeeded(_) | CommandEvent::Failed(_))) @@ -251,34 +216,30 @@ async fn first_op_update_op_time() { } /// Test 4 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn read_includes_after_cluster_time() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping read_includes_after_cluster_time due to unsupported topology: standalone", ); return; } + let client = Client::for_test().monitor_events().await; + let coll = client .create_fresh_collection("causal_consistency_4", "causal_consistency_4", None) .await; for op in all_session_ops().filter(|o| o.is_read) { let command_name = op.name; - let mut session = client.start_session(None).await.unwrap(); - coll.find_one_with_session(None, None, &mut session) - .await - .unwrap(); + let mut session = client.start_session().await.unwrap(); + coll.find_one(doc! {}).session(&mut session).await.unwrap(); let op_time = session.operation_time().unwrap(); op.execute(coll.clone(), &mut session).await.unwrap(); let command_started = client + .events .get_command_started_events(&[command_name]) .pop() .unwrap(); @@ -296,14 +257,9 @@ async fn read_includes_after_cluster_time() { } /// Test 5 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn find_after_write_includes_after_cluster_time() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping find_after_write_includes_after_cluster_time due to unsupported topology: \ standalone", @@ -311,20 +267,26 @@ async fn find_after_write_includes_after_cluster_time() { return; } + let client = Client::for_test().monitor_events().await; let coll = client .create_fresh_collection("causal_consistency_5", "causal_consistency_5", None) .await; for op in all_session_ops().filter(|o| !o.is_read) { - let session_options = SessionOptions::builder().causal_consistency(true).build(); - let mut session = client.start_session(Some(session_options)).await.unwrap(); - op.execute(coll.clone(), &mut session).await.unwrap(); - let op_time = session.operation_time().unwrap(); - coll.find_one_with_session(None, None, &mut session) + let mut session = client + .start_session() + .causal_consistency(true) .await .unwrap(); + op.execute(coll.clone(), &mut session).await.unwrap(); + let op_time = session.operation_time().unwrap(); + coll.find_one(doc! {}).session(&mut session).await.unwrap(); - let command_started = client.get_command_started_events(&["find"]).pop().unwrap(); + let command_started = client + .events + .get_command_started_events(&["find"]) + .pop() + .unwrap(); assert_eq!( command_started .command @@ -338,14 +300,9 @@ async fn find_after_write_includes_after_cluster_time() { } /// Test 6 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn not_causally_consistent_omits_after_cluster_time() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping not_causally_consistent_omits_after_cluster_time due to unsupported \ topology: standalone", @@ -353,6 +310,7 @@ async fn not_causally_consistent_omits_after_cluster_time() { return; } + let client = Client::for_test().monitor_events().await; let coll = client .create_fresh_collection("causal_consistency_6", "causal_consistency_6", None) .await; @@ -360,11 +318,15 @@ async fn not_causally_consistent_omits_after_cluster_time() { for op in all_session_ops().filter(|o| o.is_read) { let command_name = op.name; - let session_options = SessionOptions::builder().causal_consistency(false).build(); - let mut session = client.start_session(Some(session_options)).await.unwrap(); + let mut session = client + .start_session() + .causal_consistency(false) + .await + .unwrap(); op.execute(coll.clone(), &mut session).await.unwrap(); let command_started = client + .events .get_command_started_events(&[command_name]) .pop() .unwrap(); @@ -376,18 +338,14 @@ async fn not_causally_consistent_omits_after_cluster_time() { } /// Test 7 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn omit_after_cluster_time_standalone() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if !client.is_standalone() { + if !topology_is_standalone().await { log_uncaptured("skipping omit_after_cluster_time_standalone due to unsupported topology"); return; } + let client = Client::for_test().monitor_events().await; let coll = client .create_fresh_collection("causal_consistency_7", "causal_consistency_7", None) .await; @@ -395,11 +353,15 @@ async fn omit_after_cluster_time_standalone() { for op in all_session_ops().filter(|o| o.is_read) { let command_name = op.name; - let session_options = SessionOptions::builder().causal_consistency(true).build(); - let mut session = client.start_session(Some(session_options)).await.unwrap(); + let mut session = client + .start_session() + .causal_consistency(true) + .await + .unwrap(); op.execute(coll.clone(), &mut session).await.unwrap(); let command_started = client + .events .get_command_started_events(&[command_name]) .pop() .unwrap(); @@ -411,20 +373,16 @@ async fn omit_after_cluster_time_standalone() { } /// Test 8 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn omit_default_read_concern_level() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping omit_default_read_concern_level due to unsupported topology: standalone", ); return; } + let client = Client::for_test().monitor_events().await; let coll = client .create_fresh_collection("causal_consistency_8", "causal_consistency_8", None) .await; @@ -432,15 +390,17 @@ async fn omit_default_read_concern_level() { for op in all_session_ops().filter(|o| o.is_read) { let command_name = op.name; - let session_options = SessionOptions::builder().causal_consistency(true).build(); - let mut session = client.start_session(Some(session_options)).await.unwrap(); - coll.find_one_with_session(None, None, &mut session) + let mut session = client + .start_session() + .causal_consistency(true) .await .unwrap(); + coll.find_one(doc! {}).session(&mut session).await.unwrap(); let op_time = session.operation_time().unwrap(); op.execute(coll.clone(), &mut session).await.unwrap(); let command_started = client + .events .get_command_started_events(&[command_name]) .pop() .unwrap(); @@ -452,13 +412,9 @@ async fn omit_default_read_concern_level() { } /// Test 9 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn test_causal_consistency_read_concern_merge() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured( "skipping test_causal_consistency_read_concern_merge due to unsupported topology: \ standalone", @@ -466,8 +422,12 @@ async fn test_causal_consistency_read_concern_merge() { return; } - let session_options = SessionOptions::builder().causal_consistency(true).build(); - let mut session = client.start_session(Some(session_options)).await.unwrap(); + let client = Client::for_test().monitor_events().await; + let mut session = client + .start_session() + .causal_consistency(true) + .await + .unwrap(); let coll_options = CollectionOptions::builder() .read_concern(ReadConcern::majority()) @@ -481,13 +441,12 @@ async fn test_causal_consistency_read_concern_merge() { for op in all_session_ops().filter(|o| o.is_read) { let command_name = op.name; - coll.find_one_with_session(None, None, &mut session) - .await - .unwrap(); + coll.find_one(doc! {}).session(&mut session).await.unwrap(); let op_time = session.operation_time().unwrap(); op.execute(coll.clone(), &mut session).await.unwrap(); let command_started = client + .events .get_command_started_events(&[command_name]) .pop() .unwrap(); @@ -502,45 +461,39 @@ async fn test_causal_consistency_read_concern_merge() { } /// Test 11 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn omit_cluster_time_standalone() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if !client.is_standalone() { + if !topology_is_standalone().await { log_uncaptured("skipping omit_cluster_time_standalone due to unsupported topology"); return; } + let client = Client::for_test().monitor_events().await; let coll = client .database("causal_consistency_11") .collection::("causal_consistency_11"); - coll.find_one(None, None).await.unwrap(); + coll.find_one(doc! {}).await.unwrap(); - let (started, _) = client.get_successful_command_execution("find"); + let (started, _) = client.events.get_successful_command_execution("find"); started.command.get_document("$clusterTime").unwrap_err(); } /// Test 12 from the causal consistency specification. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn cluster_time_sent_in_commands() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured("skipping cluster_time_sent_in_commands due to unsupported topology"); return; } + let client = Client::for_test().monitor_events().await; let coll = client - .database("causal_consistency_12") - .collection::("causal_consistency_12"); + .create_fresh_collection("causal_consistency_12", "causal_consistency_12", None) + .await; - coll.find_one(None, None).await.unwrap(); + coll.find_one(doc! {}).await.unwrap(); - let (started, _) = client.get_successful_command_execution("find"); + let (started, _) = client.events.get_successful_command_execution("find"); started.command.get_document("$clusterTime").unwrap(); } diff --git a/src/client/session/test/mod.rs b/src/client/session/test/mod.rs deleted file mode 100644 index 32da9b1a4..000000000 --- a/src/client/session/test/mod.rs +++ /dev/null @@ -1,640 +0,0 @@ -mod causal_consistency; - -use std::{future::Future, sync::Arc, time::Duration}; - -use bson::Document; -use futures::stream::StreamExt; -use tokio::sync::RwLockReadGuard; - -use crate::{ - bson::{doc, Bson}, - coll::options::{CountOptions, InsertManyOptions}, - error::Result, - options::{Acknowledgment, FindOptions, ReadConcern, ReadPreference, WriteConcern}, - runtime, - sdam::ServerInfo, - selection_criteria::SelectionCriteria, - test::{ - log_uncaptured, - Event, - EventClient, - EventHandler, - SdamEvent, - TestClient, - CLIENT_OPTIONS, - LOCK, - }, - Client, - Collection, -}; - -/// Macro defining a closure that returns a future populated by an operation on the -/// provided client identifier. -macro_rules! client_op { - ($client:ident, $body:expr) => { - |$client| async move { - $body.await.unwrap(); - } - }; -} - -/// Macro defining a closure that returns a future populated by an operation on the -/// provided database identifier. -macro_rules! db_op { - ($test_name:expr, $db:ident, $body:expr) => { - |client| async move { - let $db = client.database($test_name); - $body.await.unwrap(); - } - }; -} - -/// Macro defining a closure that returns a future populated by an operation on the -/// provided collection identifier. -macro_rules! collection_op { - ($test_name:expr, $coll:ident, $body:expr) => { - |client| async move { - let $coll = client - .database($test_name) - .collection::($test_name); - $body.await.unwrap(); - } - }; -} - -/// Macro that runs the provided function with each operation that uses a session. -macro_rules! for_each_op { - ($test_name:expr, $test_func:ident) => {{ - // collection operations - $test_func( - "insert", - collection_op!($test_name, coll, coll.insert_one(doc! { "x": 1 }, None)), - ) - .await; - $test_func( - "insert", - collection_op!( - $test_name, - coll, - coll.insert_many(vec![doc! { "x": 1 }], None) - ), - ) - .await; - $test_func( - "update", - collection_op!( - $test_name, - coll, - coll.replace_one(doc! { "x": 1 }, doc! { "x": 2 }, None) - ), - ) - .await; - $test_func( - "update", - collection_op!( - $test_name, - coll, - coll.update_one(doc! {}, doc! { "$inc": {"x": 5 } }, None) - ), - ) - .await; - $test_func( - "update", - collection_op!( - $test_name, - coll, - coll.update_many(doc! {}, doc! { "$inc": {"x": 5 } }, None) - ), - ) - .await; - $test_func( - "delete", - collection_op!($test_name, coll, coll.delete_one(doc! { "x": 1 }, None)), - ) - .await; - $test_func( - "delete", - collection_op!($test_name, coll, coll.delete_many(doc! { "x": 1 }, None)), - ) - .await; - $test_func( - "findAndModify", - collection_op!( - $test_name, - coll, - coll.find_one_and_delete(doc! { "x": 1 }, None) - ), - ) - .await; - $test_func( - "findAndModify", - collection_op!( - $test_name, - coll, - coll.find_one_and_update(doc! {}, doc! { "$inc": { "x": 1 } }, None) - ), - ) - .await; - $test_func( - "findAndModify", - collection_op!( - $test_name, - coll, - coll.find_one_and_replace(doc! {}, doc! {"x": 1}, None) - ), - ) - .await; - $test_func( - "aggregate", - collection_op!( - $test_name, - coll, - coll.aggregate(vec![doc! { "$match": { "x": 1 } }], None) - ), - ) - .await; - $test_func( - "find", - collection_op!($test_name, coll, coll.find(doc! { "x": 1 }, None)), - ) - .await; - $test_func( - "find", - collection_op!($test_name, coll, coll.find_one(doc! { "x": 1 }, None)), - ) - .await; - $test_func( - "distinct", - collection_op!($test_name, coll, coll.distinct("x", None, None)), - ) - .await; - $test_func( - "aggregate", - collection_op!($test_name, coll, coll.count_documents(None, None)), - ) - .await; - $test_func("drop", collection_op!($test_name, coll, coll.drop(None))).await; - - // db operations - $test_func( - "listCollections", - db_op!($test_name, db, db.list_collections(None, None)), - ) - .await; - $test_func( - "ping", - db_op!($test_name, db, db.run_command(doc! { "ping": 1 }, None)), - ) - .await; - $test_func( - "create", - db_op!($test_name, db, db.create_collection("sessionopcoll", None)), - ) - .await; - $test_func("dropDatabase", db_op!($test_name, db, db.drop(None))).await; - - // client operations - $test_func( - "listDatabases", - client_op!(client, client.list_databases(None, None)), - ) - .await; - $test_func( - "listDatabases", - client_op!(client, client.list_database_names(None, None)), - ) - .await; - }}; -} - -/// Prose test 1 from sessions spec. -/// This test also satisifies the `endSession` testing requirement of prose test 5. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn pool_is_lifo() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - // Wait for the implicit sessions created in TestClient::new to be returned to the pool. - runtime::delay_for(Duration::from_millis(500)).await; - - if client.is_standalone() { - return; - } - - let a = client.start_session(None).await.unwrap(); - let b = client.start_session(None).await.unwrap(); - - let a_id = a.id().clone(); - let b_id = b.id().clone(); - - // End both sessions, waiting after each to ensure the background task got scheduled - // in the Drop impls. - drop(a); - runtime::delay_for(Duration::from_millis(250)).await; - - drop(b); - runtime::delay_for(Duration::from_millis(250)).await; - - let s1 = client.start_session(None).await.unwrap(); - assert_eq!(s1.id(), &b_id); - - let s2 = client.start_session(None).await.unwrap(); - assert_eq!(s2.id(), &a_id); -} - -/// Prose test 2 from sessions spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn cluster_time_in_commands() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let test_client = TestClient::new().await; - if test_client.is_standalone() { - log_uncaptured("skipping cluster_time_in_commands test due to standalone topology"); - return; - } - - async fn cluster_time_test( - command_name: &str, - client: &Client, - event_handler: &EventHandler, - operation: F, - ) where - F: Fn(Client) -> G, - G: Future>, - { - let mut subscriber = event_handler.subscribe(); - - operation(client.clone()) - .await - .expect("operation should succeed"); - - operation(client.clone()) - .await - .expect("operation should succeed"); - - let (first_command_started, first_command_succeeded) = subscriber - .wait_for_successful_command_execution(Duration::from_secs(5), command_name) - .await - .unwrap_or_else(|| { - panic!( - "did not see command started and succeeded events for {}", - command_name - ) - }); - - assert!(first_command_started.command.get("$clusterTime").is_some()); - let response_cluster_time = first_command_succeeded - .reply - .get("$clusterTime") - .expect("should get cluster time from command response"); - - let (second_command_started, _) = subscriber - .wait_for_successful_command_execution(Duration::from_secs(5), command_name) - .await - .unwrap_or_else(|| { - panic!( - "did not see command started and succeeded events for {}", - command_name - ) - }); - - assert_eq!( - response_cluster_time, - second_command_started - .command - .get("$clusterTime") - .expect("second command should contain cluster time"), - "cluster time not equal for {}", - command_name - ); - } - - let handler = Arc::new(EventHandler::new()); - let mut options = CLIENT_OPTIONS.get().await.clone(); - options.heartbeat_freq = Some(Duration::from_secs(1000)); - options.command_event_handler = Some(handler.clone()); - options.sdam_event_handler = Some(handler.clone()); - - // Ensure we only connect to one server so the monitor checks from other servers - // don't affect the TopologyDescription's clusterTime value between commands. - if options.load_balanced != Some(true) { - options.direct_connection = Some(true); - - // Since we need to run an insert below, ensure the single host is a primary - // if we're connected to a replica set. - if let Some(primary) = test_client.primary() { - options.hosts = vec![primary]; - } else { - options.hosts.drain(1..); - } - } - - let mut subscriber = handler.subscribe(); - - let client = Client::with_options(options).unwrap(); - - // Wait for initial monitor check to complete and discover the server. - subscriber - .wait_for_event(Duration::from_secs(5), |event| match event { - Event::Sdam(SdamEvent::ServerDescriptionChanged(e)) => { - !e.previous_description.server_type().is_available() - && e.new_description.server_type().is_available() - } - _ => false, - }) - .await - .expect("server should be discovered"); - - // LoadBalanced topologies don't have monitors, so the client needs to get a clusterTime from - // a command invocation. - client - .database("admin") - .run_command(doc! { "ping": 1 }, None) - .await - .unwrap(); - - cluster_time_test("ping", &client, handler.as_ref(), |client| async move { - client - .database(function_name!()) - .run_command(doc! { "ping": 1 }, None) - .await - }) - .await; - - cluster_time_test( - "aggregate", - &client, - handler.as_ref(), - |client| async move { - client - .database(function_name!()) - .collection::(function_name!()) - .aggregate(vec![doc! { "$match": { "x": 1 } }], None) - .await - }, - ) - .await; - - cluster_time_test("find", &client, handler.as_ref(), |client| async move { - client - .database(function_name!()) - .collection::(function_name!()) - .find(doc! {}, None) - .await - }) - .await; - - cluster_time_test("insert", &client, handler.as_ref(), |client| async move { - client - .database(function_name!()) - .collection::(function_name!()) - .insert_one(doc! {}, None) - .await - }) - .await; -} - -/// Prose test 3 from sessions spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn session_usage() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - if client.is_standalone() { - return; - } - - async fn session_usage_test(command_name: &str, operation: F) - where - F: Fn(EventClient) -> G, - G: Future, - { - let client = EventClient::new().await; - operation(client.clone()).await; - let (command_started, _) = client.get_successful_command_execution(command_name); - assert!( - command_started.command.get("lsid").is_some(), - "implicit session not passed to {}", - command_name - ); - } - - for_each_op!(function_name!(), session_usage_test) -} - -/// Prose test 7 from sessions spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn implicit_session_returned_after_immediate_exhaust() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if client.is_standalone() { - return; - } - - let coll = client - .init_db_and_coll(function_name!(), function_name!()) - .await; - coll.insert_many(vec![doc! {}, doc! {}], None) - .await - .expect("insert should succeed"); - - // wait for sessions to be returned to the pool and clear them out. - runtime::delay_for(Duration::from_millis(250)).await; - client.clear_session_pool().await; - - let mut cursor = coll.find(doc! {}, None).await.expect("find should succeed"); - assert!(matches!(cursor.next().await, Some(Ok(_)))); - - let (find_started, _) = client.get_successful_command_execution("find"); - let session_id = find_started - .command - .get("lsid") - .expect("find should use implicit session") - .as_document() - .expect("session id should be a document"); - - runtime::delay_for(Duration::from_millis(250)).await; - assert!( - client.is_session_checked_in(session_id).await, - "session not checked back in" - ); - - assert!(matches!(cursor.next().await, Some(Ok(_)))); -} - -/// Prose test 8 from sessions spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn implicit_session_returned_after_exhaust_by_get_more() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if client.is_standalone() { - return; - } - - let coll = client - .init_db_and_coll(function_name!(), function_name!()) - .await; - for _ in 0..5 { - coll.insert_one(doc! {}, None) - .await - .expect("insert should succeed"); - } - - // wait for sessions to be returned to the pool and clear them out. - runtime::delay_for(Duration::from_millis(250)).await; - client.clear_session_pool().await; - - let options = FindOptions::builder().batch_size(3).build(); - let mut cursor = coll - .find(doc! {}, options) - .await - .expect("find should succeed"); - - for _ in 0..4 { - assert!(matches!(cursor.next().await, Some(Ok(_)))); - } - - let (find_started, _) = client.get_successful_command_execution("find"); - let session_id = find_started - .command - .get("lsid") - .expect("find should use implicit session") - .as_document() - .expect("session id should be a document"); - - runtime::delay_for(Duration::from_millis(250)).await; - assert!( - client.is_session_checked_in(session_id).await, - "session not checked back in" - ); - - assert!(matches!(cursor.next().await, Some(Ok(_)))); -} - -/// Prose test 10 from sessions spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn find_and_getmore_share_session() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - if client.is_standalone() { - log_uncaptured( - "skipping find_and_getmore_share_session due to unsupported topology: Standalone", - ); - return; - } - - let coll = client - .init_db_and_coll(function_name!(), function_name!()) - .await; - - let options = InsertManyOptions::builder() - .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build()) - .build(); - coll.insert_many(vec![doc! {}; 3], options).await.unwrap(); - - let read_preferences: Vec = vec![ - ReadPreference::Primary, - ReadPreference::PrimaryPreferred { - options: Default::default(), - }, - ReadPreference::Secondary { - options: Default::default(), - }, - ReadPreference::SecondaryPreferred { - options: Default::default(), - }, - ReadPreference::Nearest { - options: Default::default(), - }, - ]; - - async fn run_test( - client: &EventClient, - coll: &Collection, - read_preference: ReadPreference, - ) { - let options = FindOptions::builder() - .batch_size(2) - .selection_criteria(SelectionCriteria::ReadPreference(read_preference.clone())) - .read_concern(ReadConcern::local()) - .build(); - - // Loop until data is found to avoid racing with replication. - let mut cursor; - loop { - cursor = coll - .find(doc! {}, options.clone()) - .await - .expect("find should succeed"); - if cursor.has_next() { - break; - } - } - - for _ in 0..3 { - cursor - .next() - .await - .unwrap_or_else(|| { - panic!( - "should get result with read preference {:?}", - read_preference - ) - }) - .unwrap_or_else(|e| { - panic!( - "result should not be error with read preference {:?}, but got {:?}", - read_preference, e - ) - }); - } - - let (find_started, _) = client.get_successful_command_execution("find"); - let session_id = find_started - .command - .get("lsid") - .expect("find should use implicit session"); - assert!(session_id != &Bson::Null); - - let (command_started, _) = client.get_successful_command_execution("getMore"); - let getmore_session_id = command_started - .command - .get("lsid") - .expect("count documents should use implicit session"); - assert_eq!(getmore_session_id, session_id); - } - - let topology_description = client.topology_description(); - for (addr, server) in topology_description.servers { - if !server.server_type.is_data_bearing() { - continue; - } - - let a = addr.clone(); - let rp = Arc::new(move |si: &ServerInfo| si.address() == &a); - let options = CountOptions::builder() - .selection_criteria(SelectionCriteria::Predicate(rp)) - .read_concern(ReadConcern::local()) - .build(); - - while coll.count_documents(None, options.clone()).await.unwrap() != 3 {} - } - - for read_pref in read_preferences { - run_test(&client, &coll, read_pref).await; - } -} diff --git a/src/cmap.rs b/src/cmap.rs new file mode 100644 index 000000000..b0a02f8ed --- /dev/null +++ b/src/cmap.rs @@ -0,0 +1,191 @@ +#[cfg(test)] +pub(crate) mod test; + +pub(crate) mod conn; +mod connection_requester; +pub(crate) mod establish; +mod manager; +pub(crate) mod options; +mod status; +mod worker; + +use std::time::Instant; + +use derive_where::derive_where; + +pub use self::conn::ConnectionInfo; +use self::{ + conn::pooled::PooledConnection, + connection_requester::ConnectionRequestResult, + establish::ConnectionEstablisher, + options::ConnectionPoolOptions, +}; +pub(crate) use self::{ + conn::{Command, Connection, RawCommandResponse, StreamDescription}, + status::PoolGenerationSubscriber, + worker::PoolGeneration, +}; +use crate::{ + bson::oid::ObjectId, + error::{Error, Result}, + event::cmap::{ + CmapEvent, + CmapEventEmitter, + ConnectionCheckoutFailedEvent, + ConnectionCheckoutFailedReason, + ConnectionCheckoutStartedEvent, + PoolCreatedEvent, + }, + options::ServerAddress, + runtime::AcknowledgmentReceiver, + sdam::{BroadcastMessage, TopologyUpdater}, +}; +use connection_requester::ConnectionRequester; +use manager::PoolManager; +use worker::ConnectionPoolWorker; + +#[cfg(test)] +use crate::runtime::WorkerHandle; + +pub(crate) const DEFAULT_MAX_POOL_SIZE: u32 = 10; + +/// A pool of connections implementing the CMAP spec. +/// This type is actually a handle to task that manages the connections and is cheap to clone and +/// pass around. +#[derive(Clone)] +#[derive_where(Debug)] +pub(crate) struct ConnectionPool { + address: ServerAddress, + manager: PoolManager, + connection_requester: ConnectionRequester, + generation_subscriber: PoolGenerationSubscriber, + + #[derive_where(skip)] + event_emitter: CmapEventEmitter, +} + +impl ConnectionPool { + pub(crate) fn new( + address: ServerAddress, + connection_establisher: ConnectionEstablisher, + server_updater: TopologyUpdater, + topology_id: ObjectId, + options: Option, + ) -> Self { + let event_handler = options + .as_ref() + .and_then(|opts| opts.cmap_event_handler.clone()); + + let event_emitter = CmapEventEmitter::new(event_handler, topology_id); + + let (manager, connection_requester, generation_subscriber) = ConnectionPoolWorker::start( + address.clone(), + connection_establisher, + server_updater, + event_emitter.clone(), + options.clone(), + ); + + event_emitter.emit_event(|| { + CmapEvent::PoolCreated(PoolCreatedEvent { + address: address.clone(), + options: options.map(|o| o.to_event_options()), + }) + }); + + Self { + address, + manager, + connection_requester, + generation_subscriber, + event_emitter, + } + } + + #[cfg(test)] + pub(crate) fn new_mocked(address: ServerAddress) -> Self { + let (manager, _) = manager::channel(); + let handle = WorkerHandle::new_mocked(); + let (connection_requester, _) = connection_requester::channel(handle); + let (_, generation_subscriber) = status::channel(PoolGeneration::normal()); + + Self { + address, + manager, + connection_requester, + generation_subscriber, + event_emitter: CmapEventEmitter::new(None, ObjectId::new()), + } + } + + /// Checks out a connection from the pool. This method will yield until this thread is at the + /// front of the wait queue, and then will block again if no available connections are in the + /// pool and the total number of connections is not less than the max pool size. + pub(crate) async fn check_out(&self) -> Result { + let time_started = Instant::now(); + self.event_emitter.emit_event(|| { + ConnectionCheckoutStartedEvent { + address: self.address.clone(), + } + .into() + }); + + let response = self.connection_requester.request().await; + + let conn = match response { + ConnectionRequestResult::Pooled(c) => Ok(*c), + ConnectionRequestResult::Establishing(task) => task.await, + ConnectionRequestResult::PoolCleared(e) => { + Err(Error::pool_cleared_error(&self.address, &e)) + } + ConnectionRequestResult::PoolWarmed => { + Err(Error::internal("Invalid result from connection requester")) + } + }; + + match conn { + Ok(ref conn) => { + self.event_emitter + .emit_event(|| conn.checked_out_event(time_started).into()); + } + + Err(ref _err) => { + self.event_emitter.emit_event(|| { + ConnectionCheckoutFailedEvent { + address: self.address.clone(), + reason: ConnectionCheckoutFailedReason::ConnectionError, + #[cfg(feature = "tracing-unstable")] + error: Some(_err.clone()), + duration: Instant::now() - time_started, + } + .into() + }); + } + } + + conn + } + + /// Increments the generation of the pool. Rather than eagerly removing stale connections from + /// the pool, they are left for the background thread to clean up. + pub(crate) async fn clear(&self, cause: Error, service_id: Option) { + self.manager.clear(cause, service_id).await + } + + /// Mark the pool as "ready" as per the CMAP specification. + pub(crate) async fn mark_as_ready(&self) { + self.manager.mark_as_ready().await + } + + pub(crate) fn generation(&self) -> PoolGeneration { + self.generation_subscriber.generation() + } + + pub(crate) fn broadcast(&self, msg: BroadcastMessage) -> AcknowledgmentReceiver<()> { + self.manager.broadcast(msg) + } +} + +pub(crate) fn is_faas() -> bool { + establish::handshake::FaasEnvironmentName::new().is_some() +} diff --git a/src/cmap/conn.rs b/src/cmap/conn.rs new file mode 100644 index 000000000..d7c39cca7 --- /dev/null +++ b/src/cmap/conn.rs @@ -0,0 +1,432 @@ +mod command; +pub(crate) mod pooled; +mod stream_description; +pub(crate) mod wire; + +use std::{sync::Arc, time::Instant}; + +use derive_where::derive_where; +use serde::Serialize; +use tokio::{ + io::BufStream, + sync::{ + broadcast::{self, error::RecvError}, + mpsc, + Mutex, + }, +}; + +use self::wire::{Message, MessageFlags}; +use super::{conn::pooled::PooledConnection, manager::PoolManager}; +use crate::{ + bson::oid::ObjectId, + cmap::PoolGeneration, + error::{load_balanced_mode_mismatch, Error, ErrorKind, Result}, + event::cmap::{CmapEventEmitter, ConnectionCreatedEvent}, + options::ServerAddress, + runtime::AsyncStream, +}; +pub(crate) use command::{Command, RawCommandResponse}; +pub(crate) use stream_description::StreamDescription; + +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +use crate::options::Compressor; + +/// User-facing information about a connection to the database. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ConnectionInfo { + /// A driver-generated identifier that uniquely identifies the connection. + pub id: u32, + + /// A server-generated identifier that uniquely identifies the connection. Available on server + /// versions 4.2+. This may be used to correlate driver connections with server logs. + pub server_id: Option, + + /// The address that the connection is connected to. + pub address: ServerAddress, +} + +/// A wrapper around Stream that contains all the CMAP information needed to maintain a connection. +#[derive_where(Debug)] +pub(crate) struct Connection { + /// The stream this connection reads from and writes to. + stream: BufStream, + + /// The cached stream description from the connection's handshake. + pub(crate) stream_description: Option, + + /// Driver-generated ID for the connection. + pub(crate) id: u32, + + /// The server-side ID for this connection. Only set on server versions 4.2+. + pub(crate) server_id: Option, + + /// The address of the server to which this connection connects. + pub(crate) address: ServerAddress, + + /// The time at which this connection was created. + pub(crate) time_created: Instant, + + /// Whether or not a command is currently being run on this connection. This is set to `true` + /// right before sending bytes to the server and set back to `false` once a full response has + /// been read. + command_executing: bool, + + /// Stores a network error encountered while reading or writing. Once the connection has + /// received an error, it should not be used again and will be closed upon check-in to the + /// pool. + error: Option, + + /// Whether the most recently received message included the moreToCome flag, indicating the + /// server may send more responses without any additional requests. Attempting to send new + /// messages on this connection while this value is true will return an error. This value + /// will remain true until a server response does not include the moreToComeFlag. + more_to_come: bool, + + /// The token callback for OIDC authentication. + #[derive_where(skip)] + pub(crate) oidc_token_gen_id: tokio::sync::Mutex, + + /// The compressor to use to compress outgoing messages. + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + pub(crate) compressor: Option, +} + +impl Connection { + /// Create a new connection. + pub(crate) fn new( + address: ServerAddress, + stream: AsyncStream, + id: u32, + time_created: Instant, + ) -> Self { + Self { + stream: BufStream::new(stream), + stream_description: None, + address, + id, + server_id: None, + time_created, + command_executing: false, + error: None, + more_to_come: false, + oidc_token_gen_id: tokio::sync::Mutex::new(0), + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressor: None, + } + } + + pub(crate) fn take(&mut self) -> Self { + Self { + stream: std::mem::replace(&mut self.stream, BufStream::new(AsyncStream::Null)), + stream_description: self.stream_description.take(), + address: self.address.clone(), + id: self.id, + server_id: self.server_id, + time_created: self.time_created, + command_executing: self.command_executing, + error: self.error.take(), + more_to_come: false, + oidc_token_gen_id: tokio::sync::Mutex::new(0), + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressor: self.compressor.clone(), + } + } + + pub(crate) fn address(&self) -> &ServerAddress { + &self.address + } + + /// Gets the connection's StreamDescription. + pub(crate) fn stream_description(&self) -> Result<&StreamDescription> { + self.stream_description.as_ref().ok_or_else(|| { + ErrorKind::Internal { + message: "Stream checked out but not handshaked".to_string(), + } + .into() + }) + } + + /// Whether the connection is currently executing an operation. + pub(super) fn is_executing(&self) -> bool { + self.command_executing + } + + /// Whether an error has been encountered on this connection. + pub(super) fn has_errored(&self) -> bool { + self.error.is_some() + } + + pub(crate) async fn send_message_with_cancellation( + &mut self, + message: impl TryInto>, + cancellation_receiver: &mut broadcast::Receiver<()>, + ) -> Result { + tokio::select! { + biased; + + // A lagged error indicates that more heartbeats failed than the channel's capacity + // between checking out this connection and executing the operation. If this occurs, + // then proceed with cancelling the operation. RecvError::Closed can be ignored, as + // the sender (and by extension the connection pool) dropping does not indicate that + // the operation should be cancelled. + Ok(_) | Err(RecvError::Lagged(_)) = cancellation_receiver.recv() => { + let error: Error = ErrorKind::ConnectionPoolCleared { + message: format!( + "Connection to {} interrupted due to server monitor timeout", + self.address, + ) + }.into(); + self.error = Some(error.clone()); + Err(error) + } + // This future is not cancellation safe because it contains calls to methods that are + // not cancellation safe (e.g. AsyncReadExt::read_exact). However, in the case that + // this future is cancelled because a cancellation message was received, this + // connection will be closed upon being returned to the pool, so any data loss on its + // underlying stream is not an issue. + result = self.send_message(message) => result, + } + } + + pub(crate) async fn send_message( + &mut self, + message: impl TryInto>, + ) -> Result { + let message = message.try_into().map_err(Into::into)?; + + if self.more_to_come { + return Err(Error::internal(format!( + "attempted to send a new message to {} but moreToCome bit was set", + self.address() + ))); + } + + self.command_executing = true; + + let max_message_size = self.max_message_size_bytes(); + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + let write_result = match self.compressor { + Some(ref compressor) if message.should_compress => { + message + .write_op_compressed_to(&mut self.stream, compressor, max_message_size) + .await + } + _ => { + message + .write_op_msg_to(&mut self.stream, max_message_size) + .await + } + }; + #[cfg(all( + not(feature = "zstd-compression"), + not(feature = "zlib-compression"), + not(feature = "snappy-compression") + ))] + let write_result = message + .write_op_msg_to(&mut self.stream, max_message_size) + .await; + + if let Err(ref err) = write_result { + self.error = Some(err.clone()); + } + write_result?; + + let response_message_result = Message::read_from(&mut self.stream, max_message_size).await; + self.command_executing = false; + if let Err(ref err) = response_message_result { + self.error = Some(err.clone()); + } + + let response_message = response_message_result?; + self.more_to_come = response_message.flags.contains(MessageFlags::MORE_TO_COME); + + Ok(RawCommandResponse::new( + self.address.clone(), + response_message, + )) + } + + /// Receive the next message from the connection. + /// This will return an error if the previous response on this connection did not include the + /// moreToCome flag. + pub(crate) async fn receive_message(&mut self) -> Result { + if !self.more_to_come { + return Err(Error::internal(format!( + "attempted to stream response from connection to {} but moreToCome bit was not set", + self.address() + ))); + } + + self.command_executing = true; + let response_message_result = Message::read_from( + &mut self.stream, + self.stream_description + .as_ref() + .map(|d| d.max_message_size_bytes), + ) + .await; + self.command_executing = false; + if let Err(ref err) = response_message_result { + self.error = Some(err.clone()); + } + + let response_message = response_message_result?; + self.more_to_come = response_message.flags.contains(MessageFlags::MORE_TO_COME); + + Ok(RawCommandResponse::new( + self.address.clone(), + response_message, + )) + } + + /// Whether or not the previous command response indicated that the server may send + /// more responses without another request. + pub(crate) fn is_streaming(&self) -> bool { + self.more_to_come + } + + fn max_message_size_bytes(&self) -> Option { + self.stream_description + .as_ref() + .map(|d| d.max_message_size_bytes) + } +} + +/// A handle to a pinned connection - the connection itself can be retrieved or returned to the +/// normal pool via this handle. +#[derive(Debug)] +pub(crate) struct PinnedConnectionHandle { + id: u32, + receiver: Arc>>, +} + +impl PinnedConnectionHandle { + /// Make a new `PinnedConnectionHandle` that refers to the same connection as this one. + /// Use with care and only when "lending" a handle in a way that can't be expressed as a + /// normal borrow. + pub(crate) fn replicate(&self) -> Self { + Self { + id: self.id, + receiver: self.receiver.clone(), + } + } + + /// Retrieve the pinned connection. Will fail if the connection has been unpinned or is still + /// in use. + pub(crate) async fn take_connection(&self) -> Result { + use tokio::sync::mpsc::error::TryRecvError; + let mut receiver = self.receiver.lock().await; + let mut connection = match receiver.try_recv() { + Ok(conn) => conn, + Err(TryRecvError::Disconnected) => { + return Err(Error::internal(format!( + "cannot take connection after unpin (id={})", + self.id + ))) + } + Err(TryRecvError::Empty) => { + return Err(Error::internal(format!( + "cannot take in-use connection (id={})", + self.id + ))) + } + }; + connection.mark_pinned_in_use(); + Ok(connection) + } + + pub(crate) fn id(&self) -> u32 { + self.id + } +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct LoadBalancedGeneration { + pub(crate) generation: u32, + pub(crate) service_id: ObjectId, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ConnectionGeneration { + Normal(u32), + LoadBalanced(Option), +} + +impl ConnectionGeneration { + pub(crate) fn service_id(self) -> Option { + match self { + ConnectionGeneration::LoadBalanced(Some(gen)) => Some(gen.service_id), + _ => None, + } + } + + pub(crate) fn is_stale(self, current_generation: &PoolGeneration) -> bool { + match (self, current_generation) { + (ConnectionGeneration::Normal(cgen), PoolGeneration::Normal(pgen)) => cgen != *pgen, + (ConnectionGeneration::LoadBalanced(cgen), PoolGeneration::LoadBalanced(gen_map)) => { + if let Some(cgen) = cgen { + cgen.generation != *gen_map.get(&cgen.service_id).unwrap_or(&0) + } else { + // In the event that an error occurred during handshake and no serviceId was + // returned, just ignore the error for SDAM purposes, since + // we won't know which serviceId to clear for. + false + } + } + _ => load_balanced_mode_mismatch!(false), + } + } +} + +impl From for ConnectionGeneration { + fn from(gen: LoadBalancedGeneration) -> Self { + ConnectionGeneration::LoadBalanced(Some(gen)) + } +} + +/// Struct encapsulating the information needed to establish a `Connection`. +/// +/// Creating a `PendingConnection` contributes towards the total connection count of a pool, despite +/// not actually making a TCP connection to the pool's endpoint. This models a "pending" Connection +/// from the CMAP specification. +pub(crate) struct PendingConnection { + pub(crate) id: u32, + pub(crate) address: ServerAddress, + pub(crate) generation: PoolGeneration, + pub(crate) event_emitter: CmapEventEmitter, + pub(crate) time_created: Instant, + pub(crate) cancellation_receiver: Option>, +} + +impl PendingConnection { + /// Helper to create a `ConnectionCreatedEvent` for the connection. + pub(super) fn created_event(&self) -> ConnectionCreatedEvent { + ConnectionCreatedEvent { + address: self.address.clone(), + connection_id: self.id, + } + } +} diff --git a/src/cmap/conn/command.rs b/src/cmap/conn/command.rs index fa6ee8a25..efa5e5859 100644 --- a/src/cmap/conn/command.rs +++ b/src/cmap/conn/command.rs @@ -1,10 +1,10 @@ -use bson::{RawDocument, RawDocumentBuf}; +use crate::bson::{RawDocument, RawDocumentBuf}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use super::wire::Message; +use super::wire::{message::DocumentSequence, Message}; use crate::{ bson::Document, - client::{options::ServerApi, ClusterTime, HELLO_COMMAND_NAMES, REDACTED_COMMANDS}, + client::{options::ServerApi, ClusterTime}, error::{Error, ErrorKind, Result}, hello::{HelloCommandResponse, HelloReply}, operation::{CommandErrorBody, CommandResponse}, @@ -13,28 +13,11 @@ use crate::{ ClientSession, }; -/// A command that has been serialized to BSON. -#[derive(Debug)] -pub(crate) struct RawCommand { - pub(crate) name: String, - pub(crate) target_db: String, - /// Whether or not the server may respond to this command multiple times via the moreToComeBit. - pub(crate) exhaust_allowed: bool, - pub(crate) bytes: Vec, -} - -impl RawCommand { - pub(crate) fn should_compress(&self) -> bool { - let name = self.name.to_lowercase(); - !REDACTED_COMMANDS.contains(name.as_str()) && !HELLO_COMMAND_NAMES.contains(name.as_str()) - } -} - /// Driver-side model of a database command. #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] -pub(crate) struct Command { +pub(crate) struct Command { #[serde(skip)] pub(crate) name: String, @@ -42,7 +25,10 @@ pub(crate) struct Command { pub(crate) exhaust_allowed: bool, #[serde(flatten)] - pub(crate) body: T, + pub(crate) body: RawDocumentBuf, + + #[serde(skip)] + pub(crate) document_sequences: Vec, #[serde(rename = "$db")] pub(crate) target_db: String, @@ -69,13 +55,14 @@ pub(crate) struct Command { recovery_token: Option, } -impl Command { - pub(crate) fn new(name: String, target_db: String, body: T) -> Self { +impl Command { + pub(crate) fn new(name: impl ToString, target_db: impl ToString, body: RawDocumentBuf) -> Self { Self { - name, - target_db, + name: name.to_string(), + target_db: target_db.to_string(), exhaust_allowed: false, body, + document_sequences: Vec::new(), lsid: None, cluster_time: None, server_api: None, @@ -89,16 +76,17 @@ impl Command { } pub(crate) fn new_read( - name: String, - target_db: String, + name: impl ToString, + target_db: impl ToString, read_concern: Option, - body: T, + body: RawDocumentBuf, ) -> Self { Self { - name, - target_db, + name: name.to_string(), + target_db: target_db.to_string(), exhaust_allowed: false, body, + document_sequences: Vec::new(), lsid: None, cluster_time: None, server_api: None, @@ -111,6 +99,17 @@ impl Command { } } + pub(crate) fn add_document_sequence( + &mut self, + identifier: impl ToString, + documents: Vec, + ) { + self.document_sequences.push(DocumentSequence { + identifier: identifier.to_string(), + documents, + }); + } + pub(crate) fn set_session(&mut self, session: &ClientSession) { self.lsid = Some(session.id().clone()) } @@ -186,29 +185,18 @@ pub(crate) struct RawCommandResponse { impl RawCommandResponse { #[cfg(test)] pub(crate) fn with_document_and_address(source: ServerAddress, doc: Document) -> Result { - let mut raw = Vec::new(); - doc.to_writer(&mut raw)?; + #[cfg(not(feature = "bson-3"))] + use crate::bson_compat::{DocumentExt as _, RawDocumentBufExt as _}; + + let raw = doc.encode_to_vec()?; Ok(Self { source, - raw: RawDocumentBuf::from_bytes(raw)?, + raw: RawDocumentBuf::decode_from_bytes(raw)?, }) } - /// Initialize a response from a document. - #[cfg(test)] - pub(crate) fn with_document(doc: Document) -> Result { - Self::with_document_and_address( - ServerAddress::Tcp { - host: "localhost".to_string(), - port: None, - }, - doc, - ) - } - - pub(crate) fn new(source: ServerAddress, message: Message) -> Result { - let raw = message.single_document_response()?; - Ok(Self::new_raw(source, RawDocumentBuf::from_bytes(raw)?)) + pub(crate) fn new(source: ServerAddress, message: Message) -> Self { + Self::new_raw(source, message.document_payload) } pub(crate) fn new_raw(source: ServerAddress, raw: RawDocumentBuf) -> Self { @@ -216,17 +204,7 @@ impl RawCommandResponse { } pub(crate) fn body<'a, T: Deserialize<'a>>(&'a self) -> Result { - bson::from_slice(self.raw.as_bytes()).map_err(|e| { - Error::from(ErrorKind::InvalidResponse { - message: format!("{}", e), - }) - }) - } - - /// Used to handle decoding responses where the server may return invalid UTF-8 in error - /// messages. - pub(crate) fn body_utf8_lossy<'a, T: Deserialize<'a>>(&'a self) -> Result { - bson::from_slice_utf8_lossy(self.raw.as_bytes()).map_err(|e| { + crate::bson_compat::deserialize_from_slice(self.raw.as_bytes()).map_err(|e| { Error::from(ErrorKind::InvalidResponse { message: format!("{}", e), }) diff --git a/src/cmap/conn/mod.rs b/src/cmap/conn/mod.rs deleted file mode 100644 index b2a7c5cba..000000000 --- a/src/cmap/conn/mod.rs +++ /dev/null @@ -1,611 +0,0 @@ -mod command; -mod stream_description; -mod wire; - -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; - -use derivative::Derivative; -use serde::Serialize; -use tokio::{ - io::BufStream, - sync::{mpsc, Mutex}, -}; - -use self::wire::{Message, MessageFlags}; -use super::manager::PoolManager; -use crate::{ - bson::oid::ObjectId, - cmap::PoolGeneration, - compression::Compressor, - error::{load_balanced_mode_mismatch, Error, ErrorKind, Result}, - event::cmap::{ - CmapEventEmitter, - ConnectionCheckedInEvent, - ConnectionCheckedOutEvent, - ConnectionClosedEvent, - ConnectionClosedReason, - ConnectionCreatedEvent, - ConnectionReadyEvent, - }, - options::ServerAddress, - runtime::AsyncStream, -}; -pub(crate) use command::{Command, RawCommand, RawCommandResponse}; -pub(crate) use stream_description::StreamDescription; -pub(crate) use wire::next_request_id; - -/// User-facing information about a connection to the database. -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ConnectionInfo { - /// A driver-generated identifier that uniquely identifies the connection. - pub id: u32, - - /// A server-generated identifier that uniquely identifies the connection. Available on server - /// versions 4.2+. This may be used to correlate driver connections with server logs. - /// If the connection ID sent by the server is too large for an i32, this will be a truncated - /// value. - pub server_id: Option, - - /// A server-generated identifier that uniquely identifies the connection. Available on server - /// versions 4.2+. This may be used to correlate driver connections with server logs. This - /// value will not be truncated and should be used rather than `server_id`. - pub server_id_i64: Option, - - /// The address that the connection is connected to. - pub address: ServerAddress, -} - -/// A wrapper around Stream that contains all the CMAP information needed to maintain a connection. -#[derive(Derivative)] -#[derivative(Debug)] -pub(crate) struct Connection { - /// Driver-generated ID for the connection. - pub(super) id: u32, - /// Server-generated ID for the connection. - pub(crate) server_id: Option, - - pub(crate) address: ServerAddress, - pub(crate) generation: ConnectionGeneration, - - /// The cached StreamDescription from the connection's handshake. - pub(super) stream_description: Option, - - /// Marks the time when the connection was last checked into the pool. This is used - /// to detect if the connection is idle. - ready_and_available_time: Option, - - /// PoolManager used to check this connection back in when dropped. - /// None when checked into the pool. - pub(super) pool_manager: Option, - - /// Whether or not a command is currently being run on this connection. This is set to `true` - /// right before sending bytes to the server and set back to `false` once a full response has - /// been read. - command_executing: bool, - - /// Stores a network error encountered while reading or writing. Once the connection has - /// received an error, it should not be used again and will be closed upon check-in to the - /// pool. - error: Option, - - /// Whether the most recently received message included the moreToCome flag, indicating the - /// server may send more responses without any additional requests. Attempting to send new - /// messages on this connection while this value is true will return an error. This value - /// will remain true until a server response does not include the moreToComeFlag. - more_to_come: bool, - - stream: BufStream, - - /// Compressor that the client will use before sending messages. - /// This compressor does not get used to decompress server messages. - /// The client will decompress server messages using whichever compressor - /// the server indicates in its message. This compressor is the first - /// compressor in the client's compressor list that also appears in the - /// server's compressor list. - pub(super) compressor: Option, - - /// If the connection is pinned to a cursor or transaction, the channel sender to return this - /// connection to the pin holder. - pinned_sender: Option>, - - /// Type responsible for emitting events related to this connection. This is None for - /// monitoring connections as we do not emit events for those. - #[derivative(Debug = "ignore")] - event_emitter: Option, -} - -impl Connection { - fn new( - address: ServerAddress, - stream: AsyncStream, - id: u32, - generation: ConnectionGeneration, - ) -> Self { - Self { - id, - server_id: None, - generation, - pool_manager: None, - command_executing: false, - ready_and_available_time: None, - stream: BufStream::new(stream), - address, - event_emitter: None, - stream_description: None, - error: None, - pinned_sender: None, - compressor: None, - more_to_come: false, - } - } - - /// Create a connection intended to be stored in a connection pool for operation execution. - /// TODO: RUST-1454 Remove this from `Connection`, instead wrap a `Connection` type in a - /// separate type specific to pool. - pub(crate) fn new_pooled(pending_connection: PendingConnection, stream: AsyncStream) -> Self { - let generation = match pending_connection.generation { - PoolGeneration::Normal(gen) => ConnectionGeneration::Normal(gen), - PoolGeneration::LoadBalanced(_) => ConnectionGeneration::LoadBalanced(None), - }; - let mut conn = Self::new( - pending_connection.address, - stream, - pending_connection.id, - generation, - ); - conn.event_emitter = Some(pending_connection.event_emitter); - conn - } - - /// Create a connection intended for monitoring purposes. - /// TODO: RUST-1454 Rename this to just `new`, drop the pooling-specific data. - pub(crate) fn new_monitoring(address: ServerAddress, stream: AsyncStream) -> Self { - // Monitoring connections don't have IDs, so just use 0 as a placeholder here. - Self::new(address, stream, 0, ConnectionGeneration::Monitoring) - } - - pub(crate) fn info(&self) -> ConnectionInfo { - ConnectionInfo { - id: self.id, - server_id: self.server_id.map(|value| value as i32), - server_id_i64: self.server_id, - address: self.address.clone(), - } - } - - pub(crate) fn service_id(&self) -> Option { - self.stream_description - .as_ref() - .and_then(|sd| sd.service_id) - } - - pub(crate) fn address(&self) -> &ServerAddress { - &self.address - } - - /// Helper to mark the time that the connection was checked into the pool for the purpose of - /// detecting when it becomes idle. - pub(super) fn mark_as_available(&mut self) { - self.pool_manager.take(); - self.ready_and_available_time = Some(Instant::now()); - } - - /// Helper to mark that the connection has been checked out of the pool. This ensures that the - /// connection is not marked as idle based on the time that it's checked out and that it has a - /// reference to the pool. - pub(super) fn mark_as_in_use(&mut self, manager: PoolManager) { - self.pool_manager = Some(manager); - self.ready_and_available_time.take(); - } - - /// Checks if the connection is idle. - pub(super) fn is_idle(&self, max_idle_time: Option) -> bool { - self.ready_and_available_time - .and_then(|ready_and_available_time| { - max_idle_time.map(|max_idle_time| { - Instant::now().duration_since(ready_and_available_time) >= max_idle_time - }) - }) - .unwrap_or(false) - } - - /// Checks if the connection is currently executing an operation. - pub(super) fn is_executing(&self) -> bool { - self.command_executing - } - - /// Checks if the connection experienced a network error and should be closed. - pub(super) fn has_errored(&self) -> bool { - self.error.is_some() - } - - /// Helper to create a `ConnectionCheckedOutEvent` for the connection. - pub(super) fn checked_out_event(&self) -> ConnectionCheckedOutEvent { - ConnectionCheckedOutEvent { - address: self.address.clone(), - connection_id: self.id, - } - } - - /// Helper to create a `ConnectionCheckedInEvent` for the connection. - pub(super) fn checked_in_event(&self) -> ConnectionCheckedInEvent { - ConnectionCheckedInEvent { - address: self.address.clone(), - connection_id: self.id, - } - } - - /// Helper to create a `ConnectionReadyEvent` for the connection. - pub(super) fn ready_event(&self) -> ConnectionReadyEvent { - ConnectionReadyEvent { - address: self.address.clone(), - connection_id: self.id, - } - } - - /// Helper to create a `ConnectionClosedEvent` for the connection. - pub(super) fn closed_event(&self, reason: ConnectionClosedReason) -> ConnectionClosedEvent { - ConnectionClosedEvent { - address: self.address.clone(), - connection_id: self.id, - reason, - #[cfg(feature = "tracing-unstable")] - error: self.error.clone(), - } - } - - async fn send_message( - &mut self, - message: Message, - to_compress: bool, - ) -> Result { - if self.more_to_come { - return Err(Error::internal(format!( - "attempted to send a new message to {} but moreToCome bit was set", - self.address() - ))); - } - - self.command_executing = true; - - // If the client has agreed on a compressor with the server, and the command - // is the right type of command, then compress the message. - let write_result = match self.compressor { - Some(ref compressor) if to_compress => { - message - .write_compressed_to(&mut self.stream, compressor) - .await - } - _ => message.write_to(&mut self.stream).await, - }; - - if let Err(ref err) = write_result { - self.error = Some(err.clone()); - } - write_result?; - - let response_message_result = Message::read_from( - &mut self.stream, - self.stream_description - .as_ref() - .map(|d| d.max_message_size_bytes), - ) - .await; - self.command_executing = false; - if let Err(ref err) = response_message_result { - self.error = Some(err.clone()); - } - - let response_message = response_message_result?; - self.more_to_come = response_message.flags.contains(MessageFlags::MORE_TO_COME); - - RawCommandResponse::new(self.address.clone(), response_message) - } - - /// Executes a `Command` and returns a `CommandResponse` containing the result from the server. - /// - /// An `Ok(...)` result simply means the server received the command and that the driver - /// driver received the response; it does not imply anything about the success of the command - /// itself. - pub(crate) async fn send_command( - &mut self, - command: Command, - request_id: impl Into>, - ) -> Result { - let to_compress = command.should_compress(); - let message = Message::with_command(command, request_id.into())?; - self.send_message(message, to_compress).await - } - - /// Executes a `RawCommand` and returns a `CommandResponse` containing the result from the - /// server. - /// - /// An `Ok(...)` result simply means the server received the command and that the driver - /// received the response; it does not imply anything about the success of the command - /// itself. - pub(crate) async fn send_raw_command( - &mut self, - command: RawCommand, - request_id: impl Into>, - ) -> Result { - let to_compress = command.should_compress(); - let message = Message::with_raw_command(command, request_id.into()); - self.send_message(message, to_compress).await - } - - /// Receive the next message from the connection. - /// This will return an error if the previous response on this connection did not include the - /// moreToCome flag. - pub(crate) async fn receive_message(&mut self) -> Result { - if !self.more_to_come { - return Err(Error::internal(format!( - "attempted to stream response from connection to {} but moreToCome bit was not set", - self.address() - ))); - } - - self.command_executing = true; - let response_message_result = Message::read_from( - &mut self.stream, - self.stream_description - .as_ref() - .map(|d| d.max_message_size_bytes), - ) - .await; - self.command_executing = false; - if let Err(ref err) = response_message_result { - self.error = Some(err.clone()); - } - - let response_message = response_message_result?; - self.more_to_come = response_message.flags.contains(MessageFlags::MORE_TO_COME); - - RawCommandResponse::new(self.address.clone(), response_message) - } - - /// Gets the connection's StreamDescription. - pub(crate) fn stream_description(&self) -> Result<&StreamDescription> { - self.stream_description.as_ref().ok_or_else(|| { - ErrorKind::Internal { - message: "Stream checked out but not handshaked".to_string(), - } - .into() - }) - } - - /// Pin the connection, removing it from the normal connection pool. - pub(crate) fn pin(&mut self) -> Result { - if self.pinned_sender.is_some() { - return Err(Error::internal(format!( - "cannot pin an already-pinned connection (id = {})", - self.id - ))); - } - if self.pool_manager.is_none() { - return Err(Error::internal(format!( - "cannot pin a checked-in connection (id = {})", - self.id - ))); - } - let (tx, rx) = mpsc::channel(1); - self.pinned_sender = Some(tx); - Ok(PinnedConnectionHandle { - id: self.id, - receiver: Arc::new(Mutex::new(rx)), - }) - } - - /// Close this connection, emitting a `ConnectionClosedEvent` with the supplied reason. - pub(super) fn close_and_drop(mut self, reason: ConnectionClosedReason) { - self.close(reason); - } - - /// Close this connection, emitting a `ConnectionClosedEvent` with the supplied reason. - fn close(&mut self, reason: ConnectionClosedReason) { - self.pool_manager.take(); - if let Some(ref event_emitter) = self.event_emitter { - event_emitter.emit_event(|| self.closed_event(reason).into()); - } - } - - /// Nullify the inner state and return it in a new `Connection` for checking back in to - /// the pool. - fn take(&mut self) -> Connection { - Connection { - id: self.id, - server_id: self.server_id, - address: self.address.clone(), - generation: self.generation, - stream: std::mem::replace(&mut self.stream, BufStream::new(AsyncStream::Null)), - event_emitter: self.event_emitter.take(), - stream_description: self.stream_description.take(), - command_executing: self.command_executing, - error: self.error.take(), - pool_manager: None, - ready_and_available_time: None, - pinned_sender: self.pinned_sender.clone(), - compressor: self.compressor.clone(), - more_to_come: false, - } - } - - /// Whether or not the previous command response indicated that the server may send - /// more responses without another request. - pub(crate) fn is_streaming(&self) -> bool { - self.more_to_come - } - - /// Whether the connection supports sessions. - pub(crate) fn supports_sessions(&self) -> bool { - self.stream_description - .as_ref() - .and_then(|sd| sd.logical_session_timeout) - .is_some() - } -} - -impl Drop for Connection { - fn drop(&mut self) { - // If the connection has a pool manager, that means that the connection is - // being dropped when it's checked out. If the pool is still alive, it - // should check itself back in. Otherwise, the connection should close - // itself and emit a ConnectionClosed event (because the `close_and_drop` - // helper was not called explicitly). - // - // If the connection does not have a pool manager, then the connection is - // being dropped while it's not checked out. This means that the pool called - // the `close_and_drop` helper explicitly, so we don't add it back to the - // pool or emit any events. - if let Some(pool_manager) = self.pool_manager.take() { - let mut dropped_connection = self.take(); - let result = if let Some(sender) = self.pinned_sender.as_mut() { - // Preserve the pool manager and timestamp for pinned connections. - dropped_connection.pool_manager = Some(pool_manager.clone()); - dropped_connection.ready_and_available_time = self.ready_and_available_time; - match sender.try_send(dropped_connection) { - Ok(()) => Ok(()), - // The connection has been unpinned and should be checked back in. - Err(mpsc::error::TrySendError::Closed(mut conn)) => { - conn.pinned_sender = None; - conn.ready_and_available_time = None; - pool_manager.check_in(conn) - } - // The connection is being returned to the pin holder while another connection - // is in the pin buffer; this should never happen. Only possible action is to - // check the connection back in. - Err(mpsc::error::TrySendError::Full(mut conn)) => { - // Panic in debug mode - if cfg!(debug_assertions) { - panic!( - "buffer full when attempting to return a pinned connection (id = \ - {})", - conn.id - ); - } - // TODO RUST-230 log an error in non-debug mode. - conn.pinned_sender = None; - conn.ready_and_available_time = None; - pool_manager.check_in(conn) - } - } - } else { - pool_manager.check_in(dropped_connection) - }; - if let Err(mut conn) = result { - // the check in failed because the pool has been dropped, so we emit the event - // here and drop the connection. - conn.close(ConnectionClosedReason::PoolClosed); - } - } - } -} - -/// A handle to a pinned connection - the connection itself can be retrieved or returned to the -/// normal pool via this handle. -#[derive(Debug)] -pub(crate) struct PinnedConnectionHandle { - id: u32, - receiver: Arc>>, -} - -impl PinnedConnectionHandle { - /// Make a new `PinnedConnectionHandle` that refers to the same connection as this one. - /// Use with care and only when "lending" a handle in a way that can't be expressed as a - /// normal borrow. - pub(crate) fn replicate(&self) -> Self { - Self { - id: self.id, - receiver: self.receiver.clone(), - } - } - - /// Retrieve the pinned connection, blocking until it's available for use. Will fail if the - /// connection has been unpinned. - pub(crate) async fn take_connection(&self) -> Result { - let mut receiver = self.receiver.lock().await; - receiver.recv().await.ok_or_else(|| { - Error::internal(format!( - "cannot take connection after unpin (id={})", - self.id - )) - }) - } - - pub(crate) fn id(&self) -> u32 { - self.id - } -} - -#[derive(Debug, Clone, Copy)] -pub(crate) struct LoadBalancedGeneration { - pub(crate) generation: u32, - pub(crate) service_id: ObjectId, -} - -/// TODO: RUST-1454 Once we have separate types for pooled and non-pooled connections, the -/// monitoring case and the Option<> wrapper can be dropped from this. -#[derive(Debug, Clone, Copy)] -pub(crate) enum ConnectionGeneration { - Monitoring, - Normal(u32), - LoadBalanced(Option), -} - -impl ConnectionGeneration { - pub(crate) fn service_id(self) -> Option { - match self { - ConnectionGeneration::LoadBalanced(Some(gen)) => Some(gen.service_id), - _ => None, - } - } - - pub(crate) fn is_stale(self, current_generation: &PoolGeneration) -> bool { - match (self, current_generation) { - (ConnectionGeneration::Normal(cgen), PoolGeneration::Normal(pgen)) => cgen != *pgen, - (ConnectionGeneration::LoadBalanced(cgen), PoolGeneration::LoadBalanced(gen_map)) => { - if let Some(cgen) = cgen { - cgen.generation != *gen_map.get(&cgen.service_id).unwrap_or(&0) - } else { - // In the event that an error occurred during handshake and no serviceId was - // returned, just ignore the error for SDAM purposes, since - // we won't know which serviceId to clear for. - false - } - } - _ => load_balanced_mode_mismatch!(false), - } - } -} - -impl From for ConnectionGeneration { - fn from(gen: LoadBalancedGeneration) -> Self { - ConnectionGeneration::LoadBalanced(Some(gen)) - } -} - -/// Struct encapsulating the information needed to establish a `Connection`. -/// -/// Creating a `PendingConnection` contributes towards the total connection count of a pool, despite -/// not actually making a TCP connection to the pool's endpoint. This models a "pending" Connection -/// from the CMAP specification. -pub(crate) struct PendingConnection { - pub(crate) id: u32, - pub(crate) address: ServerAddress, - pub(crate) generation: PoolGeneration, - pub(crate) event_emitter: CmapEventEmitter, -} - -impl PendingConnection { - /// Helper to create a `ConnectionCreatedEvent` for the connection. - pub(super) fn created_event(&self) -> ConnectionCreatedEvent { - ConnectionCreatedEvent { - address: self.address.clone(), - connection_id: self.id, - } - } -} diff --git a/src/cmap/conn/pooled.rs b/src/cmap/conn/pooled.rs new file mode 100644 index 000000000..acf61fb55 --- /dev/null +++ b/src/cmap/conn/pooled.rs @@ -0,0 +1,398 @@ +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, + time::{Duration, Instant}, +}; + +use derive_where::derive_where; +use tokio::sync::{broadcast, mpsc, Mutex}; + +use super::{ + CmapEventEmitter, + Connection, + ConnectionGeneration, + ConnectionInfo, + Message, + PendingConnection, + PinnedConnectionHandle, + PoolManager, + RawCommandResponse, +}; +use crate::{ + bson::oid::ObjectId, + cmap::PoolGeneration, + error::{Error, Result}, + event::cmap::{ + ConnectionCheckedInEvent, + ConnectionCheckedOutEvent, + ConnectionClosedEvent, + ConnectionClosedReason, + ConnectionReadyEvent, + }, + runtime::AsyncStream, +}; + +/// A wrapper around the [`Connection`] type that represents a connection within a connection pool. +/// This type derefs into [`Connection`], so fields and methods exposed on that type can be called +/// directly from this one. +#[derive_where(Debug)] +pub(crate) struct PooledConnection { + /// The connection this pooled connection wraps. + connection: Connection, + + /// The connection pool generation from which this connection was checked out. + pub(crate) generation: ConnectionGeneration, + + /// Emitter for events related to this connection. + #[derive_where(skip)] + event_emitter: CmapEventEmitter, + + /// The state of this connection. + state: PooledConnectionState, +} + +/// The state of a pooled connection. +#[derive(Debug)] +enum PooledConnectionState { + /// The state associated with a connection checked into the connection pool. + CheckedIn { available_time: Instant }, + + /// The state associated with a connection checked out of the connection pool. + CheckedOut { + /// The manager used to check this connection back into the pool. + pool_manager: PoolManager, + + /// The receiver to receive a cancellation notice. Only present on non-load-balanced + /// connections. + cancellation_receiver: Option>, + }, + + /// The state associated with a pinned connection. + Pinned { + /// The state of the pinned connection. + pinned_state: PinnedState, + + pinned_sender: mpsc::Sender, + + /// The manager used to check this connection back into the pool. + pool_manager: PoolManager, + }, +} + +/// The state of a pinned connection. +#[derive(Clone, Debug)] +enum PinnedState { + /// The state associated with a pinned connection that is currently in use. + InUse, + + /// The state associated with a pinned connection that has been returned to its pinner. + Returned { + /// The time at which the connection was returned to its pinner. + returned_time: Instant, + }, +} + +impl Deref for PooledConnection { + type Target = Connection; + + fn deref(&self) -> &Self::Target { + &self.connection + } +} + +impl DerefMut for PooledConnection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.connection + } +} + +impl PooledConnection { + /// Creates a new pooled connection in the checked-in state. + pub(crate) fn new(pending_connection: PendingConnection, stream: AsyncStream) -> Self { + let connection = Connection::new( + pending_connection.address, + stream, + pending_connection.id, + pending_connection.time_created, + ); + let generation = match pending_connection.generation { + PoolGeneration::Normal(generation) => ConnectionGeneration::Normal(generation), + PoolGeneration::LoadBalanced(_) => ConnectionGeneration::LoadBalanced(None), + }; + Self { + connection, + generation, + event_emitter: pending_connection.event_emitter, + state: PooledConnectionState::CheckedIn { + available_time: Instant::now(), + }, + } + } + + pub(crate) fn info(&self) -> ConnectionInfo { + ConnectionInfo { + id: self.connection.id, + server_id: self.server_id, + address: self.connection.address.clone(), + } + } + + /// The service ID for this connection. Only returns a value if this connection is to a load + /// balancer. + pub(crate) fn service_id(&self) -> Option { + self.stream_description + .as_ref() + .and_then(|sd| sd.service_id) + } + + /// Sends a message on this connection. + pub(crate) async fn send_message( + &mut self, + message: impl TryInto>, + ) -> Result { + match self.state { + PooledConnectionState::CheckedOut { + cancellation_receiver: Some(ref mut cancellation_receiver), + .. + } => { + self.connection + .send_message_with_cancellation(message, cancellation_receiver) + .await + } + _ => self.connection.send_message(message).await, + } + } + + /// Updates the state of the connection to indicate that it is checked into the pool. + pub(crate) fn mark_checked_in(&mut self) { + if !matches!(self.state, PooledConnectionState::CheckedIn { .. }) { + let available_time = match self.state { + PooledConnectionState::Pinned { + pinned_state: PinnedState::Returned { returned_time }, + .. + } => returned_time, + _ => Instant::now(), + }; + self.state = PooledConnectionState::CheckedIn { available_time }; + } + } + + /// Updates the state of the connection to indicate that it is checked out of the pool. + pub(crate) fn mark_checked_out( + &mut self, + pool_manager: PoolManager, + cancellation_receiver: Option>, + ) { + self.state = PooledConnectionState::CheckedOut { + pool_manager, + cancellation_receiver, + }; + } + + /// Whether this connection is idle. + pub(crate) fn is_idle(&self, max_idle_time: Option) -> bool { + let Some(max_idle_time) = max_idle_time else { + return false; + }; + let available_time = match self.state { + PooledConnectionState::CheckedIn { available_time } => available_time, + PooledConnectionState::Pinned { + pinned_state: PinnedState::Returned { returned_time }, + .. + } => returned_time, + _ => return false, + }; + Instant::now().duration_since(available_time) >= max_idle_time + } + + /// Nullifies the internal state of this connection and returns it in a new [PooledConnection] + /// with the given state. + fn take(&mut self, new_state: PooledConnectionState) -> Self { + Self { + connection: self.connection.take(), + generation: self.generation, + event_emitter: self.event_emitter.clone(), + state: new_state, + } + } + + /// Pin the connection and return a handle to the pinned connection. + pub(crate) fn pin(&mut self) -> Result { + match &mut self.state { + PooledConnectionState::CheckedIn { .. } => Err(Error::internal(format!( + "cannot pin a checked-in connection (id = {})", + self.id + ))), + PooledConnectionState::CheckedOut { + ref pool_manager, .. + } => { + let (tx, rx) = mpsc::channel(1); + self.state = PooledConnectionState::Pinned { + // Mark the connection as in-use while the operation currently using the + // connection finishes. Once that operation drops the connection, it will be + // sent back to the pinner. + pinned_sender: tx, + pinned_state: PinnedState::InUse, + pool_manager: pool_manager.clone(), + }; + + Ok(PinnedConnectionHandle { + id: self.id, + receiver: Arc::new(Mutex::new(rx)), + }) + } + PooledConnectionState::Pinned { .. } => Err(Error::internal(format!( + "cannot pin an already-pinned connection (id = {})", + self.id + ))), + } + } + + pub(crate) fn mark_pinned_in_use(&mut self) { + match self.state { + PooledConnectionState::Pinned { + ref mut pinned_state, + .. + } => { + *pinned_state = PinnedState::InUse; + } + _ => { + if cfg!(debug_assertions) { + panic!("attempting to mark a non-pinned connection in use") + } + } + } + } + + /// Emit a [`ConnectionClosedEvent`] for this connection with the supplied reason. + pub(crate) fn emit_closed_event(&self, reason: ConnectionClosedReason) { + self.event_emitter + .emit_event(|| self.closed_event(reason).into()); + } + + /// Whether the connection supports sessions. + pub(crate) fn supports_sessions(&self) -> bool { + self.connection + .stream_description + .as_ref() + .and_then(|sd| sd.logical_session_timeout) + .is_some() + } + + /// Helper to create a [`ConnectionCheckedOutEvent`] for the connection. + pub(crate) fn checked_out_event(&self, time_started: Instant) -> ConnectionCheckedOutEvent { + ConnectionCheckedOutEvent { + address: self.connection.address.clone(), + connection_id: self.connection.id, + duration: Instant::now() - time_started, + } + } + + /// Helper to create a [`ConnectionCheckedInEvent`] for the connection. + pub(crate) fn checked_in_event(&self) -> ConnectionCheckedInEvent { + ConnectionCheckedInEvent { + address: self.connection.address.clone(), + connection_id: self.connection.id, + } + } + + /// Helper to create a [`ConnectionReadyEvent`] for the connection. + pub(crate) fn ready_event(&self) -> ConnectionReadyEvent { + ConnectionReadyEvent { + address: self.connection.address.clone(), + connection_id: self.connection.id, + duration: Instant::now() - self.connection.time_created, + } + } + + /// Helper to create a [`ConnectionClosedEvent`] for the connection. + pub(super) fn closed_event(&self, reason: ConnectionClosedReason) -> ConnectionClosedEvent { + ConnectionClosedEvent { + address: self.connection.address.clone(), + connection_id: self.connection.id, + reason, + #[cfg(feature = "tracing-unstable")] + error: self.connection.error.clone(), + } + } +} + +impl Drop for PooledConnection { + fn drop(&mut self) { + let result = match &self.state { + // Nothing needs to be done when a checked-in connection is dropped. + PooledConnectionState::CheckedIn { .. } => Ok(()), + // A checked-out connection should be sent back to the connection pool. + PooledConnectionState::CheckedOut { pool_manager, .. } => { + let pool_manager = pool_manager.clone(); + let dropped_connection = self.take(PooledConnectionState::CheckedIn { + available_time: Instant::now(), + }); + pool_manager.check_in(dropped_connection) + } + // A pinned connection should be returned to its pinner or to the connection pool. + PooledConnectionState::Pinned { + pinned_sender, + pinned_state, + pool_manager, + } => { + let pool_manager = pool_manager.clone(); + match pinned_state { + // If the pinned connection is in use, it is being dropped at the end of an + // operation and should be sent back to its pinner. + PinnedState::InUse => { + let pinned_sender = pinned_sender.clone(); + + let dropped_connection = self.take(PooledConnectionState::Pinned { + pinned_sender: pinned_sender.clone(), + pinned_state: PinnedState::Returned { + returned_time: Instant::now(), + }, + pool_manager: pool_manager.clone(), + }); + + if let Err(send_error) = pinned_sender.try_send(dropped_connection) { + use mpsc::error::TrySendError; + let returned_connection = match send_error { + // The connection is being returned to the pin holder while another + // connection is in the pin buffer; this should never happen. Panic + // in debug mode and send the connection back to the pool. + TrySendError::Full(returned_connection) => { + if cfg!(debug_assertions) { + panic!( + "buffer full when attempting to return pinned \ + connection to its pinner (id: {})", + self.id + ); + } + returned_connection + } + // The pinner has dropped, so the connection should be returned to + // the pool. + TrySendError::Closed(returned_connection) => returned_connection, + }; + + pool_manager.check_in(returned_connection) + } else { + Ok(()) + } + } + // The pinner of this connection has been dropped while the connection was + // sitting in its channel, so the connection should be returned to the pool. + PinnedState::Returned { .. } => { + pool_manager.check_in(self.take(PooledConnectionState::CheckedIn { + available_time: Instant::now(), + })) + } + } + } + }; + + // Checking in the connection failed because the pool has closed, so emit an event. + if let Err(mut returned_connection) = result { + // Mark as checked in to prevent a drop cycle. + returned_connection.mark_checked_in(); + returned_connection.emit_closed_event(ConnectionClosedReason::PoolClosed); + } + } +} diff --git a/src/cmap/conn/stream_description.rs b/src/cmap/conn/stream_description.rs index 2ea6c7f99..b6c6ca05f 100644 --- a/src/cmap/conn/stream_description.rs +++ b/src/cmap/conn/stream_description.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use bson::oid::ObjectId; +use crate::bson::oid::ObjectId; use crate::{client::options::ServerAddress, hello::HelloReply, sdam::ServerType}; @@ -76,31 +76,6 @@ impl StreamDescription { pub(crate) fn supports_retryable_writes(&self) -> bool { self.initial_server_type != ServerType::Standalone && self.logical_session_timeout.is_some() - && self.max_wire_version.map_or(false, |version| version >= 6) - } - - /// Gets a description of a stream for a 4.2 connection. - #[cfg(test)] - pub(crate) fn new_testing() -> Self { - Self::with_wire_version(8) - } - - /// Gets a description of a stream for a connection to a server with the provided - /// maxWireVersion. - #[cfg(test)] - pub(crate) fn with_wire_version(max_wire_version: i32) -> Self { - Self { - server_address: Default::default(), - initial_server_type: Default::default(), - max_wire_version: Some(max_wire_version), - min_wire_version: Some(max_wire_version), - sasl_supported_mechs: Default::default(), - logical_session_timeout: Some(Duration::from_secs(30 * 60)), - max_bson_object_size: 16 * 1024 * 1024, - max_write_batch_size: 100_000, - hello_ok: false, - max_message_size_bytes: Default::default(), - service_id: None, - } + && self.max_wire_version.is_some_and(|version| version >= 6) } } diff --git a/src/cmap/conn/wire.rs b/src/cmap/conn/wire.rs new file mode 100644 index 000000000..76ef2cfab --- /dev/null +++ b/src/cmap/conn/wire.rs @@ -0,0 +1,8 @@ +mod header; +pub(crate) mod message; +mod util; + +pub(crate) use self::{ + message::{Message, MessageFlags}, + util::next_request_id, +}; diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index 8b5d54bce..6c50deb35 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -1,85 +1,118 @@ use std::io::Read; +use crate::bson::{doc, Array, Document}; use bitflags::bitflags; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use super::header::{Header, OpCode}; +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +use crate::options::Compressor; use crate::{ + bson::RawDocumentBuf, bson_util, - cmap::{ - conn::{command::RawCommand, wire::util::SyncCountReader}, - Command, - }, + checked::Checked, + cmap::{conn::wire::util::SyncCountReader, Command}, + compression::decompress::decompress_message, error::{Error, ErrorKind, Result}, runtime::SyncLittleEndianRead, }; -use crate::compression::{Compressor, Decoder}; +use super::{ + header::{Header, OpCode}, + next_request_id, +}; /// Represents an OP_MSG wire protocol operation. #[derive(Debug)] pub(crate) struct Message { + /// OP_MSG payload type 0. + pub(crate) document_payload: RawDocumentBuf, + + /// OP_MSG payload type 1. + pub(crate) document_sequences: Vec, + pub(crate) response_to: i32, + pub(crate) flags: MessageFlags, - pub(crate) sections: Vec, + pub(crate) checksum: Option, + pub(crate) request_id: Option, + + /// Whether the message should be compressed by the driver. + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + pub(crate) should_compress: bool, } -impl Message { - /// Creates a `Message` from a given `Command`. - /// - /// Note that `response_to` will need to be set manually. - pub(crate) fn with_command(command: Command, request_id: Option) -> Result { - let bytes = bson::to_vec(&command)?; - Ok(Self::with_raw_command( - RawCommand { - bytes, - target_db: command.target_db, - name: command.name, - exhaust_allowed: command.exhaust_allowed, - }, - request_id, - )) - } +#[derive(Clone, Debug)] +pub(crate) struct DocumentSequence { + pub(crate) identifier: String, + pub(crate) documents: Vec, +} + +/// Creates a Message from a Command. The response_to and request_id fields must be set manually. +impl TryFrom for Message { + type Error = Error; + + fn try_from(command: Command) -> Result { + let document_payload = crate::bson_compat::serialize_to_raw_document_buf(&command)?; + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + let should_compress = command.should_compress(); - /// Creates a `Message` from a given `Command`. - /// - /// Note that `response_to` will need to be set manually. - pub(crate) fn with_raw_command(command: RawCommand, request_id: Option) -> Self { let mut flags = MessageFlags::empty(); if command.exhaust_allowed { flags |= MessageFlags::EXHAUST_ALLOWED; } - Self { + Ok(Self { + document_payload, + document_sequences: command.document_sequences, response_to: 0, flags, - sections: vec![MessageSection::Document(command.bytes)], checksum: None, - request_id, - } + request_id: None, + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + should_compress, + }) } +} + +impl Message { + /// Gets this message's command as a Document. If serialization fails, returns a document + /// containing the error. + pub(crate) fn get_command_document(&self) -> Document { + let mut command = match self.document_payload.to_document() { + Ok(document) => document, + Err(error) => return doc! { "serialization error": error.to_string() }, + }; - /// Gets the first document contained in this Message. - pub(crate) fn single_document_response(self) -> Result> { - let section = self.sections.into_iter().next().ok_or_else(|| { - Error::new( - ErrorKind::InvalidResponse { - message: "no response received from server".into(), - }, - Option::>::None, - ) - })?; - match section { - MessageSection::Document(doc) => Some(doc), - MessageSection::Sequence { documents, .. } => documents.into_iter().next(), + for document_sequence in &self.document_sequences { + let mut documents = Array::new(); + for document in &document_sequence.documents { + match document.to_document() { + Ok(document) => documents.push(document.into()), + Err(error) => return doc! { "serialization error": error.to_string() }, + } + } + command.insert(document_sequence.identifier.clone(), documents); } - .ok_or_else(|| { - Error::from(ErrorKind::InvalidResponse { - message: "no message received from the server".to_string(), - }) - }) + + command } /// Reads bytes from `reader` and deserializes them into a Message. @@ -100,7 +133,7 @@ impl Message { return Self::read_from_op_msg(reader, &header).await; } if header.op_code == OpCode::Compressed { - return Self::read_from_op_compressed(reader, &header).await; + return Self::read_op_compressed_from(reader, &header).await; } Err(Error::new( @@ -120,30 +153,30 @@ impl Message { mut reader: T, header: &Header, ) -> Result { - // TODO: RUST-616 ensure length is < maxMessageSizeBytes - let length_remaining = header.length - Header::LENGTH as i32; - let mut buf = vec![0u8; length_remaining as usize]; + let length = Checked::::try_from(header.length)?; + let length_remaining = length - Header::LENGTH; + let mut buf = vec![0u8; length_remaining.get()?]; reader.read_exact(&mut buf).await?; let reader = buf.as_slice(); - Self::read_op_common(reader, length_remaining, header) + Self::read_op_common(reader, length_remaining.get()?, header) } - async fn read_from_op_compressed( + async fn read_op_compressed_from( mut reader: T, header: &Header, ) -> Result { - let length_remaining = header.length - Header::LENGTH as i32; - let mut buf = vec![0u8; length_remaining as usize]; - reader.read_exact(&mut buf).await?; - let mut reader = buf.as_slice(); + let length = Checked::::try_from(header.length)?; + let length_remaining = length - Header::LENGTH; + let mut buffer = vec![0u8; length_remaining.get()?]; + reader.read_exact(&mut buffer).await?; + let mut compressed = buffer.as_slice(); - // Read original opcode (should be OP_MSG) - let original_opcode = reader.read_i32_sync()?; + let original_opcode = compressed.read_i32_sync()?; if original_opcode != OpCode::Message as i32 { return Err(ErrorKind::InvalidResponse { message: format!( - "The original opcode of the compressed message must be {}, but was {}.", + "The original opcode of the compressed message must be {}, but was {}", OpCode::Message as i32, original_opcode, ), @@ -151,107 +184,128 @@ impl Message { .into()); } - // Read uncompressed size - let uncompressed_size = reader.read_i32_sync()?; + let uncompressed_size = Checked::::try_from(compressed.read_i32_sync()?)?; + let compressor_id: u8 = compressed.read_u8_sync()?; + let decompressed = decompress_message(compressed, compressor_id)?; - // Read compressor id - let compressor_id: u8 = reader.read_u8_sync()?; - - // Get decoder - let decoder = Decoder::from_u8(compressor_id)?; - - // Decode message - let decoded_message = decoder.decode(reader)?; - - // Check that claimed length matches original length - if decoded_message.len() as i32 != uncompressed_size { + if decompressed.len() != uncompressed_size.get()? { return Err(ErrorKind::InvalidResponse { message: format!( "The server's message claims that the uncompressed length is {}, but was \ computed to be {}.", uncompressed_size, - decoded_message.len(), + decompressed.len(), ), } .into()); } - // Read decompressed message as a standard OP_MSG - let reader = decoded_message.as_slice(); - let length_remaining = decoded_message.len(); + // Read decompressed message as a standard OP_MSG. + let reader = decompressed.as_slice(); + let length_remaining = decompressed.len(); - Self::read_op_common(reader, length_remaining as i32, header) + Self::read_op_common(reader, length_remaining, header) } - fn read_op_common( - mut reader: &[u8], - mut length_remaining: i32, - header: &Header, - ) -> Result { + fn read_op_common(mut reader: &[u8], length_remaining: usize, header: &Header) -> Result { + let mut length_remaining = Checked::new(length_remaining); let flags = MessageFlags::from_bits_truncate(reader.read_u32_sync()?); - length_remaining -= std::mem::size_of::() as i32; + length_remaining -= std::mem::size_of::(); let mut count_reader = SyncCountReader::new(&mut reader); - let mut sections = Vec::new(); - - while length_remaining - count_reader.bytes_read() as i32 > 4 { - sections.push(MessageSection::read(&mut count_reader)?); + let mut document_payload = None; + let mut document_sequences = Vec::new(); + while (length_remaining - count_reader.bytes_read()).get()? > 4 { + let next_section = MessageSection::read(&mut count_reader)?; + match next_section { + MessageSection::Document(document) => { + if document_payload.is_some() { + return Err(ErrorKind::InvalidResponse { + message: "an OP_MSG response must contain exactly one payload type 0 \ + section" + .into(), + } + .into()); + } else { + document_payload = Some(document); + } + } + MessageSection::Sequence(document_sequence) => { + document_sequences.push(document_sequence) + } + } } - length_remaining -= count_reader.bytes_read() as i32; + length_remaining -= count_reader.bytes_read(); let mut checksum = None; - if length_remaining == 4 && flags.contains(MessageFlags::CHECKSUM_PRESENT) { + if length_remaining.get()? == 4 && flags.contains(MessageFlags::CHECKSUM_PRESENT) { checksum = Some(reader.read_u32_sync()?); - } else if length_remaining != 0 { - return Err(ErrorKind::InvalidResponse { - message: format!( - "The server indicated that the reply would be {} bytes long, but it instead \ - was {}", - header.length, - header.length - length_remaining + count_reader.bytes_read() as i32, - ), - } - .into()); + } else if length_remaining.get()? != 0 { + let header_len = Checked::::try_from(header.length)?; + return Err(Error::invalid_response(format!( + "The server indicated that the reply would be {} bytes long, but it instead was {}", + header.length, + header_len - length_remaining + count_reader.bytes_read(), + ))); } Ok(Self { response_to: header.response_to, flags, - sections, + document_payload: document_payload.ok_or_else(|| ErrorKind::InvalidResponse { + message: "an OP_MSG response must contain exactly one payload type 0 section" + .into(), + })?, + document_sequences, checksum, request_id: None, + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + should_compress: false, }) } - /// Serializes the Message to bytes and writes them to `writer`. - pub(crate) async fn write_to(&self, mut writer: T) -> Result<()> { - let mut sections_bytes = Vec::new(); - - for section in &self.sections { - section.write(&mut sections_bytes).await?; - } + /// Serializes this message into an OP_MSG and writes it to the provided writer. + pub(crate) async fn write_op_msg_to( + &self, + mut writer: T, + max_message_size_bytes: Option, + ) -> Result<()> { + let sections = self.get_sections_bytes()?; - let total_length = Header::LENGTH + let total_length = Checked::new(Header::LENGTH) + std::mem::size_of::() - + sections_bytes.len() + + sections.len() + self .checksum .as_ref() .map(std::mem::size_of_val) .unwrap_or(0); + let max_len = + Checked::try_from(max_message_size_bytes.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE_BYTES))?; + if total_length > max_len { + return Err(ErrorKind::InvalidArgument { + message: format!("Message length {} over maximum {}", total_length, max_len), + } + .into()); + } + let header = Header { - length: total_length as i32, - request_id: self.request_id.unwrap_or_else(super::util::next_request_id), + length: total_length.try_into()?, + request_id: self.request_id.unwrap_or_else(next_request_id), response_to: self.response_to, op_code: OpCode::Message, }; header.write_to(&mut writer).await?; writer.write_u32_le(self.flags.bits()).await?; - writer.write_all(§ions_bytes).await?; + writer.write_all(§ions).await?; if let Some(checksum) = self.checksum { writer.write_u32_le(checksum).await?; @@ -262,56 +316,91 @@ impl Message { Ok(()) } - /// Serializes message to bytes, compresses those bytes, and writes the bytes. - pub async fn write_compressed_to( + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + /// Serializes this message into an OP_COMPRESSED message and writes it to the provided writer. + pub(crate) async fn write_op_compressed_to( &self, mut writer: T, compressor: &Compressor, + max_message_size_bytes: Option, ) -> Result<()> { - let mut encoder = compressor.to_encoder()?; - let compressor_id = compressor.id() as u8; + let flag_bytes = &self.flags.bits().to_le_bytes(); + let section_bytes = self.get_sections_bytes()?; + let uncompressed_len = Checked::new(section_bytes.len()) + flag_bytes.len(); - let mut sections_bytes = Vec::new(); + let compressed_bytes = compressor.compress(flag_bytes, §ion_bytes)?; - for section in &self.sections { - section.write(&mut sections_bytes).await?; - } - let flag_bytes = &self.flags.bits().to_le_bytes(); - let uncompressed_len = sections_bytes.len() + flag_bytes.len(); - // Compress the flags and sections. Depending on the handshake - // this could use zlib, zstd or snappy - encoder.write_all(flag_bytes)?; - encoder.write_all(sections_bytes.as_slice())?; - let compressed_bytes = encoder.finish()?; - - let total_length = Header::LENGTH + let total_length = Checked::new(Header::LENGTH) + std::mem::size_of::() + std::mem::size_of::() + std::mem::size_of::() + compressed_bytes.len(); + let max_len = + Checked::try_from(max_message_size_bytes.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE_BYTES))?; + if total_length > max_len { + return Err(ErrorKind::InvalidArgument { + message: format!("Message length {} over maximum {}", total_length, max_len), + } + .into()); + } + let header = Header { - length: total_length as i32, - request_id: self.request_id.unwrap_or_else(super::util::next_request_id), + length: total_length.try_into()?, + request_id: self.request_id.unwrap_or_else(next_request_id), response_to: self.response_to, op_code: OpCode::Compressed, }; - // Write header header.write_to(&mut writer).await?; - // Write original (pre-compressed) opcode (always OP_MSG) writer.write_i32_le(OpCode::Message as i32).await?; - // Write uncompressed size - writer.write_i32_le(uncompressed_len as i32).await?; - // Write compressor id - writer.write_u8(compressor_id).await?; - // Write compressed message + writer.write_i32_le(uncompressed_len.try_into()?).await?; + writer.write_u8(compressor.id()).await?; writer.write_all(compressed_bytes.as_slice()).await?; writer.flush().await?; Ok(()) } + + fn get_sections_bytes(&self) -> Result> { + let mut sections = Vec::new(); + + // Payload type 0 + sections.push(0); + sections.extend(self.document_payload.as_bytes()); + + for document_sequence in &self.document_sequences { + // Payload type 1 + sections.push(1); + + let identifier_bytes = document_sequence.identifier.as_bytes(); + + let documents_size = document_sequence + .documents + .iter() + .fold(0, |running_size, document| { + running_size + document.as_bytes().len() + }); + + // Size bytes + identifier bytes + null-terminator byte + document bytes + let size = Checked::new(4) + identifier_bytes.len() + 1 + documents_size; + sections.extend(size.try_into::()?.to_le_bytes()); + + sections.extend(identifier_bytes); + sections.push(0); + + for document in &document_sequence.documents { + sections.extend(document.as_bytes()); + } + } + + Ok(sections) + } } const DEFAULT_MAX_MESSAGE_SIZE_BYTES: i32 = 48 * 1024 * 1024; @@ -327,83 +416,55 @@ bitflags! { /// Represents a section as defined by the OP_MSG spec. #[derive(Debug)] -pub(crate) enum MessageSection { - Document(Vec), - Sequence { - size: i32, - identifier: String, - documents: Vec>, - }, +enum MessageSection { + Document(RawDocumentBuf), + Sequence(DocumentSequence), } impl MessageSection { /// Reads bytes from `reader` and deserializes them into a MessageSection. fn read(reader: &mut R) -> Result { + #[cfg(not(feature = "bson-3"))] + use crate::bson_compat::RawDocumentBufExt as _; + let payload_type = reader.read_u8_sync()?; if payload_type == 0 { - return Ok(MessageSection::Document(bson_util::read_document_bytes( - reader, - )?)); + let bytes = bson_util::read_document_bytes(reader)?; + let document = RawDocumentBuf::decode_from_bytes(bytes)?; + return Ok(MessageSection::Document(document)); } - let size = reader.read_i32_sync()?; - let mut length_remaining = size - std::mem::size_of::() as i32; + let size = Checked::::try_from(reader.read_i32_sync()?)?; + let mut length_remaining = size - std::mem::size_of::(); let mut identifier = String::new(); - length_remaining -= reader.read_to_string(&mut identifier)? as i32; + length_remaining -= reader.read_to_string(&mut identifier)?; let mut documents = Vec::new(); let mut count_reader = SyncCountReader::new(reader); - while length_remaining > count_reader.bytes_read() as i32 { - documents.push(bson_util::read_document_bytes(&mut count_reader)?); + while length_remaining.get()? > count_reader.bytes_read() { + let bytes = bson_util::read_document_bytes(&mut count_reader)?; + let document = RawDocumentBuf::decode_from_bytes(bytes)?; + documents.push(document); } - if length_remaining != count_reader.bytes_read() as i32 { + if length_remaining.get()? != count_reader.bytes_read() { return Err(ErrorKind::InvalidResponse { message: format!( "The server indicated that the reply would be {} bytes long, but it instead \ was {}", size, - length_remaining + count_reader.bytes_read() as i32, + length_remaining + count_reader.bytes_read(), ), } .into()); } - Ok(MessageSection::Sequence { - size, + Ok(MessageSection::Sequence(DocumentSequence { identifier, documents, - }) - } - - /// Serializes the MessageSection to bytes and writes them to `writer`. - async fn write(&self, writer: &mut W) -> Result<()> { - match self { - Self::Document(doc) => { - // Write payload type. - writer.write_u8(0).await?; - writer.write_all(doc.as_slice()).await?; - } - Self::Sequence { - size, - identifier, - documents, - } => { - // Write payload type. - writer.write_u8(1).await?; - - writer.write_i32_le(*size).await?; - super::util::write_cstring(writer, identifier).await?; - - for doc in documents { - writer.write_all(doc.as_slice()).await?; - } - } - } - - Ok(()) + })) } } diff --git a/src/cmap/conn/wire/mod.rs b/src/cmap/conn/wire/mod.rs deleted file mode 100644 index cfcaaaa04..000000000 --- a/src/cmap/conn/wire/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod header; -mod message; -mod util; - -pub(crate) use self::{ - message::{Message, MessageFlags}, - util::next_request_id, -}; diff --git a/src/cmap/conn/wire/util.rs b/src/cmap/conn/wire/util.rs index c39746bcb..719a8dfa6 100644 --- a/src/cmap/conn/wire/util.rs +++ b/src/cmap/conn/wire/util.rs @@ -3,34 +3,13 @@ use std::{ sync::atomic::{AtomicI32, Ordering}, }; -use lazy_static::lazy_static; -use tokio::io::{AsyncWrite, AsyncWriteExt}; - -use crate::error::Result; - /// Closure to obtain a new, unique request ID. pub(crate) fn next_request_id() -> i32 { - lazy_static! { - static ref REQUEST_ID: AtomicI32 = AtomicI32::new(0); - } + static REQUEST_ID: AtomicI32 = AtomicI32::new(0); REQUEST_ID.fetch_add(1, Ordering::SeqCst) } -/// Serializes `string` to bytes and writes them to `writer` with a null terminator appended. -pub(super) async fn write_cstring( - writer: &mut W, - string: &str, -) -> Result<()> { - // Write the string's UTF-8 bytes. - writer.write_all(string.as_bytes()).await?; - - // Write the null terminator. - writer.write_all(&[0]).await?; - - Ok(()) -} - pub(super) struct SyncCountReader { reader: R, bytes_read: usize, diff --git a/src/cmap/connection_requester.rs b/src/cmap/connection_requester.rs index a3128308f..d2d644f90 100644 --- a/src/cmap/connection_requester.rs +++ b/src/cmap/connection_requester.rs @@ -1,6 +1,6 @@ use tokio::sync::{mpsc, oneshot}; -use super::Connection; +use super::conn::pooled::PooledConnection; use crate::{ error::{Error, Result}, runtime::{AsyncJoinHandle, WorkerHandle}, @@ -23,7 +23,7 @@ pub(super) fn channel(handle: WorkerHandle) -> (ConnectionRequester, ConnectionR /// the pool will stop servicing requests, drop its available connections, and close. #[derive(Clone, Debug)] pub(super) struct ConnectionRequester { - sender: mpsc::UnboundedSender>, + sender: mpsc::UnboundedSender, _handle: WorkerHandle, } @@ -34,26 +34,58 @@ impl ConnectionRequester { // this only errors if the receiver end is dropped, which can't happen because // we own a handle to the worker, keeping it alive. - self.sender.send(sender).unwrap(); + self.sender + .send(ConnectionRequest { + sender, + warm_pool: false, + }) + .unwrap(); // similarly, the receiver only returns an error if the sender is dropped, which // can't happen due to the handle. receiver.await.unwrap() } + + pub(super) fn weak(&self) -> WeakConnectionRequester { + WeakConnectionRequester { + sender: self.sender.clone(), + } + } +} + +/// Handle for requesting Connections from the pool. This does *not* keep the +/// pool alive. +#[derive(Clone, Debug)] +pub(super) struct WeakConnectionRequester { + sender: mpsc::UnboundedSender, +} + +impl WeakConnectionRequester { + pub(super) async fn request_warm_pool(&self) -> Option { + let (sender, receiver) = oneshot::channel(); + if self + .sender + .send(ConnectionRequest { + sender, + warm_pool: true, + }) + .is_err() + { + return None; + } + receiver.await.ok() + } } /// Receiving end of a given ConnectionRequester. #[derive(Debug)] pub(super) struct ConnectionRequestReceiver { - receiver: mpsc::UnboundedReceiver>, + receiver: mpsc::UnboundedReceiver, } impl ConnectionRequestReceiver { pub(super) async fn recv(&mut self) -> Option { - self.receiver - .recv() - .await - .map(|sender| ConnectionRequest { sender }) + self.receiver.recv().await } } @@ -61,6 +93,7 @@ impl ConnectionRequestReceiver { #[derive(Debug)] pub(super) struct ConnectionRequest { sender: oneshot::Sender, + warm_pool: bool, } impl ConnectionRequest { @@ -72,24 +105,31 @@ impl ConnectionRequest { ) -> std::result::Result<(), ConnectionRequestResult> { self.sender.send(result) } + + pub(super) fn is_warm_pool(&self) -> bool { + self.warm_pool + } } #[derive(Debug)] pub(super) enum ConnectionRequestResult { /// A connection that was already established and was simply checked out of the pool. - Pooled(Box), + Pooled(Box), /// A new connection in the process of being established. /// The handle can be awaited upon to receive the established connection. - Establishing(AsyncJoinHandle>), + Establishing(AsyncJoinHandle>), /// The request was rejected because the pool was cleared before it could /// be fulfilled. The error that caused the pool to be cleared is returned. PoolCleared(Error), + + /// The request set `warm_pool: true` and the pool has reached `min_pool_size`. + PoolWarmed, } impl ConnectionRequestResult { - pub(super) fn unwrap_pooled_connection(self) -> Connection { + pub(super) fn unwrap_pooled_connection(self) -> PooledConnection { match self { ConnectionRequestResult::Pooled(c) => *c, _ => panic!("attempted to unwrap pooled connection when was establishing"), diff --git a/src/cmap/establish.rs b/src/cmap/establish.rs new file mode 100644 index 000000000..c8c1eee06 --- /dev/null +++ b/src/cmap/establish.rs @@ -0,0 +1,200 @@ +pub(crate) mod handshake; + +use std::time::{Duration, Instant}; + +use self::handshake::{Handshaker, HandshakerOptions}; +use super::{ + conn::{ + pooled::PooledConnection, + ConnectionGeneration, + LoadBalancedGeneration, + PendingConnection, + }, + Connection, + PoolGeneration, +}; +use crate::{ + client::{ + auth::Credential, + options::{ClientOptions, ServerAddress, TlsOptions}, + }, + error::{Error as MongoError, ErrorKind, Result}, + hello::HelloReply, + runtime::{self, stream::DEFAULT_CONNECT_TIMEOUT, AsyncStream, TlsConfig}, + sdam::HandshakePhase, +}; + +/// Contains the logic to establish a connection, including handshaking, authenticating, and +/// potentially more. +#[derive(Clone)] +pub(crate) struct ConnectionEstablisher { + /// Contains the logic for handshaking a connection. + handshaker: Handshaker, + + /// Cached configuration needed to create TLS connections, if needed. + tls_config: Option, + + connect_timeout: Duration, + + #[cfg(test)] + test_patch_reply: Option)>, +} + +pub(crate) struct EstablisherOptions { + handshake_options: HandshakerOptions, + tls_options: Option, + connect_timeout: Option, + #[cfg(test)] + pub(crate) test_patch_reply: Option)>, +} + +impl From<&ClientOptions> for EstablisherOptions { + fn from(opts: &ClientOptions) -> Self { + Self { + handshake_options: HandshakerOptions::from(opts), + tls_options: opts.tls_options(), + connect_timeout: opts.connect_timeout, + #[cfg(test)] + test_patch_reply: None, + } + } +} + +impl ConnectionEstablisher { + /// Creates a new ConnectionEstablisher from the given options. + pub(crate) fn new(options: EstablisherOptions) -> Result { + let handshaker = Handshaker::new(options.handshake_options)?; + + let tls_config = if let Some(tls_options) = options.tls_options { + Some(TlsConfig::new(tls_options)?) + } else { + None + }; + + let connect_timeout = match options.connect_timeout { + Some(d) if d.is_zero() => Duration::MAX, + Some(d) => d, + None => DEFAULT_CONNECT_TIMEOUT, + }; + + Ok(Self { + handshaker, + tls_config, + connect_timeout, + #[cfg(test)] + test_patch_reply: options.test_patch_reply, + }) + } + + async fn make_stream(&self, address: ServerAddress) -> Result { + runtime::timeout( + self.connect_timeout, + AsyncStream::connect(address, self.tls_config.as_ref()), + ) + .await? + } + + /// Establishes a connection. + pub(crate) async fn establish_connection( + &self, + mut pending_connection: PendingConnection, + credential: Option<&Credential>, + ) -> std::result::Result { + let pool_gen = pending_connection.generation.clone(); + let address = pending_connection.address.clone(); + let cancellation_receiver = pending_connection.cancellation_receiver.take(); + + let stream = self + .make_stream(address) + .await + .map_err(|e| EstablishError::pre_hello(e, pool_gen.clone()))?; + + let mut connection = PooledConnection::new(pending_connection, stream); + #[allow(unused_mut)] + let mut handshake_result = self + .handshaker + .handshake(&mut connection, credential, cancellation_receiver) + .await; + #[cfg(test)] + if let Some(patch) = self.test_patch_reply { + patch(&mut handshake_result); + } + let handshake_result = handshake_result; + + // If the handshake response had a `serviceId` field, this is a connection to a load + // balancer and must derive its generation from the service_generations map. + match (&pool_gen, connection.service_id()) { + (PoolGeneration::Normal(_), _) => {} + (PoolGeneration::LoadBalanced(gen_map), Some(service_id)) => { + connection.generation = LoadBalancedGeneration { + generation: *gen_map.get(&service_id).unwrap_or(&0), + service_id, + } + .into(); + } + (PoolGeneration::LoadBalanced(_), None) => { + // If the handshake succeeded and there isn't a service id, return a special error. + // If the handshake failed, just return the error from that instead. + if handshake_result.is_ok() { + return Err(EstablishError::post_hello( + ErrorKind::IncompatibleServer { + message: "Driver attempted to initialize in load balancing mode, but \ + the server does not support this mode." + .to_string(), + } + .into(), + connection.generation, + )); + } + } + } + + handshake_result.map_err(|e| { + if connection.stream_description().is_err() { + EstablishError::pre_hello(e, pool_gen) + } else { + EstablishError::post_hello(e, connection.generation) + } + })?; + + Ok(connection) + } + + /// Establishes a monitoring connection. + pub(crate) async fn establish_monitoring_connection( + &self, + address: ServerAddress, + id: u32, + ) -> Result<(Connection, HelloReply)> { + let stream = self.make_stream(address.clone()).await?; + let mut connection = Connection::new(address, stream, id, Instant::now()); + + let hello_reply = self + .handshaker + .handshake(&mut connection, None, None) + .await?; + + Ok((connection, hello_reply)) + } +} + +#[derive(Debug, Clone)] +pub(crate) struct EstablishError { + pub(crate) cause: MongoError, + pub(crate) handshake_phase: HandshakePhase, +} + +impl EstablishError { + fn pre_hello(cause: MongoError, generation: PoolGeneration) -> Self { + Self { + cause, + handshake_phase: HandshakePhase::PreHello { generation }, + } + } + fn post_hello(cause: MongoError, generation: ConnectionGeneration) -> Self { + Self { + cause, + handshake_phase: HandshakePhase::PostHello { generation }, + } + } +} diff --git a/src/cmap/establish/handshake.rs b/src/cmap/establish/handshake.rs new file mode 100644 index 000000000..56e21a2c3 --- /dev/null +++ b/src/cmap/establish/handshake.rs @@ -0,0 +1,582 @@ +#[cfg(test)] +mod test; + +use std::env; + +use crate::{ + bson::{rawdoc, RawBson, RawDocumentBuf}, + bson_compat::cstr, + options::{AuthOptions, ClientOptions}, +}; +use once_cell::sync::Lazy; +use tokio::sync::broadcast; + +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +use crate::options::Compressor; +use crate::{ + client::auth::ClientFirst, + cmap::{Command, Connection, StreamDescription}, + error::Result, + hello::{hello_command, run_hello, HelloReply}, + options::{AuthMechanism, Credential, DriverInfo, ServerApi}, +}; + +#[cfg(not(feature = "sync"))] +const RUNTIME_NAME: &str = "tokio"; + +#[cfg(feature = "sync")] +const RUNTIME_NAME: &str = "sync (with tokio)"; + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct ClientMetadata { + pub(crate) application: Option, + pub(crate) driver: DriverMetadata, + pub(crate) os: OsMetadata, + pub(crate) platform: String, + pub(crate) env: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct AppMetadata { + pub(crate) name: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct DriverMetadata { + pub(crate) name: String, + pub(crate) version: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct OsMetadata { + pub(crate) os_type: String, + pub(crate) name: Option, + pub(crate) architecture: Option, + pub(crate) version: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct RuntimeEnvironment { + pub(crate) name: Option, + pub(crate) runtime: Option, + pub(crate) timeout_sec: Option, + pub(crate) memory_mb: Option, + pub(crate) region: Option, + pub(crate) url: Option, + pub(crate) container: Option, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum FaasEnvironmentName { + AwsLambda, + AzureFunc, + GcpFunc, + Vercel, +} + +impl From<&ClientMetadata> for RawDocumentBuf { + fn from(metadata: &ClientMetadata) -> Self { + let mut metadata_doc = RawDocumentBuf::new(); + + if let Some(application) = &metadata.application { + metadata_doc.append( + cstr!("application"), + rawdoc! { "name": application.name.as_str() }, + ); + } + + metadata_doc.append( + cstr!("driver"), + rawdoc! { + "name": metadata.driver.name.as_str(), + "version": metadata.driver.version.as_str(), + }, + ); + + let raw_os: RawBson = (&metadata.os).into(); + metadata_doc.append(cstr!("os"), raw_os); + metadata_doc.append(cstr!("platform"), metadata.platform.as_str()); + + if let Some(env) = &metadata.env { + let raw_env: RawBson = env.into(); + metadata_doc.append(cstr!("env"), raw_env); + } + + metadata_doc + } +} + +impl From<&OsMetadata> for RawBson { + fn from(metadata: &OsMetadata) -> Self { + let mut doc = rawdoc! { "type": metadata.os_type.as_str() }; + + if let Some(name) = &metadata.name { + doc.append(cstr!("name"), name.as_str()); + } + + if let Some(arch) = &metadata.architecture { + doc.append(cstr!("architecture"), arch.as_str()); + } + + if let Some(version) = &metadata.version { + doc.append(cstr!("version"), version.as_str()); + } + + RawBson::Document(doc) + } +} + +impl From<&RuntimeEnvironment> for RawBson { + fn from(env: &RuntimeEnvironment) -> Self { + let RuntimeEnvironment { + name, + runtime, + timeout_sec, + memory_mb, + region, + url, + container, + } = env; + let mut out = rawdoc! {}; + if let Some(name) = name { + out.append(cstr!("name"), name.name()); + } + if let Some(rt) = runtime { + out.append(cstr!("runtime"), rt.as_str()); + } + if let Some(t) = timeout_sec { + out.append(cstr!("timeout_sec"), *t); + } + if let Some(m) = memory_mb { + out.append(cstr!("memory_mb"), *m); + } + if let Some(r) = region { + out.append(cstr!("region"), r.as_str()); + } + if let Some(u) = url { + out.append(cstr!("url"), u.as_str()); + } + if let Some(c) = container { + out.append(cstr!("container"), c.clone()); + } + RawBson::Document(out) + } +} + +impl RuntimeEnvironment { + pub(crate) const UNSET: Self = RuntimeEnvironment { + name: None, + runtime: None, + timeout_sec: None, + memory_mb: None, + region: None, + url: None, + container: None, + }; + + fn new() -> Option { + let mut out = Self::UNSET; + if let Some(name) = FaasEnvironmentName::new() { + out.name = Some(name); + match name { + FaasEnvironmentName::AwsLambda => { + out.runtime = env::var("AWS_EXECUTION_ENV").ok(); + out.region = env::var("AWS_REGION").ok(); + out.memory_mb = env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") + .ok() + .and_then(|s| s.parse().ok()); + } + FaasEnvironmentName::AzureFunc => { + out.runtime = env::var("FUNCTIONS_WORKER_RUNTIME").ok(); + } + FaasEnvironmentName::GcpFunc => { + out.memory_mb = env::var("FUNCTION_MEMORY_MB") + .ok() + .and_then(|s| s.parse().ok()); + out.timeout_sec = env::var("FUNCTION_TIMEOUT_SEC") + .ok() + .and_then(|s| s.parse().ok()); + out.region = env::var("FUNCTION_REGION").ok(); + } + FaasEnvironmentName::Vercel => { + out.region = env::var("VERCEL_REGION").ok(); + } + } + } + let mut container = rawdoc! {}; + if std::path::Path::new("/.dockerenv").exists() { + container.append(cstr!("runtime"), "docker"); + } + if var_set("KUBERNETES_SERVICE_HOST") { + container.append(cstr!("orchestrator"), "kubernetes"); + } + if !container.is_empty() { + out.container = Some(container); + } + if out == Self::UNSET { + None + } else { + Some(out) + } + } +} + +fn var_set(name: &str) -> bool { + env::var_os(name).is_some_and(|v| !v.is_empty()) +} + +impl FaasEnvironmentName { + pub(crate) fn new() -> Option { + use FaasEnvironmentName::*; + let mut found: Option = None; + let lambda_env = env::var_os("AWS_EXECUTION_ENV") + .is_some_and(|v| v.to_string_lossy().starts_with("AWS_Lambda_")); + if lambda_env || var_set("AWS_LAMBDA_RUNTIME_API") { + found = Some(AwsLambda); + } + if var_set("VERCEL") { + // Vercel takes precedence over AwsLambda. + found = Some(Vercel); + } + // Any other conflict is treated as unset. + if var_set("FUNCTIONS_WORKER_RUNTIME") { + match found { + None => found = Some(AzureFunc), + _ => return None, + } + } + if var_set("K_SERVICE") || var_set("FUNCTION_NAME") { + match found { + None => found = Some(GcpFunc), + _ => return None, + } + } + found + } + + fn name(&self) -> &'static str { + use FaasEnvironmentName::*; + match self { + AwsLambda => "aws.lambda", + AzureFunc => "azure.func", + GcpFunc => "gcp.func", + Vercel => "vercel", + } + } +} + +/// Contains the basic handshake information that can be statically determined. This document +/// (potentially with additional fields added) can be cloned and put in the `client` field of +/// the `hello` or legacy hello command. +pub(crate) static BASE_CLIENT_METADATA: Lazy = Lazy::new(|| ClientMetadata { + application: None, + driver: DriverMetadata { + name: "mongo-rust-driver".into(), + version: env!("CARGO_PKG_VERSION").into(), + }, + os: OsMetadata { + os_type: std::env::consts::OS.into(), + architecture: Some(std::env::consts::ARCH.into()), + name: None, + version: None, + }, + platform: format!( + "{} with {}", + rustc_version_runtime::version_meta().short_version_string, + RUNTIME_NAME + ), + env: None, +}); + +type Truncation = fn(&mut ClientMetadata); + +const METADATA_TRUNCATIONS: &[Truncation] = &[ + // clear `env.*` except `name` + |metadata| { + if let Some(env) = &mut metadata.env { + *env = RuntimeEnvironment { + name: env.name, + ..RuntimeEnvironment::UNSET + } + } + }, + // clear `os.*` except `type` + |metadata| { + metadata.os = OsMetadata { + os_type: metadata.os.os_type.clone(), + architecture: None, + name: None, + version: None, + } + }, + // clear `env` + |metadata| { + metadata.env = None; + }, + // truncate `platform` + |metadata| { + metadata.platform = rustc_version_runtime::version_meta().short_version_string; + }, +]; + +/// Contains the logic needed to handshake a connection. +#[derive(Clone, Debug)] +pub(crate) struct Handshaker { + /// The hello or legacy hello command to send when handshaking. This will always be identical + /// given the same pool options, so it can be created at the time the Handshaker is created. + command: Command, + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressors: Option>, + + metadata: ClientMetadata, + + auth_options: AuthOptions, +} + +#[cfg(test)] +#[allow(clippy::incompatible_msrv)] +pub(crate) static TEST_METADATA: std::sync::OnceLock = std::sync::OnceLock::new(); + +impl Handshaker { + /// Creates a new Handshaker. + pub(crate) fn new(options: HandshakerOptions) -> Result { + let mut metadata = BASE_CLIENT_METADATA.clone(); + + let mut command = hello_command( + options.server_api.as_ref(), + options.load_balanced.into(), + None, + None, + ); + + if let Some(app_name) = options.app_name { + metadata.application = Some(AppMetadata { name: app_name }); + } + + if let Some(driver_info) = options.driver_info { + metadata.driver.name.push('|'); + metadata.driver.name.push_str(&driver_info.name); + + if let Some(ref version) = driver_info.version { + metadata.driver.version.push('|'); + metadata.driver.version.push_str(version); + } + + if let Some(ref driver_info_platform) = driver_info.platform { + metadata.platform.push('|'); + metadata.platform.push_str(driver_info_platform); + } + } + + metadata.env = RuntimeEnvironment::new(); + + if options.load_balanced { + command.body.append(cstr!("loadBalanced"), true); + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + if let Some(ref compressors) = options.compressors { + command.body.append( + crate::bson_compat::cstr!("compression"), + crate::bson::RawArrayBuf::from_iter( + compressors.iter().map(|compressor| compressor.name()), + ), + ); + } + + Ok(Self { + command, + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressors: options.compressors, + metadata, + auth_options: options.auth_options, + }) + } + + async fn build_command( + &self, + credential: Option<&Credential>, + ) -> Result<(Command, Option)> { + let mut command = self.command.clone(); + + if let Some(cred) = credential { + cred.append_needed_mechanism_negotiation(&mut command.body); + command.target_db = cred.resolved_source().to_string(); + } + + let client_first = set_speculative_auth_info(&mut command.body, credential).await?; + + let body = &mut command.body; + let body_size = body.as_bytes().len(); + let mut metadata = self.metadata.clone(); + let mut meta_doc: RawDocumentBuf = (&metadata).into(); + const OVERHEAD: usize = 1 /* tag */ + 6 /* name */ + 1 /* null */; + for trunc_fn in METADATA_TRUNCATIONS { + if body_size + OVERHEAD + meta_doc.as_bytes().len() <= MAX_HELLO_SIZE { + break; + } + trunc_fn(&mut metadata); + meta_doc = (&metadata).into(); + } + #[cfg(test)] + #[allow(clippy::incompatible_msrv)] + let _ = TEST_METADATA.set(metadata); + body.append(cstr!("client"), meta_doc); + + Ok((command, client_first)) + } + + /// Handshakes a connection. + pub(crate) async fn handshake( + &self, + conn: &mut Connection, + credential: Option<&Credential>, + cancellation_receiver: Option>, + ) -> Result { + let (command, client_first) = self.build_command(credential).await?; + let mut hello_reply = run_hello(conn, command, cancellation_receiver).await?; + + conn.stream_description = Some(StreamDescription::from_hello_reply(&hello_reply)); + + // Record the client's message and the server's response from speculative authentication if + // the server did send a response. + let first_round = client_first.and_then(|client_first| { + hello_reply + .command_response + .speculative_authenticate + .take() + .map(|server_first| client_first.into_first_round(server_first)) + }); + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + if let (Some(server_compressors), Some(client_compressors)) = ( + hello_reply.command_response.compressors.as_ref(), + self.compressors.as_ref(), + ) { + // Use the first compressor in the user's list that is also supported by the server. + if let Some(compressor) = client_compressors.iter().find(|client_compressor| { + server_compressors + .iter() + .any(|server_compressor| client_compressor.name() == server_compressor) + }) { + conn.compressor = Some(compressor.clone()); + } + } + + conn.server_id = hello_reply.command_response.connection_id; + + if let Some(credential) = credential { + credential + .authenticate_stream(conn, first_round, &self.auth_options) + .await? + } + + Ok(hello_reply) + } +} + +#[derive(Debug)] +pub(crate) struct HandshakerOptions { + /// The application name specified by the user. This is sent to the server as part of the + /// handshake that each connection makes when it's created. + pub(crate) app_name: Option, + + /// The compressors specified by the user. This list is sent to the server and the server + /// replies with the subset of the compressors it supports. + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + pub(crate) compressors: Option>, + + /// Extra information to append to the driver version in the metadata of the handshake with the + /// server. This should be used by libraries wrapping the driver, e.g. ODMs. + pub(crate) driver_info: Option, + + /// The declared API version. + /// + /// The default value is to have no declared API version + pub(crate) server_api: Option, + + /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. + pub(crate) load_balanced: bool, + + /// Auxiliary data for authentication mechanisms. + pub(crate) auth_options: AuthOptions, +} + +impl From<&ClientOptions> for HandshakerOptions { + fn from(opts: &ClientOptions) -> Self { + Self { + app_name: opts.app_name.clone(), + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + compressors: opts.compressors.clone(), + driver_info: opts.driver_info.clone(), + server_api: opts.server_api.clone(), + load_balanced: opts.load_balanced.unwrap_or(false), + auth_options: AuthOptions::from(opts), + } + } +} + +/// Updates the handshake command document with the speculative authentication info. +async fn set_speculative_auth_info( + command: &mut RawDocumentBuf, + credential: Option<&Credential>, +) -> Result> { + let credential = match credential { + Some(credential) => credential, + None => return Ok(None), + }; + + // The spec indicates that SCRAM-SHA-256 should be assumed for speculative authentication if no + // mechanism is provided. This doesn't cause issues with servers where SCRAM-SHA-256 is not the + // default due to them being too old to support speculative authentication at all. + let auth_mechanism = credential + .mechanism + .as_ref() + .unwrap_or(&AuthMechanism::ScramSha256); + + let client_first = match auth_mechanism + .build_speculative_client_first(credential) + .await? + { + Some(client_first) => client_first, + None => return Ok(None), + }; + + command.append( + cstr!("speculativeAuthenticate"), + client_first.to_document()?, + ); + + Ok(Some(client_first)) +} + +const MAX_HELLO_SIZE: usize = 512; diff --git a/src/cmap/establish/handshake/mod.rs b/src/cmap/establish/handshake/mod.rs deleted file mode 100644 index d85bc5414..000000000 --- a/src/cmap/establish/handshake/mod.rs +++ /dev/null @@ -1,533 +0,0 @@ -#[cfg(test)] -mod test; - -use std::env; - -use lazy_static::lazy_static; - -use crate::{ - bson::{doc, Bson, Document}, - client::auth::ClientFirst, - cmap::{Command, Connection, StreamDescription}, - compression::Compressor, - error::Result, - hello::{hello_command, run_hello, HelloReply}, - options::{AuthMechanism, Credential, DriverInfo, ServerApi}, -}; - -#[cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] -const RUNTIME_NAME: &str = "tokio"; - -#[cfg(all(feature = "async-std-runtime", not(feature = "sync")))] -const RUNTIME_NAME: &str = "async-std"; - -#[cfg(feature = "sync")] -const RUNTIME_NAME: &str = "sync (with async-std)"; - -#[cfg(feature = "tokio-sync")] -const RUNTIME_NAME: &str = "sync (with tokio)"; - -#[derive(Clone, Debug)] -struct ClientMetadata { - application: Option, - driver: DriverMetadata, - os: OsMetadata, - platform: String, - env: Option, -} - -#[derive(Clone, Debug)] -struct AppMetadata { - name: String, -} - -#[derive(Clone, Debug)] -struct DriverMetadata { - name: String, - version: String, -} - -#[derive(Clone, Debug)] -struct OsMetadata { - os_type: String, - name: Option, - architecture: Option, - version: Option, -} - -#[derive(Clone, Debug)] -struct FaasEnvironment { - name: FaasEnvironmentName, - runtime: Option, - timeout_sec: Option, - memory_mb: Option, - region: Option, - url: Option, -} - -#[derive(Copy, Clone, Debug)] -enum FaasEnvironmentName { - AwsLambda, - AzureFunc, - GcpFunc, - Vercel, -} - -impl From for Bson { - fn from(metadata: ClientMetadata) -> Self { - let mut metadata_doc = Document::new(); - - if let Some(application) = metadata.application { - metadata_doc.insert("application", doc! { "name": application.name }); - } - - metadata_doc.insert( - "driver", - doc! { - "name": metadata.driver.name, - "version": metadata.driver.version, - }, - ); - - metadata_doc.insert("os", metadata.os); - metadata_doc.insert("platform", metadata.platform); - - if let Some(env) = metadata.env { - metadata_doc.insert("env", env); - } - - Bson::Document(metadata_doc) - } -} - -impl From for Bson { - fn from(metadata: OsMetadata) -> Self { - let mut doc = doc! { "type": metadata.os_type }; - - if let Some(name) = metadata.name { - doc.insert("name", name); - } - - if let Some(arch) = metadata.architecture { - doc.insert("architecture", arch); - } - - if let Some(version) = metadata.version { - doc.insert("version", version); - } - - Bson::Document(doc) - } -} - -impl From for Bson { - fn from(env: FaasEnvironment) -> Self { - let FaasEnvironment { - name, - runtime, - timeout_sec, - memory_mb, - region, - url, - } = env; - let mut out = doc! { - "name": name.name(), - }; - if let Some(rt) = runtime { - out.insert("runtime", rt); - } - if let Some(t) = timeout_sec { - out.insert("timeout_sec", t); - } - if let Some(m) = memory_mb { - out.insert("memory_mb", m); - } - if let Some(r) = region { - out.insert("region", r); - } - if let Some(u) = url { - out.insert("url", u); - } - Bson::Document(out) - } -} - -impl FaasEnvironment { - const UNSET: Self = FaasEnvironment { - name: FaasEnvironmentName::AwsLambda, - runtime: None, - timeout_sec: None, - memory_mb: None, - region: None, - url: None, - }; - - fn new() -> Option { - let name = FaasEnvironmentName::new()?; - Some(match name { - FaasEnvironmentName::AwsLambda => { - let runtime = env::var("AWS_EXECUTION_ENV").ok(); - let region = env::var("AWS_REGION").ok(); - let memory_mb = env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") - .ok() - .and_then(|s| s.parse().ok()); - Self { - name, - runtime, - region, - memory_mb, - ..Self::UNSET - } - } - FaasEnvironmentName::AzureFunc => { - let runtime = env::var("FUNCTIONS_WORKER_RUNTIME").ok(); - Self { - name, - runtime, - ..Self::UNSET - } - } - FaasEnvironmentName::GcpFunc => { - let memory_mb = env::var("FUNCTION_MEMORY_MB") - .ok() - .and_then(|s| s.parse().ok()); - let timeout_sec = env::var("FUNCTION_TIMEOUT_SEC") - .ok() - .and_then(|s| s.parse().ok()); - let region = env::var("FUNCTION_REGION").ok(); - Self { - name, - memory_mb, - timeout_sec, - region, - ..Self::UNSET - } - } - FaasEnvironmentName::Vercel => { - let region = env::var("VERCEL_REGION").ok(); - Self { - name, - region, - ..Self::UNSET - } - } - }) - } -} - -fn var_set(name: &str) -> bool { - env::var_os(name).map_or(false, |v| !v.is_empty()) -} - -impl FaasEnvironmentName { - fn new() -> Option { - use FaasEnvironmentName::*; - let mut found: Option = None; - let lambda_env = env::var_os("AWS_EXECUTION_ENV") - .map_or(false, |v| v.to_string_lossy().starts_with("AWS_Lambda_")); - if lambda_env || var_set("AWS_LAMBDA_RUNTIME_API") { - found = Some(AwsLambda); - } - if var_set("VERCEL") { - // Vercel takes precedence over AwsLambda. - found = Some(Vercel); - } - // Any other conflict is treated as unset. - if var_set("FUNCTIONS_WORKER_RUNTIME") { - match found { - None => found = Some(AzureFunc), - _ => return None, - } - } - if var_set("K_SERVICE") || var_set("FUNCTION_NAME") { - match found { - None => found = Some(GcpFunc), - _ => return None, - } - } - found - } - - fn name(&self) -> &'static str { - use FaasEnvironmentName::*; - match self { - AwsLambda => "aws.lambda", - AzureFunc => "azure.func", - GcpFunc => "gcp.func", - Vercel => "vercel", - } - } -} - -lazy_static! { - /// Contains the basic handshake information that can be statically determined. This document - /// (potentially with additional fields added) can be cloned and put in the `client` field of - /// the `hello` or legacy hello command. - static ref BASE_CLIENT_METADATA: ClientMetadata = { - ClientMetadata { - application: None, - driver: DriverMetadata { - name: "mongo-rust-driver".into(), - version: env!("CARGO_PKG_VERSION").into(), - }, - os: OsMetadata { - os_type: std::env::consts::OS.into(), - architecture: Some(std::env::consts::ARCH.into()), - name: None, - version: None, - }, - platform: format!("{} with {}", rustc_version_runtime::version_meta().short_version_string, RUNTIME_NAME), - env: None, - } - }; -} - -type Truncation = fn(&mut ClientMetadata); - -const METADATA_TRUNCATIONS: &[Truncation] = &[ - // clear `env.*` except `name` - |metadata| { - if let Some(env) = &mut metadata.env { - *env = FaasEnvironment { - name: env.name, - ..FaasEnvironment::UNSET - } - } - }, - // clear `os.*` except `type` - |metadata| { - metadata.os = OsMetadata { - os_type: metadata.os.os_type.clone(), - architecture: None, - name: None, - version: None, - } - }, - // clear `env` - |metadata| { - metadata.env = None; - }, - // truncate `platform` - |metadata| { - metadata.platform = rustc_version_runtime::version_meta().short_version_string; - }, -]; - -/// Contains the logic needed to handshake a connection. -#[derive(Clone, Debug)] -pub(crate) struct Handshaker { - /// The hello or legacy hello command to send when handshaking. This will always be identical - /// given the same pool options, so it can be created at the time the Handshaker is created. - command: Command, - - // This field is not read without a compression feature flag turned on. - #[allow(dead_code)] - compressors: Option>, - - server_api: Option, - - metadata: ClientMetadata, - - #[cfg(feature = "aws-auth")] - http_client: crate::runtime::HttpClient, -} - -impl Handshaker { - /// Creates a new Handshaker. - pub(crate) fn new(options: HandshakerOptions) -> Self { - let mut metadata = BASE_CLIENT_METADATA.clone(); - let compressors = options.compressors; - - let mut command = hello_command( - options.server_api.as_ref(), - options.load_balanced.into(), - None, - None, - ); - - if let Some(app_name) = options.app_name { - metadata.application = Some(AppMetadata { name: app_name }); - } - - if let Some(driver_info) = options.driver_info { - metadata.driver.name.push('|'); - metadata.driver.name.push_str(&driver_info.name); - - if let Some(ref version) = driver_info.version { - metadata.driver.version.push('|'); - metadata.driver.version.push_str(version); - } - - if let Some(ref driver_info_platform) = driver_info.platform { - metadata.platform.push('|'); - metadata.platform.push_str(driver_info_platform); - } - } - - metadata.env = FaasEnvironment::new(); - - if options.load_balanced { - command.body.insert("loadBalanced", true); - } - - // Add compressors to handshake. - // See https://github.com/mongodb/specifications/blob/master/source/compression/OP_COMPRESSED.rst - if let Some(ref compressors) = compressors { - command.body.insert( - "compression", - compressors - .iter() - .map(|x| x.name()) - .collect::>(), - ); - } - - command.body.insert("client", metadata.clone()); - - Self { - command, - compressors, - server_api: options.server_api, - metadata, - #[cfg(feature = "aws-auth")] - http_client: crate::runtime::HttpClient::default(), - } - } - - /// Handshakes a connection. - pub(crate) async fn handshake( - &self, - conn: &mut Connection, - credential: Option<&Credential>, - ) -> Result { - let mut command = self.command.clone(); - - if let Some(cred) = credential { - cred.append_needed_mechanism_negotiation(&mut command.body); - command.target_db = cred.resolved_source().to_string(); - } - - let client_first = set_speculative_auth_info(&mut command.body, credential)?; - - let body = &mut command.body; - let mut trunc_meta = self.metadata.clone(); - for trunc_fn in METADATA_TRUNCATIONS { - if doc_size(body)? <= MAX_HELLO_SIZE { - break; - } - trunc_fn(&mut trunc_meta); - body.insert("client", trunc_meta.clone()); - } - - let mut hello_reply = run_hello(conn, command).await?; - - conn.stream_description = Some(StreamDescription::from_hello_reply(&hello_reply)); - - // Record the client's message and the server's response from speculative authentication if - // the server did send a response. - let first_round = client_first.and_then(|client_first| { - hello_reply - .command_response - .speculative_authenticate - .take() - .map(|server_first| client_first.into_first_round(server_first)) - }); - - // Check that the hello reply has a compressor list and unpack it - if let (Some(server_compressors), Some(client_compressors)) = ( - hello_reply.command_response.compressors.as_ref(), - self.compressors.as_ref(), - ) { - // Use the Client's first compressor choice that the server supports (comparing only on - // enum variant) - if let Some(compressor) = client_compressors - .iter() - .find(|c| server_compressors.iter().any(|x| c.name() == x)) - { - // Without a feature flag turned on, the Compressor enum is empty which causes an - // unreachable code warning. - #[allow(unreachable_code)] - // zlib compression level is already set - { - conn.compressor = Some(compressor.clone()); - } - } - } - - conn.server_id = hello_reply.command_response.connection_id; - - if let Some(credential) = credential { - credential - .authenticate_stream( - conn, - self.server_api.as_ref(), - first_round, - #[cfg(feature = "aws-auth")] - &self.http_client, - ) - .await? - } - - Ok(hello_reply) - } -} - -#[derive(Debug)] -pub(crate) struct HandshakerOptions { - /// The application name specified by the user. This is sent to the server as part of the - /// handshake that each connection makes when it's created. - pub(crate) app_name: Option, - - /// The compressors that the Client is willing to use in the order they are specified - /// in the configuration. The Client sends this list of compressors to the server. - /// The server responds with the intersection of its supported list of compressors. - pub(crate) compressors: Option>, - - /// Extra information to append to the driver version in the metadata of the handshake with the - /// server. This should be used by libraries wrapping the driver, e.g. ODMs. - pub(crate) driver_info: Option, - - /// The declared API version. - /// - /// The default value is to have no declared API version - pub(crate) server_api: Option, - - /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. - pub(crate) load_balanced: bool, -} - -/// Updates the handshake command document with the speculative authenitication info. -fn set_speculative_auth_info( - command: &mut Document, - credential: Option<&Credential>, -) -> Result> { - let credential = match credential { - Some(credential) => credential, - None => return Ok(None), - }; - - // The spec indicates that SCRAM-SHA-256 should be assumed for speculative authentication if no - // mechanism is provided. This doesn't cause issues with servers where SCRAM-SHA-256 is not the - // default due to them being too old to support speculative authentication at all. - let auth_mechanism = credential - .mechanism - .as_ref() - .unwrap_or(&AuthMechanism::ScramSha256); - - let client_first = match auth_mechanism.build_speculative_client_first(credential)? { - Some(client_first) => client_first, - None => return Ok(None), - }; - - command.insert("speculativeAuthenticate", client_first.to_document()); - - Ok(Some(client_first)) -} - -fn doc_size(d: &Document) -> Result { - let mut tmp = vec![]; - d.to_writer(&mut tmp)?; - Ok(tmp.len()) -} - -const MAX_HELLO_SIZE: usize = 512; diff --git a/src/cmap/establish/handshake/test.rs b/src/cmap/establish/handshake/test.rs index 127b9fdf5..9816d0d90 100644 --- a/src/cmap/establish/handshake/test.rs +++ b/src/cmap/establish/handshake/test.rs @@ -1,31 +1,52 @@ +use std::ops::Deref; + +use crate::{bson::rawdoc, options::AuthOptions}; + use super::Handshaker; -use crate::{bson::doc, cmap::establish::handshake::HandshakerOptions, options::DriverInfo}; +use crate::{cmap::establish::handshake::HandshakerOptions, options::DriverInfo}; -#[test] -fn metadata_no_options() { +#[tokio::test] +async fn metadata_no_options() { let handshaker = Handshaker::new(HandshakerOptions { app_name: None, + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] compressors: None, driver_info: None, server_api: None, load_balanced: false, - }); + auth_options: AuthOptions::default(), + }) + .unwrap(); - let metadata = handshaker.command.body.get_document("client").unwrap(); - assert!(!metadata.contains_key("application")); + let command = handshaker.build_command(None).await.unwrap().0; + let metadata = command.body.get_document("client").unwrap(); + assert!(!matches!(metadata.get("application"), Ok(Some(_)))); let driver = metadata.get_document("driver").unwrap(); - assert_eq!(driver.keys().collect::>(), vec!["name", "version"]); - assert_eq!(driver.get_str("name"), Ok("mongo-rust-driver")); - assert_eq!(driver.get_str("version"), Ok(env!("CARGO_PKG_VERSION"))); + assert_eq!( + driver + .iter_elements() + .map(|e| e.unwrap().key()) + .collect::>(), + vec!["name", "version"] + ); + assert_eq!(driver.get_str("name").unwrap(), "mongo-rust-driver"); + assert_eq!( + driver.get_str("version").unwrap(), + env!("CARGO_PKG_VERSION") + ); let os = metadata.get_document("os").unwrap(); - assert_eq!(os.get_str("type"), Ok(std::env::consts::OS)); - assert_eq!(os.get_str("architecture"), Ok(std::env::consts::ARCH)); + assert_eq!(os.get_str("type").unwrap(), std::env::consts::OS); + assert_eq!(os.get_str("architecture").unwrap(), std::env::consts::ARCH); } -#[test] -fn metadata_with_options() { +#[tokio::test] +async fn metadata_with_options() { let app_name = "myspace 2.0"; let name = "even better Rust driver"; let version = "the best version, of course"; @@ -38,31 +59,43 @@ fn metadata_with_options() { .version(version.to_string()) .build(), ), + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] compressors: None, server_api: None, load_balanced: false, + auth_options: AuthOptions::default(), }; - let handshaker = Handshaker::new(options); - - let metadata = handshaker.command.body.get_document("client").unwrap(); + let handshaker = Handshaker::new(options).unwrap(); + let command = handshaker.build_command(None).await.unwrap().0; + let metadata = command.body.get_document("client").unwrap(); assert_eq!( - metadata.get_document("application"), - Ok(&doc! { "name": app_name }) + metadata.get_document("application").unwrap(), + rawdoc! { "name": app_name }.deref() ); let driver = metadata.get_document("driver").unwrap(); - assert_eq!(driver.keys().collect::>(), vec!["name", "version"]); assert_eq!( - driver.get_str("name"), - Ok(format!("mongo-rust-driver|{}", name).as_str()) + driver + .iter_elements() + .map(|e| e.unwrap().key()) + .collect::>(), + vec!["name", "version"] + ); + assert_eq!( + driver.get_str("name").unwrap(), + format!("mongo-rust-driver|{}", name).as_str() ); assert_eq!( - driver.get_str("version"), - Ok(format!("{}|{}", env!("CARGO_PKG_VERSION"), version).as_str()) + driver.get_str("version").unwrap(), + format!("{}|{}", env!("CARGO_PKG_VERSION"), version).as_str() ); let os = metadata.get_document("os").unwrap(); - assert_eq!(os.get_str("type"), Ok(std::env::consts::OS)); - assert_eq!(os.get_str("architecture"), Ok(std::env::consts::ARCH)); + assert_eq!(os.get_str("type").unwrap(), std::env::consts::OS); + assert_eq!(os.get_str("architecture").unwrap(), std::env::consts::ARCH); } diff --git a/src/cmap/establish/mod.rs b/src/cmap/establish/mod.rs deleted file mode 100644 index 36285fa99..000000000 --- a/src/cmap/establish/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -pub(super) mod handshake; - -use std::time::Duration; - -use self::handshake::{Handshaker, HandshakerOptions}; -use super::{ - conn::{ConnectionGeneration, LoadBalancedGeneration, PendingConnection}, - Connection, - PoolGeneration, -}; -use crate::{ - client::{ - auth::Credential, - options::{ClientOptions, ServerAddress, TlsOptions}, - }, - error::{Error as MongoError, ErrorKind, Result}, - hello::HelloReply, - runtime::{self, stream::DEFAULT_CONNECT_TIMEOUT, AsyncStream, TlsConfig}, - sdam::HandshakePhase, -}; - -/// Contains the logic to establish a connection, including handshaking, authenticating, and -/// potentially more. -#[derive(Clone)] -pub(crate) struct ConnectionEstablisher { - /// Contains the logic for handshaking a connection. - handshaker: Handshaker, - - /// Cached configuration needed to create TLS connections, if needed. - tls_config: Option, - - connect_timeout: Duration, -} - -pub(crate) struct EstablisherOptions { - handshake_options: HandshakerOptions, - tls_options: Option, - connect_timeout: Option, -} - -impl EstablisherOptions { - pub(crate) fn from_client_options(opts: &ClientOptions) -> Self { - Self { - handshake_options: HandshakerOptions { - app_name: opts.app_name.clone(), - compressors: opts.compressors.clone(), - driver_info: opts.driver_info.clone(), - server_api: opts.server_api.clone(), - load_balanced: opts.load_balanced.unwrap_or(false), - }, - tls_options: opts.tls_options(), - connect_timeout: opts.connect_timeout, - } - } -} - -impl ConnectionEstablisher { - /// Creates a new ConnectionEstablisher from the given options. - pub(crate) fn new(options: EstablisherOptions) -> Result { - let handshaker = Handshaker::new(options.handshake_options); - - let tls_config = if let Some(tls_options) = options.tls_options { - Some(TlsConfig::new(tls_options)?) - } else { - None - }; - - let connect_timeout = match options.connect_timeout { - Some(d) if d.is_zero() => Duration::MAX, - Some(d) => d, - None => DEFAULT_CONNECT_TIMEOUT, - }; - - Ok(Self { - handshaker, - tls_config, - connect_timeout, - }) - } - - async fn make_stream(&self, address: ServerAddress) -> Result { - runtime::timeout( - self.connect_timeout, - AsyncStream::connect(address, self.tls_config.as_ref()), - ) - .await? - } - - /// Establishes a connection. - pub(super) async fn establish_connection( - &self, - pending_connection: PendingConnection, - credential: Option<&Credential>, - ) -> std::result::Result { - let pool_gen = pending_connection.generation.clone(); - let address = pending_connection.address.clone(); - - let stream = self - .make_stream(address) - .await - .map_err(|e| EstablishError::pre_hello(e, pool_gen.clone()))?; - - let mut connection = Connection::new_pooled(pending_connection, stream); - let handshake_result = self.handshaker.handshake(&mut connection, credential).await; - - // If the handshake response had a `serviceId` field, this is a connection to a load - // balancer and must derive its generation from the service_generations map. - match (&pool_gen, connection.service_id()) { - (PoolGeneration::Normal(_), _) => {} - (PoolGeneration::LoadBalanced(gen_map), Some(service_id)) => { - connection.generation = LoadBalancedGeneration { - generation: *gen_map.get(&service_id).unwrap_or(&0), - service_id, - } - .into(); - } - (PoolGeneration::LoadBalanced(_), None) => { - // If the handshake succeeded and there isn't a service id, return a special error. - // If the handshake failed, just return the error from that instead. - if handshake_result.is_ok() { - return Err(EstablishError::post_hello( - ErrorKind::IncompatibleServer { - message: "Driver attempted to initialize in load balancing mode, but \ - the server does not support this mode." - .to_string(), - } - .into(), - connection.generation, - )); - } - } - } - - handshake_result.map_err(|e| { - if connection.stream_description.is_none() { - EstablishError::pre_hello(e, pool_gen) - } else { - EstablishError::post_hello(e, connection.generation) - } - })?; - - Ok(connection) - } - - /// Establishes a monitoring connection. - pub(crate) async fn establish_monitoring_connection( - &self, - address: ServerAddress, - ) -> Result<(Connection, HelloReply)> { - let stream = self.make_stream(address.clone()).await?; - let mut connection = Connection::new_monitoring(address, stream); - - let hello_reply = self.handshaker.handshake(&mut connection, None).await?; - - Ok((connection, hello_reply)) - } -} - -#[derive(Debug, Clone)] -pub(crate) struct EstablishError { - pub(crate) cause: MongoError, - pub(crate) handshake_phase: HandshakePhase, -} - -impl EstablishError { - fn pre_hello(cause: MongoError, generation: PoolGeneration) -> Self { - Self { - cause, - handshake_phase: HandshakePhase::PreHello { generation }, - } - } - fn post_hello(cause: MongoError, generation: ConnectionGeneration) -> Self { - Self { - cause, - handshake_phase: HandshakePhase::PostHello { generation }, - } - } -} diff --git a/src/cmap/manager.rs b/src/cmap/manager.rs index e1da249a2..6ba510f60 100644 --- a/src/cmap/manager.rs +++ b/src/cmap/manager.rs @@ -1,9 +1,12 @@ use tokio::sync::mpsc; -#[cfg(test)] -use tokio::sync::oneshot; -use super::Connection; -use crate::{bson::oid::ObjectId, error::Error, runtime::AcknowledgedMessage}; +use super::conn::pooled::PooledConnection; +use crate::{ + bson::oid::ObjectId, + error::Error, + runtime::{AcknowledgedMessage, AcknowledgmentReceiver}, + sdam::BroadcastMessage, +}; pub(super) fn channel() -> (PoolManager, ManagementRequestReceiver) { let (sender, receiver) = mpsc::unbounded_channel(); @@ -16,7 +19,7 @@ pub(super) fn channel() -> (PoolManager, ManagementRequestReceiver) { /// Struct used to make management requests to the pool (e.g. checking in a connection). /// A PoolManager will NOT keep a pool from going out of scope and closing. #[derive(Clone, Debug)] -pub(super) struct PoolManager { +pub(crate) struct PoolManager { sender: mpsc::UnboundedSender, } @@ -51,9 +54,13 @@ impl PoolManager { } } - /// Check in the given connection to the pool. - /// This returns an error containing the connection if the pool has been dropped already. - pub(crate) fn check_in(&self, connection: Connection) -> std::result::Result<(), Connection> { + /// Check in the given connection to the pool. This returns an error containing the connection + /// if the pool has been dropped. The connection's state will be transitioned to checked-in upon + /// success. + pub(crate) fn check_in( + &self, + connection: PooledConnection, + ) -> std::result::Result<(), PooledConnection> { if let Err(request) = self .sender .send(PoolManagementRequest::CheckIn(Box::new(connection))) @@ -78,12 +85,10 @@ impl PoolManager { .send(PoolManagementRequest::HandleConnectionSucceeded(conn)); } - /// Create a synchronization point for the pool's worker. - #[cfg(test)] - pub(super) fn sync_worker(&self) -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - let _ = self.sender.send(PoolManagementRequest::Sync(tx)); - rx + pub(super) fn broadcast(&self, msg: BroadcastMessage) -> AcknowledgmentReceiver<()> { + let (msg, ack) = AcknowledgedMessage::package(msg); + let _ = self.sender.send(PoolManagementRequest::Broadcast(msg)); + ack } } @@ -113,7 +118,7 @@ pub(super) enum PoolManagementRequest { }, /// Check in the given connection. - CheckIn(Box), + CheckIn(Box), /// Update the pool based on the given establishment error. HandleConnectionFailed, @@ -122,13 +127,12 @@ pub(super) enum PoolManagementRequest { /// with the successful connection. HandleConnectionSucceeded(ConnectionSucceeded), - /// Synchronize the worker queue state with an external caller, i.e. a test. - #[cfg(test)] - Sync(oneshot::Sender<()>), + /// Handle a broadcast message. + Broadcast(AcknowledgedMessage), } impl PoolManagementRequest { - fn unwrap_check_in(self) -> Connection { + fn unwrap_check_in(self) -> PooledConnection { match self { PoolManagementRequest::CheckIn(conn) => *conn, _ => panic!("tried to unwrap checkin but got {:?}", self), @@ -138,7 +142,7 @@ impl PoolManagementRequest { #[derive(Debug)] pub(super) enum ConnectionSucceeded { - ForPool(Box), + ForPool(Box), Used { service_id: Option }, } diff --git a/src/cmap/mod.rs b/src/cmap/mod.rs deleted file mode 100644 index a942096df..000000000 --- a/src/cmap/mod.rs +++ /dev/null @@ -1,190 +0,0 @@ -#[cfg(test)] -pub(crate) mod test; - -pub(crate) mod conn; -mod connection_requester; -pub(crate) mod establish; -mod manager; -pub(crate) mod options; -mod status; -mod worker; - -use derivative::Derivative; -#[cfg(test)] -use tokio::sync::oneshot; - -pub use self::conn::ConnectionInfo; -pub(crate) use self::{ - conn::{Command, Connection, RawCommand, RawCommandResponse, StreamDescription}, - status::PoolGenerationSubscriber, - worker::PoolGeneration, -}; -use self::{ - connection_requester::ConnectionRequestResult, - establish::ConnectionEstablisher, - options::ConnectionPoolOptions, -}; -use crate::{ - bson::oid::ObjectId, - error::{Error, Result}, - event::cmap::{ - CmapEvent, - CmapEventEmitter, - ConnectionCheckoutFailedEvent, - ConnectionCheckoutFailedReason, - ConnectionCheckoutStartedEvent, - PoolCreatedEvent, - }, - options::ServerAddress, - sdam::TopologyUpdater, -}; -use connection_requester::ConnectionRequester; -use manager::PoolManager; -use worker::ConnectionPoolWorker; - -#[cfg(test)] -use crate::runtime::WorkerHandle; - -const DEFAULT_MAX_POOL_SIZE: u32 = 10; - -/// A pool of connections implementing the CMAP spec. -/// This type is actually a handle to task that manages the connections and is cheap to clone and -/// pass around. -#[derive(Clone, Derivative)] -#[derivative(Debug)] -pub(crate) struct ConnectionPool { - address: ServerAddress, - manager: PoolManager, - connection_requester: ConnectionRequester, - generation_subscriber: PoolGenerationSubscriber, - - #[derivative(Debug = "ignore")] - event_emitter: CmapEventEmitter, -} - -impl ConnectionPool { - pub(crate) fn new( - address: ServerAddress, - connection_establisher: ConnectionEstablisher, - server_updater: TopologyUpdater, - topology_id: ObjectId, - options: Option, - ) -> Self { - let event_handler = options - .as_ref() - .and_then(|opts| opts.cmap_event_handler.clone()); - - let event_emitter = CmapEventEmitter::new(event_handler, topology_id); - - let (manager, connection_requester, generation_subscriber) = ConnectionPoolWorker::start( - address.clone(), - connection_establisher, - server_updater, - event_emitter.clone(), - options.clone(), - ); - - event_emitter.emit_event(|| { - CmapEvent::PoolCreated(PoolCreatedEvent { - address: address.clone(), - options: options.map(|o| o.to_event_options()), - }) - }); - - Self { - address, - manager, - connection_requester, - generation_subscriber, - event_emitter, - } - } - - #[cfg(test)] - pub(crate) fn new_mocked(address: ServerAddress) -> Self { - let (manager, _) = manager::channel(); - let handle = WorkerHandle::new_mocked(); - let (connection_requester, _) = connection_requester::channel(handle); - let (_, generation_subscriber) = status::channel(PoolGeneration::normal()); - - Self { - address, - manager, - connection_requester, - generation_subscriber, - event_emitter: CmapEventEmitter::new(None, ObjectId::new()), - } - } - - /// Checks out a connection from the pool. This method will yield until this thread is at the - /// front of the wait queue, and then will block again if no available connections are in the - /// pool and the total number of connections is not less than the max pool size. - pub(crate) async fn check_out(&self) -> Result { - self.event_emitter.emit_event(|| { - ConnectionCheckoutStartedEvent { - address: self.address.clone(), - } - .into() - }); - - let response = self.connection_requester.request().await; - - let conn = match response { - ConnectionRequestResult::Pooled(c) => Ok(*c), - ConnectionRequestResult::Establishing(task) => task.await, - ConnectionRequestResult::PoolCleared(e) => { - Err(Error::pool_cleared_error(&self.address, &e)) - } - }; - - match conn { - Ok(ref conn) => { - self.event_emitter - .emit_event(|| conn.checked_out_event().into()); - } - #[cfg(feature = "tracing-unstable")] - Err(ref err) => { - self.event_emitter.emit_event(|| { - ConnectionCheckoutFailedEvent { - address: self.address.clone(), - reason: ConnectionCheckoutFailedReason::ConnectionError, - error: Some(err.clone()), - } - .into() - }); - } - #[cfg(not(feature = "tracing-unstable"))] - Err(_) => { - self.event_emitter.emit_event(|| { - ConnectionCheckoutFailedEvent { - address: self.address.clone(), - reason: ConnectionCheckoutFailedReason::ConnectionError, - } - .into() - }); - } - } - - conn - } - - /// Increments the generation of the pool. Rather than eagerly removing stale connections from - /// the pool, they are left for the background thread to clean up. - pub(crate) async fn clear(&self, cause: Error, service_id: Option) { - self.manager.clear(cause, service_id).await - } - - /// Mark the pool as "ready" as per the CMAP specification. - pub(crate) async fn mark_as_ready(&self) { - self.manager.mark_as_ready().await - } - - pub(crate) fn generation(&self) -> PoolGeneration { - self.generation_subscriber.generation() - } - - #[cfg(test)] - pub(crate) fn sync_worker(&self) -> oneshot::Receiver<()> { - self.manager.sync_worker() - } -} diff --git a/src/cmap/options.rs b/src/cmap/options.rs index afa28e48e..66b171dca 100644 --- a/src/cmap/options.rs +++ b/src/cmap/options.rs @@ -1,22 +1,25 @@ #[cfg(test)] use std::cmp::Ordering; -use std::{sync::Arc, time::Duration}; +use std::time::Duration; -use derivative::Derivative; +use derive_where::derive_where; #[cfg(test)] use serde::de::{Deserializer, Error}; use serde::Deserialize; use crate::{ - bson_util, client::auth::Credential, - event::cmap::{CmapEventHandler, ConnectionPoolOptions as EventOptions}, + event::{ + cmap::{CmapEvent, ConnectionPoolOptions as EventOptions}, + EventHandler, + }, options::ClientOptions, + serde_util, }; /// Contains the options for creating a connection pool. -#[derive(Clone, Default, Deserialize, Derivative)] -#[derivative(Debug, PartialEq)] +#[derive(Clone, Default, Deserialize)] +#[derive_where(Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub(crate) struct ConnectionPoolOptions { /// The credential to use for authenticating connections in this pool. @@ -24,9 +27,9 @@ pub(crate) struct ConnectionPoolOptions { pub(crate) credential: Option, /// Processes all events generated by the pool. - #[derivative(Debug = "ignore", PartialEq = "ignore")] + #[derive_where(skip)] #[serde(skip)] - pub(crate) cmap_event_handler: Option>, + pub(crate) cmap_event_handler: Option>, /// Interval between background thread maintenance runs (e.g. ensure minPoolSize). #[cfg(test)] @@ -39,7 +42,7 @@ pub(crate) struct ConnectionPoolOptions { /// The default is that connections will not be closed due to being idle. #[serde(rename = "maxIdleTimeMS")] #[serde(default)] - #[serde(deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis")] + #[serde(deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis")] pub(crate) max_idle_time: Option, /// The maximum number of connections that the pool can have at a given time. This includes @@ -62,6 +65,11 @@ pub(crate) struct ConnectionPoolOptions { /// Whether or not the client is connecting to a MongoDB cluster through a load balancer. pub(crate) load_balanced: Option, + + /// The maximum number of new connections that can be created concurrently. + /// + /// The default is 2. + pub(crate) max_connecting: Option, } impl ConnectionPoolOptions { @@ -77,6 +85,7 @@ impl ConnectionPoolOptions { ready: None, load_balanced: options.load_balanced, credential: options.credential.clone(), + max_connecting: options.max_connecting, } } diff --git a/src/cmap/test.rs b/src/cmap/test.rs new file mode 100644 index 000000000..c28307724 --- /dev/null +++ b/src/cmap/test.rs @@ -0,0 +1,559 @@ +pub(crate) mod event; +mod file; +mod integration; + +use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration}; + +use tokio::sync::{Mutex, RwLock}; + +use self::file::{Operation, TestFile, ThreadedOperation}; + +use crate::{ + cmap::{ + establish::{ConnectionEstablisher, EstablisherOptions}, + ConnectionPool, + ConnectionPoolOptions, + }, + error::{Error, ErrorKind, Result}, + event::cmap::{ + CmapEvent, + ConnectionCheckoutFailedReason, + ConnectionClosedReason, + ConnectionPoolOptions as EventOptions, + }, + options::TlsOptions, + runtime::{self, AsyncJoinHandle}, + sdam::{TopologyUpdater, UpdateMessage}, + test::{ + assert_matches, + eq_matches, + get_client_options, + log_uncaptured, + run_spec_test, + util::event_buffer::EventBuffer, + MatchErrExt, + Matchable, + }, +}; + +use super::conn::pooled::PooledConnection; + +const TEST_DESCRIPTIONS_TO_SKIP: &[&str] = &[ + "must destroy checked in connection if pool has been closed", + "must throw error if checkOut is called on a closed pool", + // WaitQueueTimeoutMS is not supported + "must aggressively timeout threads enqueued longer than waitQueueTimeoutMS", + "waiting on maxConnecting is limited by WaitQueueTimeoutMS", + // TODO DRIVERS-1785 remove this skip when test event order is fixed + "error during minPoolSize population clears pool", + // TODO RUST-2106: unskip this test + "Pool clear SHOULD schedule the next background thread run immediately \ + (interruptInUseConnections = false)", + // TODO RUST-1052: unskip this test and investigate flaky failure linked in ticket + "threads blocked by maxConnecting check out minPoolSize connections", +]; + +/// Many different types of CMAP events are emitted from tasks spawned in the drop +/// implementations of various types (Connections, pools, etc.). Sometimes it takes +/// a longer amount of time for these tasks to get scheduled and thus their associated +/// events to get emitted, requiring the runner to wait for a little bit before asserting +/// the events were actually fired. +/// +/// This value was purposefully chosen to be large to prevent test failures, though it is not +/// expected that the 3s timeout will regularly or ever be hit. +const EVENT_TIMEOUT: Duration = Duration::from_secs(3); + +#[derive(Debug)] +struct Executor { + description: String, + operations: Vec, + error: Option, + events: Vec, + state: Arc, + ignored_event_names: Vec, + pool_options: ConnectionPoolOptions, +} + +#[derive(Debug)] +struct State { + events: EventBuffer, + connections: RwLock>, + unlabeled_connections: Mutex>, + threads: RwLock>, + + // In order to drop the pool when performing a `close` operation, we use an `Option` so that we + // can replace it with `None`. Since none of the tests should use the pool after its closed + // (besides the ones we manually skip over), it's fine for us to `unwrap` the pool during these + // tests, as panicking is sufficient to exit any aberrant test with a failure. + pool: RwLock>, +} + +impl State { + // Counts the number of events of the given type that have occurred so far. + fn count_events(&self, event_type: &str) -> usize { + self.events + .all() + .into_iter() + .filter(|cmap_event| cmap_event.name() == event_type) + .count() + } +} + +#[derive(Debug)] +struct CmapThread { + handle: AsyncJoinHandle>, + dispatcher: tokio::sync::mpsc::UnboundedSender, +} + +impl CmapThread { + fn start(state: Arc) -> Self { + let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::(); + let handle = runtime::spawn(async move { + while let Some(operation) = receiver.recv().await { + operation.execute(state.clone()).await?; + } + Ok(()) + }); + + Self { + dispatcher: sender, + handle, + } + } + + async fn wait_until_complete(self) -> Result<()> { + // hang up dispatcher so task will complete. + drop(self.dispatcher); + self.handle.await + } +} + +impl Executor { + async fn new(test_file: TestFile) -> Self { + let events = EventBuffer::new(); + let error = test_file.error; + + let mut pool_options = test_file.pool_options.unwrap_or_default(); + pool_options.cmap_event_handler = Some(events.handler()); + + let state = State { + events, + pool: RwLock::new(None), + connections: Default::default(), + threads: Default::default(), + unlabeled_connections: Mutex::new(Default::default()), + }; + + Self { + description: test_file.description, + error, + events: test_file.events, + operations: test_file.operations, + state: Arc::new(state), + ignored_event_names: test_file.ignore, + pool_options, + } + } + + async fn execute_test(self) { + let mut event_stream = self.state.events.stream(); + + let (updater, mut receiver) = TopologyUpdater::channel(); + + let pool = ConnectionPool::new( + get_client_options().await.hosts[0].clone(), + ConnectionEstablisher::new(EstablisherOptions::from(get_client_options().await)) + .unwrap(), + updater, + crate::bson::oid::ObjectId::new(), + Some(self.pool_options), + ); + + // Mock a monitoring task responding to errors reported by the pool. + let manager = pool.manager.clone(); + runtime::spawn(async move { + while let Some(update) = receiver.recv().await { + let (update, ack) = update.into_parts(); + if let UpdateMessage::ApplicationError { error, .. } = update { + manager.clear(error, None).await; + } + ack.acknowledge(true); + } + }); + + *self.state.pool.write().await = Some(pool); + + let mut error: Option = None; + let operations = self.operations; + + println!("Executing {}", self.description); + + for operation in operations { + let err = operation.execute(self.state.clone()).await.err(); + if error.is_none() { + error = err; + } + } + + match (self.error, error) { + (Some(ref expected), None) => { + panic!("Expected {}, but no error occurred", expected.type_) + } + (None, Some(ref actual)) => panic!( + "Expected no error to occur, but the following error was returned: {:?}", + actual + ), + (None, None) | (Some(_), Some(_)) => {} + } + + let ignored_event_names = self.ignored_event_names; + let description = self.description; + let filter = |e: &CmapEvent| !ignored_event_names.iter().any(|name| e.name() == name); + for expected_event in self.events { + let actual_event = event_stream + .next_match(EVENT_TIMEOUT, filter) + .await + .unwrap_or_else(|| { + panic!( + "{}: did not receive expected event: {:?}", + description, expected_event + ) + }); + assert_matches(&actual_event, &expected_event, Some(description.as_str())); + } + + assert_eq!( + event_stream.collect_now(filter), + Vec::new(), + "{}", + description + ); + } +} + +impl Operation { + /// Execute this operation. + async fn execute(self, state: Arc) -> Result<()> { + match self { + Operation::Wait { ms } => tokio::time::sleep(Duration::from_millis(ms)).await, + Operation::WaitForThread { target } => { + state + .threads + .write() + .await + .remove(&target) + .unwrap() + .wait_until_complete() + .await? + } + Operation::WaitForEvent { + event, + count, + timeout, + } => { + let event_name = event.clone(); + let task = async move { + while state.count_events(&event) < count { + tokio::time::sleep(Duration::from_millis(100)).await; + } + }; + runtime::timeout(timeout.unwrap_or(EVENT_TIMEOUT), task) + .await + .unwrap_or_else(|_| { + panic!("waiting for {} {} event(s) timed out", count, event_name) + }); + } + Operation::CheckOut { label } => { + if let Some(pool) = state.pool.read().await.deref() { + let conn = pool.check_out().await?; + + if let Some(label) = label { + state.connections.write().await.insert(label, conn); + } else { + state.unlabeled_connections.lock().await.push(conn); + } + } + } + Operation::CheckIn { connection } => { + let mut event_stream = state.events.stream(); + let conn = state.connections.write().await.remove(&connection).unwrap(); + let id = conn.id; + // connections are checked in via tasks spawned in their drop implementation, + // they are not checked in explicitly. + drop(conn); + + // wait for event to be emitted to ensure check in has completed. + event_stream + .next_match(EVENT_TIMEOUT, |e| { + matches!(e, CmapEvent::ConnectionCheckedIn(event) if event.connection_id == id) + }) + .await + .unwrap_or_else(|| { + panic!( + "did not receive checkin event after dropping connection (id={})", + connection + ) + }); + } + Operation::Clear { + interrupt_in_use_connections, + } => { + let error = if interrupt_in_use_connections == Some(true) { + Error::network_timeout() + } else { + ErrorKind::Internal { + message: "test error".to_string(), + } + .into() + }; + + if let Some(pool) = state.pool.read().await.as_ref() { + pool.clear(error, None).await; + } + } + Operation::Ready => { + if let Some(pool) = state.pool.read().await.deref() { + pool.mark_as_ready().await; + } + } + Operation::Close => { + let mut event_stream = state.events.stream(); + + // pools are closed via their drop implementation + state.pool.write().await.take(); + + // wait for event to be emitted to ensure drop has completed. + event_stream + .next_match(EVENT_TIMEOUT, |e| matches!(e, CmapEvent::PoolClosed(_))) + .await + .expect("did not receive ConnectionPoolClosed event after closing pool"); + } + Operation::Start { target } => { + state + .threads + .write() + .await + .insert(target, CmapThread::start(state.clone())); + } + } + Ok(()) + } +} + +impl Matchable for TlsOptions { + fn content_matches(&self, expected: &TlsOptions) -> std::result::Result<(), String> { + self.allow_invalid_certificates + .matches(&expected.allow_invalid_certificates) + .prefix("allow_invalid_certificates")?; + self.ca_file_path + .as_ref() + .map(|pb| pb.display().to_string()) + .matches( + &expected + .ca_file_path + .as_ref() + .map(|pb| pb.display().to_string()), + ) + .prefix("ca_file_path")?; + self.cert_key_file_path + .as_ref() + .map(|pb| pb.display().to_string()) + .matches( + &expected + .cert_key_file_path + .as_ref() + .map(|pb| pb.display().to_string()), + ) + .prefix("cert_key_file_path")?; + Ok(()) + } +} + +impl Matchable for EventOptions { + fn content_matches(&self, expected: &EventOptions) -> std::result::Result<(), String> { + self.max_idle_time + .matches(&expected.max_idle_time) + .prefix("max_idle_time")?; + self.max_pool_size + .matches(&expected.max_pool_size) + .prefix("max_pool_size")?; + self.min_pool_size + .matches(&expected.min_pool_size) + .prefix("min_pool_size")?; + Ok(()) + } +} + +impl Matchable for CmapEvent { + fn content_matches(&self, expected: &CmapEvent) -> std::result::Result<(), String> { + match (self, expected) { + (CmapEvent::PoolCreated(actual), CmapEvent::PoolCreated(ref expected)) => { + actual.options.matches(&expected.options) + } + (CmapEvent::ConnectionCreated(actual), CmapEvent::ConnectionCreated(ref expected)) => { + actual.connection_id.matches(&expected.connection_id) + } + (CmapEvent::ConnectionReady(actual), CmapEvent::ConnectionReady(ref expected)) => { + actual.connection_id.matches(&expected.connection_id) + } + (CmapEvent::ConnectionClosed(actual), CmapEvent::ConnectionClosed(ref expected)) => { + if expected.reason != ConnectionClosedReason::Unset { + eq_matches("reason", &actual.reason, &expected.reason)?; + } + // 0 is used as a placeholder for test events that do not specify a value; the + // driver will never actually generate a connection ID with this value. + if expected.connection_id != 0 { + actual + .connection_id + .matches(&expected.connection_id) + .prefix("connection_id")?; + } + Ok(()) + } + ( + CmapEvent::ConnectionCheckedOut(actual), + CmapEvent::ConnectionCheckedOut(ref expected), + ) => actual.connection_id.matches(&expected.connection_id), + ( + CmapEvent::ConnectionCheckedIn(actual), + CmapEvent::ConnectionCheckedIn(ref expected), + ) => actual.connection_id.matches(&expected.connection_id), + ( + CmapEvent::ConnectionCheckoutFailed(actual), + CmapEvent::ConnectionCheckoutFailed(ref expected), + ) => { + if expected.reason != ConnectionCheckoutFailedReason::Unset { + eq_matches("reason", &actual.reason, &expected.reason)?; + } + Ok(()) + } + (CmapEvent::ConnectionCheckoutStarted(_), CmapEvent::ConnectionCheckoutStarted(_)) => { + Ok(()) + } + (CmapEvent::PoolCleared(_), CmapEvent::PoolCleared(_)) => Ok(()), + (CmapEvent::PoolReady(_), CmapEvent::PoolReady(_)) => Ok(()), + (CmapEvent::PoolClosed(_), CmapEvent::PoolClosed(_)) => Ok(()), + (actual, expected) => Err(format!("expected event {:?}, got {:?}", actual, expected)), + } + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn cmap_spec_tests() { + async fn run_cmap_spec_tests(mut test_file: TestFile) { + if TEST_DESCRIPTIONS_TO_SKIP.contains(&test_file.description.as_str()) { + return; + } + + if let Some(ref run_on) = test_file.run_on { + let mut can_run_on = false; + for requirement in run_on { + if requirement.can_run_on().await { + can_run_on = true; + } + } + if !can_run_on { + log_uncaptured("skipping due to runOn requirements"); + return; + } + } + + let mut options = get_client_options().await.clone(); + if options.load_balanced.unwrap_or(false) { + log_uncaptured(format!( + "skipping {:?} due to load balanced topology", + test_file.description + )); + return; + } + options.hosts.drain(1..); + options.direct_connection = Some(true); + let client = crate::Client::for_test().options(options).await; + + let _guard = if let Some(fail_point) = test_file.fail_point.take() { + Some(client.enable_fail_point(fail_point).await.unwrap()) + } else { + None + }; + + let executor = Executor::new(test_file).await; + executor.execute_test().await; + } + + run_spec_test( + &["connection-monitoring-and-pooling", "cmap-format"], + run_cmap_spec_tests, + ) + .await; +} + +// TODO RUST-2074: investigate why this test is flaky +// #[tokio::test(flavor = "multi_thread")] +// async fn pool_cleared_error_has_transient_transaction_error_label() { +// if !block_connection_supported().await { +// log_uncaptured( +// "skipping pool_cleared_error_has_transient_transaction_error_label: block connection +// \ unsupported", +// ); +// return; +// } +// if !transactions_supported().await { +// log_uncaptured( +// "skipping pool_cleared_error_has_transient_transaction_error_label: transactions \ +// unsupported", +// ); +// return; +// } +// if topology_is_load_balanced().await { +// log_uncaptured( +// "skipping pool_cleared_error_has_transient_transaction_error_label: load balanced \ +// topology", +// ); +// } + +// let app_name = "pool_cleared_error_has_transient_transaction_error_label"; + +// let mut client_options = get_client_options().await.clone(); +// if topology_is_sharded().await { +// client_options.hosts.drain(1..); +// } +// client_options.connect_timeout = Some(Duration::from_millis(500)); +// client_options.heartbeat_freq = Some(Duration::from_millis(500)); +// client_options.app_name = Some(app_name.to_string()); +// let client = Client::for_test() +// .options(client_options) +// .monitor_events() +// .await; + +// let mut session = client.start_session().await.unwrap(); +// session.start_transaction().await.unwrap(); + +// let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)) +// .block_connection(Duration::from_secs(15)) +// .app_name(app_name); +// let _guard = client.enable_fail_point(fail_point).await.unwrap(); + +// let insert_client = client.clone(); +// let insert_handle = tokio::spawn(async move { +// insert_client +// .database("db") +// .collection("coll") +// .insert_one(doc! { "x": 1 }) +// .session(&mut session) +// .await +// }); + +// let fail_point = FailPoint::fail_command( +// &["hello", LEGACY_HELLO_COMMAND_NAME], +// // The RTT hellos may encounter this failpoint, so use FailPointMode::AlwaysOn to ensure +// // that the server monitors hit it as well. +// FailPointMode::AlwaysOn, +// ) +// .block_connection(Duration::from_millis(1500)) +// .app_name(app_name); +// let _guard = client.enable_fail_point(fail_point).await.unwrap(); + +// let insert_error = insert_handle.await.unwrap().unwrap_err(); +// assert!(insert_error.is_pool_cleared(), "{:?}", insert_error); +// assert!(insert_error.contains_label(TRANSIENT_TRANSACTION_ERROR)); +// } diff --git a/src/cmap/test/event.rs b/src/cmap/test/event.rs index 62ddd7b07..fdcaed287 100644 --- a/src/cmap/test/event.rs +++ b/src/cmap/test/event.rs @@ -1,83 +1,8 @@ -use std::sync::{Arc, RwLock}; +use std::time::Duration; use serde::{de::Unexpected, Deserialize, Deserializer, Serialize}; -use crate::{event::cmap::*, options::ServerAddress, test::util::EventSubscriber}; -use tokio::sync::broadcast::error::SendError; - -#[derive(Clone, Debug)] -pub struct EventHandler { - pub(crate) events: Arc>>, - channel_sender: tokio::sync::broadcast::Sender, -} - -impl EventHandler { - pub fn new() -> Self { - let (channel_sender, _) = tokio::sync::broadcast::channel(500); - Self { - events: Default::default(), - channel_sender, - } - } - - fn handle>(&self, event: E) { - let event = event.into(); - // this only errors if no receivers are listening which isn't a concern here. - let _: std::result::Result> = - self.channel_sender.send(event.clone()); - self.events.write().unwrap().push(event); - } - - pub(crate) fn subscribe(&self) -> EventSubscriber<'_, EventHandler, CmapEvent> { - EventSubscriber::new(self, self.channel_sender.subscribe()) - } -} - -impl CmapEventHandler for EventHandler { - fn handle_pool_created_event(&self, event: PoolCreatedEvent) { - self.handle(event); - } - - fn handle_pool_ready_event(&self, event: PoolReadyEvent) { - self.handle(event); - } - - fn handle_pool_cleared_event(&self, event: PoolClearedEvent) { - self.handle(event); - } - - fn handle_pool_closed_event(&self, event: PoolClosedEvent) { - self.handle(event); - } - - fn handle_connection_created_event(&self, event: ConnectionCreatedEvent) { - self.handle(event); - } - - fn handle_connection_ready_event(&self, event: ConnectionReadyEvent) { - self.handle(event); - } - - fn handle_connection_closed_event(&self, event: ConnectionClosedEvent) { - self.handle(event); - } - - fn handle_connection_checkout_started_event(&self, event: ConnectionCheckoutStartedEvent) { - self.handle(event); - } - - fn handle_connection_checkout_failed_event(&self, event: ConnectionCheckoutFailedEvent) { - self.handle(event); - } - - fn handle_connection_checked_out_event(&self, event: ConnectionCheckedOutEvent) { - self.handle(event); - } - - fn handle_connection_checked_in_event(&self, event: ConnectionCheckedInEvent) { - self.handle(event); - } -} +use crate::{event::cmap::*, options::ServerAddress}; impl Serialize for CmapEvent { fn serialize(&self, serializer: S) -> Result @@ -145,7 +70,7 @@ impl<'de> Deserialize<'de> for CmapEvent { } impl CmapEvent { - pub fn name(&self) -> &'static str { + pub(crate) fn name(&self) -> &'static str { match self { CmapEvent::PoolCreated(_) => "ConnectionPoolCreated", CmapEvent::PoolReady(_) => "ConnectionPoolReady", @@ -160,24 +85,6 @@ impl CmapEvent { CmapEvent::ConnectionCheckedIn(_) => "ConnectionCheckedIn", } } - - // The names in drivers-atlas-testing tests are slightly different than those used in spec - // tests. - pub fn planned_maintenance_testing_name(&self) -> &'static str { - match self { - CmapEvent::PoolCreated(_) => "PoolCreatedEvent", - CmapEvent::PoolReady(_) => "PoolReadyEvent", - CmapEvent::PoolCleared(_) => "PoolClearedEvent", - CmapEvent::PoolClosed(_) => "PoolClosedEvent", - CmapEvent::ConnectionCreated(_) => "ConnectionCreatedEvent", - CmapEvent::ConnectionReady(_) => "ConnectionReadyEvent", - CmapEvent::ConnectionClosed(_) => "ConnectionClosedEvent", - CmapEvent::ConnectionCheckoutStarted(_) => "ConnectionCheckOutStartedEvent", - CmapEvent::ConnectionCheckoutFailed(_) => "ConnectionCheckOutFailedEvent", - CmapEvent::ConnectionCheckedOut(_) => "ConnectionCheckedOutEvent", - CmapEvent::ConnectionCheckedIn(_) => "ConnectionCheckedInEvent", - } - } } #[derive(Debug, Deserialize)] @@ -229,7 +136,7 @@ where #[derive(Debug, Deserialize)] struct ConnectionCheckoutFailedHelper { - pub reason: CheckoutFailedReasonHelper, + reason: Option, } #[derive(Debug, Deserialize)] @@ -248,16 +155,16 @@ where { let helper = ConnectionCheckoutFailedHelper::deserialize(deserializer)?; - // The driver doesn't have a concept of a "closed pool", instead having the pool closed when the - // pool is dropped. Because of this, the driver doesn't implement the "poolClosed" reason for a - // connection checkout failure. While we skip over the corresponding tests in our spec test - // runner, we still need to be able to deserialize the "poolClosed" reason to avoid the test - // harness panicking, so we arbitrarily map the "poolClosed" to "connectionError". let reason = match helper.reason { - CheckoutFailedReasonHelper::PoolClosed | CheckoutFailedReasonHelper::ConnectionError => { + Some(CheckoutFailedReasonHelper::ConnectionError) => { ConnectionCheckoutFailedReason::ConnectionError } - CheckoutFailedReasonHelper::Timeout => ConnectionCheckoutFailedReason::Timeout, + Some(CheckoutFailedReasonHelper::Timeout) => ConnectionCheckoutFailedReason::Timeout, + // The driver does not implement the tests that use the PoolClosed reason, so we map the + // test value to unset to allow for deserialization. + Some(CheckoutFailedReasonHelper::PoolClosed) | None => { + ConnectionCheckoutFailedReason::Unset + } }; Ok(ConnectionCheckoutFailedEvent { @@ -268,5 +175,6 @@ where reason, #[cfg(feature = "tracing-unstable")] error: None, + duration: Duration::ZERO, }) } diff --git a/src/cmap/test/file.rs b/src/cmap/test/file.rs index d8e6082d7..01ff71920 100644 --- a/src/cmap/test/file.rs +++ b/src/cmap/test/file.rs @@ -4,13 +4,12 @@ use serde::Deserialize; use super::State; use crate::{ - bson_util, cmap::options::ConnectionPoolOptions, error::Result, event::cmap::CmapEvent, - test::RunOn, + serde_util, + test::{util::fail_point::FailPoint, RunOn}, }; -use bson::Document; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -26,7 +25,7 @@ pub struct TestFile { pub(crate) events: Vec, #[serde(default)] pub ignore: Vec, - pub fail_point: Option, + pub fail_point: Option, pub(crate) run_on: Option>, } @@ -75,7 +74,7 @@ pub enum Operation { WaitForEvent { event: String, count: usize, - #[serde(deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis")] + #[serde(deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis")] #[serde(default)] timeout: Option, }, @@ -85,7 +84,11 @@ pub enum Operation { CheckIn { connection: String, }, - Clear, + #[serde(rename_all = "camelCase")] + Clear { + #[serde(default)] + interrupt_in_use_connections: Option, + }, Close, Ready, } diff --git a/src/cmap/test/integration.rs b/src/cmap/test/integration.rs index ecfc24c6e..94392305f 100644 --- a/src/cmap/test/integration.rs +++ b/src/cmap/test/integration.rs @@ -1,7 +1,7 @@ +use crate::bson::rawdoc; use serde::Deserialize; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; -use super::{event::EventHandler, EVENT_TIMEOUT}; +use super::EVENT_TIMEOUT; use crate::{ bson::{doc, Document}, cmap::{ @@ -10,24 +10,26 @@ use crate::{ Command, ConnectionPool, }, - event::cmap::{CmapEvent, CmapEventHandler, ConnectionClosedReason}, + event::cmap::{CmapEvent, ConnectionClosedReason}, hello::LEGACY_HELLO_COMMAND_NAME, operation::CommandResponse, runtime, sdam::TopologyUpdater, selection_criteria::ReadPreference, test::{ + block_connection_supported, + fail_command_supported, + get_client_options, log_uncaptured, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, + topology_is_load_balanced, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, }, + Client, }; -use semver::VersionReq; -use std::{sync::Arc, time::Duration}; +use std::time::Duration; #[derive(Debug, Deserialize)] struct ListDatabasesResponse { @@ -39,26 +41,22 @@ struct DatabaseEntry { name: String, } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn acquire_connection_and_send_command() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client_options = CLIENT_OPTIONS.get().await.clone(); + let client_options = get_client_options().await.clone(); let mut pool_options = ConnectionPoolOptions::from_client_options(&client_options); pool_options.ready = Some(true); let pool = ConnectionPool::new( client_options.hosts[0].clone(), - ConnectionEstablisher::new(EstablisherOptions::from_client_options(&client_options)) - .unwrap(), + ConnectionEstablisher::new(EstablisherOptions::from(&client_options)).unwrap(), TopologyUpdater::channel().0, - bson::oid::ObjectId::new(), + crate::bson::oid::ObjectId::new(), Some(pool_options), ); let mut connection = pool.check_out().await.unwrap(); - let body = doc! { "listDatabases": 1 }; + let body = rawdoc! { "listDatabases": 1 }; let read_pref = ReadPreference::PrimaryPreferred { options: Default::default(), }; @@ -68,12 +66,13 @@ async fn acquire_connection_and_send_command() { cmd.set_server_api(server_api); } - let response = connection.send_command(cmd, None).await.unwrap(); + let response = connection.send_message(cmd).await.unwrap(); let doc_response: CommandResponse = response.body().unwrap(); assert!(doc_response.is_success()); - let response: ListDatabasesResponse = bson::from_document(doc_response.body).unwrap(); + let response: ListDatabasesResponse = + crate::bson_compat::deserialize_from_document(doc_response.body).unwrap(); let names: Vec<_> = response .databases @@ -85,12 +84,16 @@ async fn acquire_connection_and_send_command() { assert!(names.iter().any(|name| name == "config")); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn concurrent_connections() { - let _guard = LOCK.run_exclusively().await; + if !block_connection_supported().await { + log_uncaptured( + "skipping concurrent_connections test due to server not supporting block connection", + ); + return; + } - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); if options.load_balanced.unwrap_or(false) { log_uncaptured("skipping concurrent_connections test due to load-balanced topology"); return; @@ -98,15 +101,7 @@ async fn concurrent_connections() { options.direct_connection = Some(true); options.hosts.drain(1..); - let client = TestClient::with_options(Some(options)).await; - let version = VersionReq::parse(">= 4.2.9").unwrap(); - // blockConnection failpoint option only supported in 4.2.9+. - if !version.matches(&client.server_version) { - log_uncaptured( - "skipping concurrent_connections test due to server not supporting failpoint option", - ); - return; - } + let client = Client::for_test().options(options).await; // stall creating connections for a while let failpoint = doc! { @@ -116,23 +111,21 @@ async fn concurrent_connections() { }; client .database("admin") - .run_command(failpoint, None) + .run_command(failpoint) .await .expect("failpoint should succeed"); - let handler = Arc::new(EventHandler::new()); - let client_options = CLIENT_OPTIONS.get().await.clone(); + let buffer = EventBuffer::::new(); + let client_options = get_client_options().await.clone(); let mut options = ConnectionPoolOptions::from_client_options(&client_options); - options.cmap_event_handler = - Some(handler.clone() as Arc); + options.cmap_event_handler = Some(buffer.handler()); options.ready = Some(true); let pool = ConnectionPool::new( - CLIENT_OPTIONS.get().await.hosts[0].clone(), - ConnectionEstablisher::new(EstablisherOptions::from_client_options(&client_options)) - .unwrap(), + get_client_options().await.hosts[0].clone(), + ConnectionEstablisher::new(EstablisherOptions::from(&client_options)).unwrap(), TopologyUpdater::channel().0, - bson::oid::ObjectId::new(), + crate::bson::oid::ObjectId::new(), Some(options), ); @@ -146,7 +139,7 @@ async fn concurrent_connections() { { // ensure all three ConnectionCreatedEvents were emitted before one ConnectionReadyEvent. - let events = handler.events.read().unwrap(); + let events = buffer.all(); let mut consecutive_creations = 0; for event in events.iter() { match event { @@ -167,34 +160,22 @@ async fn concurrent_connections() { // clear the fail point client .database("admin") - .run_command( - doc! { "configureFailPoint": "failCommand", "mode": "off" }, - None, - ) + .run_command(doc! { "configureFailPoint": "failCommand", "mode": "off" }) .await .expect("disabling fail point should succeed"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] #[function_name::named] -async fn connection_error_during_establishment() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - if client_options.load_balanced.unwrap_or(false) { +async fn connection_error_during_establishment() { + if topology_is_load_balanced().await { log_uncaptured( "skipping connection_error_during_establishment test due to load-balanced topology", ); return; } - client_options.heartbeat_freq = Duration::from_secs(300).into(); // high so that monitors dont trip failpoint - client_options.hosts.drain(1..); - client_options.direct_connection = Some(true); - client_options.repl_set_name = None; - - let client = TestClient::with_options(Some(client_options.clone())).await; - if !client.supports_fail_command() { + if !fail_command_supported().await { log_uncaptured(format!( "skipping {} due to failCommand not being supported", function_name!() @@ -202,34 +183,40 @@ async fn connection_error_during_establishment() { return; } - let options = FailCommandOptions::builder().error_code(1234).build(); - let failpoint = FailPoint::fail_command( + let mut client_options = get_client_options().await.clone(); + client_options.heartbeat_freq = Duration::from_secs(300).into(); // high so that monitors dont trip failpoint + client_options.hosts.drain(1..); + client_options.direct_connection = Some(true); + client_options.repl_set_name = None; + + let client = Client::for_test().options(client_options.clone()).await; + + let fail_point = FailPoint::fail_command( &[LEGACY_HELLO_COMMAND_NAME, "hello"], FailPointMode::Times(10), - Some(options), - ); - let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap(); + ) + .error_code(1234); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); - let handler = Arc::new(EventHandler::new()); - let mut subscriber = handler.subscribe(); + let buffer = EventBuffer::::new(); + + let mut event_stream = buffer.stream(); let mut options = ConnectionPoolOptions::from_client_options(&client_options); options.ready = Some(true); - options.cmap_event_handler = - Some(handler.clone() as Arc); + options.cmap_event_handler = Some(buffer.handler()); let pool = ConnectionPool::new( client_options.hosts[0].clone(), - ConnectionEstablisher::new(EstablisherOptions::from_client_options(&client_options)) - .unwrap(), + ConnectionEstablisher::new(EstablisherOptions::from(&client_options)).unwrap(), TopologyUpdater::channel().0, - bson::oid::ObjectId::new(), + crate::bson::oid::ObjectId::new(), Some(options), ); pool.check_out().await.expect_err("check out should fail"); - subscriber - .wait_for_event(EVENT_TIMEOUT, |e| match e { + event_stream + .next_match(EVENT_TIMEOUT, |e| match e { CmapEvent::ConnectionClosed(event) => { event.connection_id == 1 && event.reason == ConnectionClosedReason::Error } @@ -239,20 +226,11 @@ async fn connection_error_during_establishment() { .expect("closed event with error reason should have been seen"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] #[function_name::named] -async fn connection_error_during_operation() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - let mut options = CLIENT_OPTIONS.get().await.clone(); - let handler = Arc::new(EventHandler::new()); - options.cmap_event_handler = Some(handler.clone() as Arc); - options.hosts.drain(1..); - options.max_pool_size = Some(1); - - let client = TestClient::with_options(options).await; - if !client.supports_fail_command() { +async fn connection_error_during_operation() { + if !fail_command_supported().await { log_uncaptured(format!( "skipping {} due to failCommand not being supported", function_name!() @@ -260,20 +238,28 @@ async fn connection_error_during_operation() { return; } - let options = FailCommandOptions::builder().close_connection(true).build(); - let failpoint = FailPoint::fail_command(&["ping"], FailPointMode::Times(10), Some(options)); - let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap(); + let mut options = get_client_options().await.clone(); + let buffer = EventBuffer::::new(); + options.cmap_event_handler = Some(buffer.handler()); + options.hosts.drain(1..); + options.max_pool_size = Some(1); + + let client = Client::for_test().options(options).await; + + let fail_point = + FailPoint::fail_command(&["ping"], FailPointMode::Times(10)).close_connection(true); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); - let mut subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); client .database("test") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await .expect_err("ping should fail due to fail point"); - subscriber - .wait_for_event(EVENT_TIMEOUT, |e| match e { + event_stream + .next_match(EVENT_TIMEOUT, |e| match e { CmapEvent::ConnectionClosed(event) => { event.connection_id == 1 && event.reason == ConnectionClosedReason::Error } diff --git a/src/cmap/test/mod.rs b/src/cmap/test/mod.rs deleted file mode 100644 index 8772ac739..000000000 --- a/src/cmap/test/mod.rs +++ /dev/null @@ -1,490 +0,0 @@ -pub(crate) mod event; -mod file; -mod integration; - -use std::{collections::HashMap, ops::Deref, sync::Arc, time::Duration}; - -use tokio::sync::{Mutex, RwLock, RwLockWriteGuard}; - -use self::{ - event::EventHandler, - file::{Operation, TestFile, ThreadedOperation}, -}; - -use crate::{ - cmap::{ - establish::{ConnectionEstablisher, EstablisherOptions}, - Connection, - ConnectionPool, - ConnectionPoolOptions, - }, - error::{Error, ErrorKind, Result}, - event::cmap::{CmapEvent, ConnectionPoolOptions as EventOptions}, - options::TlsOptions, - runtime, - runtime::AsyncJoinHandle, - sdam::{TopologyUpdater, UpdateMessage}, - test::{ - assert_matches, - eq_matches, - log_uncaptured, - run_spec_test, - EventClient, - MatchErrExt, - Matchable, - CLIENT_OPTIONS, - LOCK, - }, -}; -use bson::doc; - -const TEST_DESCRIPTIONS_TO_SKIP: &[&str] = &[ - "must destroy checked in connection if pool has been closed", - "must throw error if checkOut is called on a closed pool", - // WaitQueueTimeoutMS is not supported - "must aggressively timeout threads enqueued longer than waitQueueTimeoutMS", - "waiting on maxConnecting is limited by WaitQueueTimeoutMS", - // TODO DRIVERS-1785 remove this skip when test event order is fixed - "error during minPoolSize population clears pool", -]; - -/// Many different types of CMAP events are emitted from tasks spawned in the drop -/// implementations of various types (Connections, pools, etc.). Sometimes it takes -/// a longer amount of time for these tasks to get scheduled and thus their associated -/// events to get emitted, requiring the runner to wait for a little bit before asserting -/// the events were actually fired. -/// -/// This value was purposefully chosen to be large to prevent test failures, though it is not -/// expected that the 3s timeout will regularly or ever be hit. -const EVENT_TIMEOUT: Duration = Duration::from_secs(3); - -#[derive(Debug)] -struct Executor { - description: String, - operations: Vec, - error: Option, - events: Vec, - state: Arc, - ignored_event_names: Vec, - pool_options: ConnectionPoolOptions, -} - -#[derive(Debug)] -struct State { - handler: Arc, - connections: RwLock>, - unlabeled_connections: Mutex>, - threads: RwLock>, - - // In order to drop the pool when performing a `close` operation, we use an `Option` so that we - // can replace it with `None`. Since none of the tests should use the pool after its closed - // (besides the ones we manually skip over), it's fine for us to `unwrap` the pool during these - // tests, as panicking is sufficient to exit any aberrant test with a failure. - pool: RwLock>, -} - -impl State { - // Counts the number of events of the given type that have occurred so far. - fn count_events(&self, event_type: &str) -> usize { - self.handler - .events - .read() - .unwrap() - .iter() - .filter(|cmap_event| cmap_event.name() == event_type) - .count() - } -} - -#[derive(Debug)] -struct CmapThread { - handle: AsyncJoinHandle>, - dispatcher: tokio::sync::mpsc::UnboundedSender, -} - -impl CmapThread { - fn start(state: Arc) -> Self { - let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::(); - let handle = runtime::spawn(async move { - while let Some(operation) = receiver.recv().await { - operation.execute(state.clone()).await?; - } - Ok(()) - }); - - Self { - dispatcher: sender, - handle, - } - } - - async fn wait_until_complete(self) -> Result<()> { - // hang up dispatcher so task will complete. - drop(self.dispatcher); - self.handle.await - } -} - -impl Executor { - async fn new(test_file: TestFile) -> Self { - let handler = Arc::new(EventHandler::new()); - let error = test_file.error; - - let mut pool_options = test_file.pool_options.unwrap_or_default(); - pool_options.cmap_event_handler = Some(handler.clone()); - - let state = State { - handler, - pool: RwLock::new(None), - connections: Default::default(), - threads: Default::default(), - unlabeled_connections: Mutex::new(Default::default()), - }; - - Self { - description: test_file.description, - error, - events: test_file.events, - operations: test_file.operations, - state: Arc::new(state), - ignored_event_names: test_file.ignore, - pool_options, - } - } - - async fn execute_test(self) { - let mut subscriber = self.state.handler.subscribe(); - - let (updater, mut receiver) = TopologyUpdater::channel(); - - let pool = ConnectionPool::new( - CLIENT_OPTIONS.get().await.hosts[0].clone(), - ConnectionEstablisher::new(EstablisherOptions::from_client_options( - CLIENT_OPTIONS.get().await, - )) - .unwrap(), - updater, - bson::oid::ObjectId::new(), - Some(self.pool_options), - ); - - // Mock a monitoring task responding to errors reported by the pool. - let manager = pool.manager.clone(); - runtime::execute(async move { - while let Some(update) = receiver.recv().await { - let (update, ack) = update.into_parts(); - if let UpdateMessage::ApplicationError { error, .. } = update { - manager.clear(error, None).await; - } - ack.acknowledge(true); - } - }); - - *self.state.pool.write().await = Some(pool); - - let mut error: Option = None; - let operations = self.operations; - - println!("Executing {}", self.description); - - for operation in operations { - let err = operation.execute(self.state.clone()).await.err(); - if error.is_none() { - error = err; - } - } - - match (self.error, error) { - (Some(ref expected), None) => { - panic!("Expected {}, but no error occurred", expected.type_) - } - (None, Some(ref actual)) => panic!( - "Expected no error to occur, but the following error was returned: {:?}", - actual - ), - (None, None) | (Some(_), Some(_)) => {} - } - - let ignored_event_names = self.ignored_event_names; - let description = self.description; - let filter = |e: &CmapEvent| !ignored_event_names.iter().any(|name| e.name() == name); - for expected_event in self.events { - let actual_event = subscriber - .wait_for_event(EVENT_TIMEOUT, filter) - .await - .unwrap_or_else(|| { - panic!( - "{}: did not receive expected event: {:?}", - description, expected_event - ) - }); - assert_matches(&actual_event, &expected_event, Some(description.as_str())); - } - - assert_eq!(subscriber.all(filter), Vec::new(), "{}", description); - } -} - -impl Operation { - /// Execute this operation. - async fn execute(self, state: Arc) -> Result<()> { - match self { - Operation::Wait { ms } => runtime::delay_for(Duration::from_millis(ms)).await, - Operation::WaitForThread { target } => { - state - .threads - .write() - .await - .remove(&target) - .unwrap() - .wait_until_complete() - .await? - } - Operation::WaitForEvent { - event, - count, - timeout, - } => { - let event_name = event.clone(); - let task = async move { - while state.count_events(&event) < count { - runtime::delay_for(Duration::from_millis(100)).await; - } - }; - runtime::timeout(timeout.unwrap_or(EVENT_TIMEOUT), task) - .await - .unwrap_or_else(|_| { - panic!("waiting for {} {} event(s) timed out", count, event_name) - }); - } - Operation::CheckOut { label } => { - if let Some(pool) = state.pool.read().await.deref() { - let conn = pool.check_out().await?; - - if let Some(label) = label { - state.connections.write().await.insert(label, conn); - } else { - state.unlabeled_connections.lock().await.push(conn); - } - } - } - Operation::CheckIn { connection } => { - let mut subscriber = state.handler.subscribe(); - let conn = state.connections.write().await.remove(&connection).unwrap(); - let id = conn.id; - // connections are checked in via tasks spawned in their drop implementation, - // they are not checked in explicitly. - drop(conn); - - // wait for event to be emitted to ensure check in has completed. - subscriber - .wait_for_event(EVENT_TIMEOUT, |e| { - matches!(e, CmapEvent::ConnectionCheckedIn(event) if event.connection_id == id) - }) - .await - .unwrap_or_else(|| { - panic!( - "did not receive checkin event after dropping connection (id={})", - connection - ) - }); - } - Operation::Clear => { - if let Some(pool) = state.pool.read().await.as_ref() { - pool.clear( - ErrorKind::Internal { - message: "test error".to_string(), - } - .into(), - None, - ) - .await; - } - } - Operation::Ready => { - if let Some(pool) = state.pool.read().await.deref() { - pool.mark_as_ready().await; - } - } - Operation::Close => { - let mut subscriber = state.handler.subscribe(); - - // pools are closed via their drop implementation - state.pool.write().await.take(); - - // wait for event to be emitted to ensure drop has completed. - subscriber - .wait_for_event(EVENT_TIMEOUT, |e| matches!(e, CmapEvent::PoolClosed(_))) - .await - .expect("did not receive ConnectionPoolClosed event after closing pool"); - } - Operation::Start { target } => { - state - .threads - .write() - .await - .insert(target, CmapThread::start(state.clone())); - } - } - Ok(()) - } -} - -impl Matchable for TlsOptions { - fn content_matches(&self, expected: &TlsOptions) -> std::result::Result<(), String> { - self.allow_invalid_certificates - .matches(&expected.allow_invalid_certificates) - .prefix("allow_invalid_certificates")?; - self.ca_file_path - .as_ref() - .map(|pb| pb.display().to_string()) - .matches( - &expected - .ca_file_path - .as_ref() - .map(|pb| pb.display().to_string()), - ) - .prefix("ca_file_path")?; - self.cert_key_file_path - .as_ref() - .map(|pb| pb.display().to_string()) - .matches( - &expected - .cert_key_file_path - .as_ref() - .map(|pb| pb.display().to_string()), - ) - .prefix("cert_key_file_path")?; - Ok(()) - } -} - -impl Matchable for EventOptions { - fn content_matches(&self, expected: &EventOptions) -> std::result::Result<(), String> { - self.max_idle_time - .matches(&expected.max_idle_time) - .prefix("max_idle_time")?; - self.max_pool_size - .matches(&expected.max_pool_size) - .prefix("max_pool_size")?; - self.min_pool_size - .matches(&expected.min_pool_size) - .prefix("min_pool_size")?; - Ok(()) - } -} - -impl Matchable for CmapEvent { - fn content_matches(&self, expected: &CmapEvent) -> std::result::Result<(), String> { - match (self, expected) { - (CmapEvent::PoolCreated(actual), CmapEvent::PoolCreated(ref expected)) => { - actual.options.matches(&expected.options) - } - (CmapEvent::ConnectionCreated(actual), CmapEvent::ConnectionCreated(ref expected)) => { - actual.connection_id.matches(&expected.connection_id) - } - (CmapEvent::ConnectionReady(actual), CmapEvent::ConnectionReady(ref expected)) => { - actual.connection_id.matches(&expected.connection_id) - } - (CmapEvent::ConnectionClosed(actual), CmapEvent::ConnectionClosed(ref expected)) => { - eq_matches("reason", &actual.reason, &expected.reason)?; - actual - .connection_id - .matches(&expected.connection_id) - .prefix("connection_id")?; - Ok(()) - } - ( - CmapEvent::ConnectionCheckedOut(actual), - CmapEvent::ConnectionCheckedOut(ref expected), - ) => actual.connection_id.matches(&expected.connection_id), - ( - CmapEvent::ConnectionCheckedIn(actual), - CmapEvent::ConnectionCheckedIn(ref expected), - ) => actual.connection_id.matches(&expected.connection_id), - ( - CmapEvent::ConnectionCheckoutFailed(actual), - CmapEvent::ConnectionCheckoutFailed(ref expected), - ) => { - if actual.reason == expected.reason { - Ok(()) - } else { - Err(format!( - "expected reason {:?}, got {:?}", - expected.reason, actual.reason - )) - } - } - (CmapEvent::ConnectionCheckoutStarted(_), CmapEvent::ConnectionCheckoutStarted(_)) => { - Ok(()) - } - (CmapEvent::PoolCleared(_), CmapEvent::PoolCleared(_)) => Ok(()), - (CmapEvent::PoolReady(_), CmapEvent::PoolReady(_)) => Ok(()), - (CmapEvent::PoolClosed(_), CmapEvent::PoolClosed(_)) => Ok(()), - (actual, expected) => Err(format!("expected event {:?}, got {:?}", actual, expected)), - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn cmap_spec_tests() { - async fn run_cmap_spec_tests(test_file: TestFile) { - if TEST_DESCRIPTIONS_TO_SKIP.contains(&test_file.description.as_str()) { - return; - } - - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let mut options = CLIENT_OPTIONS.get().await.clone(); - if options.load_balanced.unwrap_or(false) { - log_uncaptured(format!( - "skipping {:?} due to load balanced topology", - test_file.description - )); - return; - } - options.hosts.drain(1..); - options.direct_connection = Some(true); - let client = EventClient::with_options(options).await; - if let Some(ref run_on) = test_file.run_on { - let can_run_on = run_on.iter().any(|run_on| run_on.can_run_on(&client)); - if !can_run_on { - log_uncaptured("skipping due to runOn requirements"); - return; - } - } - - let should_disable_fp = test_file.fail_point.is_some(); - if let Some(ref fail_point) = test_file.fail_point { - client - .database("admin") - .run_command(fail_point.clone(), None) - .await - .unwrap(); - } - - let executor = Executor::new(test_file).await; - executor.execute_test().await; - - if should_disable_fp { - client - .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "mode": "off" - }, - None, - ) - .await - .unwrap(); - } - } - - run_spec_test( - &["connection-monitoring-and-pooling", "cmap-format"], - run_cmap_spec_tests, - ) - .await; -} diff --git a/src/cmap/worker.rs b/src/cmap/worker.rs index 69d794a2c..a56ffaccf 100644 --- a/src/cmap/worker.rs +++ b/src/cmap/worker.rs @@ -1,13 +1,16 @@ +use tokio::sync::broadcast; + #[cfg(test)] use super::options::BackgroundThreadInterval; use super::{ - conn::PendingConnection, + conn::{pooled::PooledConnection, PendingConnection}, connection_requester, connection_requester::{ ConnectionRequest, ConnectionRequestReceiver, ConnectionRequestResult, ConnectionRequester, + WeakConnectionRequester, }, establish::ConnectionEstablisher, manager, @@ -15,7 +18,6 @@ use super::{ options::ConnectionPoolOptions, status, status::{PoolGenerationPublisher, PoolGenerationSubscriber}, - Connection, DEFAULT_MAX_POOL_SIZE, }; use crate::{ @@ -32,15 +34,15 @@ use crate::{ }, options::ServerAddress, runtime::{self, WorkerHandleListener}, - sdam::TopologyUpdater, + sdam::{BroadcastMessage, TopologyUpdater}, }; use std::{ collections::{HashMap, VecDeque}, - time::Duration, + time::{Duration, Instant}, }; -const MAX_CONNECTING: u32 = 2; +const DEFAULT_MAX_CONNECTING: u32 = 2; const MAINTENACE_FREQUENCY: Duration = Duration::from_millis(500); /// A worker task that manages the shared state of the pool. @@ -72,7 +74,7 @@ pub(crate) struct ConnectionPoolWorker { /// The established connections that are currently checked into the pool and awaiting usage in /// future operations. - available_connections: VecDeque, + available_connections: VecDeque, /// Contains the logic for "establishing" a connection. This includes handshaking and /// authenticating a connection when it's first created. @@ -110,6 +112,10 @@ pub(crate) struct ConnectionPoolWorker { /// sender ends of this receiver drop, this worker will be notified and drop too. handle_listener: WorkerHandleListener, + /// Sender for connection check out requests. Does not keep the worker alive the way + /// a `ConnectionRequeter` would since it doesn't hold a `WorkerHandle`. + weak_requester: WeakConnectionRequester, + /// Receiver for incoming connection check out requests. request_receiver: ConnectionRequestReceiver, @@ -129,6 +135,12 @@ pub(crate) struct ConnectionPoolWorker { /// A handle used to notify SDAM that a connection establishment error happened. This will /// allow the server to transition to Unknown and clear the pool as necessary. server_updater: TopologyUpdater, + + /// The maximum number of new connections that can be created concurrently. + max_connecting: u32, + + /// Sender used to broadcast cancellation notices to checked-out connections. + cancellation_sender: Option>, } impl ConnectionPoolWorker { @@ -153,6 +165,10 @@ impl ConnectionPoolWorker { .as_ref() .and_then(|opts| opts.max_pool_size) .unwrap_or(DEFAULT_MAX_POOL_SIZE); + let max_connecting = options + .as_ref() + .and_then(|opts| opts.max_connecting) + .unwrap_or(DEFAULT_MAX_CONNECTING); let min_pool_size = options.as_ref().and_then(|opts| opts.min_pool_size); @@ -204,6 +220,16 @@ impl ConnectionPoolWorker { let credential = options.and_then(|o| o.credential); + let cancellation_sender = if !is_load_balanced { + // There's not necessarily an upper bound on the number of messages that could exist in + // this channel; however, connections use both successfully receiving a message in the + // channel and receiving a lagged error as an indication that cancellation should occur, + // so we use an artificial bound of one message. + Some(broadcast::channel(1).0) + } else { + None + }; + let worker = ConnectionPoolWorker { address, event_emitter, @@ -218,6 +244,7 @@ impl ConnectionPoolWorker { service_connection_count: HashMap::new(), available_connections: VecDeque::new(), max_pool_size, + weak_requester: connection_requester.weak(), request_receiver, wait_queue: Default::default(), management_receiver, @@ -227,9 +254,11 @@ impl ConnectionPoolWorker { generation_publisher, maintenance_frequency, server_updater, + max_connecting, + cancellation_sender, }; - runtime::execute(async move { + runtime::spawn(async move { worker.execute().await; }); @@ -240,7 +269,8 @@ impl ConnectionPoolWorker { /// dropped. Once all handles are dropped, the pool will close any available connections and /// emit a pool closed event. async fn execute(mut self) { - let mut maintenance_interval = runtime::interval(self.maintenance_frequency); + let mut maintenance_interval = tokio::time::interval(self.maintenance_frequency); + let mut shutdown_ack = None; loop { let task = tokio::select! { @@ -304,9 +334,21 @@ impl ConnectionPoolWorker { PoolManagementRequest::HandleConnectionFailed => { self.handle_connection_failed(); } - #[cfg(test)] - PoolManagementRequest::Sync(tx) => { - let _ = tx.send(()); + PoolManagementRequest::Broadcast(msg) => { + let (msg, ack) = msg.into_parts(); + match msg { + BroadcastMessage::Shutdown => { + shutdown_ack = Some(ack); + break; + } + BroadcastMessage::FillPool => { + crate::runtime::spawn(fill_pool(self.weak_requester.clone(), ack)); + } + #[cfg(test)] + BroadcastMessage::SyncWorkers => { + ack.acknowledge(()); + } + } } }, PoolTask::Maintenance => { @@ -322,7 +364,7 @@ impl ConnectionPoolWorker { } while let Some(connection) = self.available_connections.pop_front() { - connection.close_and_drop(ConnectionClosedReason::PoolClosed); + connection.emit_closed_event(ConnectionClosedReason::PoolClosed); } self.event_emitter.emit_event(|| { @@ -331,6 +373,9 @@ impl ConnectionPoolWorker { } .into() }); + if let Some(tx) = shutdown_ack { + tx.acknowledge(()); + } } fn below_max_connections(&self) -> bool { @@ -346,34 +391,43 @@ impl ConnectionPoolWorker { return true; } - self.below_max_connections() && self.pending_connection_count < MAX_CONNECTING + self.below_max_connections() && self.pending_connection_count < self.max_connecting } fn check_out(&mut self, request: ConnectionRequest) { - // first attempt to check out an available connection - while let Some(mut conn) = self.available_connections.pop_back() { - // Close the connection if it's stale. - if conn.generation.is_stale(&self.generation) { - self.close_connection(conn, ConnectionClosedReason::Stale); - continue; + if request.is_warm_pool() { + if self.total_connection_count >= self.min_pool_size.unwrap_or(0) { + let _ = request.fulfill(ConnectionRequestResult::PoolWarmed); + return; } + } else { + // first attempt to check out an available connection + while let Some(mut conn) = self.available_connections.pop_back() { + // Close the connection if it's stale. + if conn.generation.is_stale(&self.generation) { + self.close_connection(conn, ConnectionClosedReason::Stale); + continue; + } - // Close the connection if it's idle. - if conn.is_idle(self.max_idle_time) { - self.close_connection(conn, ConnectionClosedReason::Idle); - continue; - } + // Close the connection if it's idle. + if conn.is_idle(self.max_idle_time) { + self.close_connection(conn, ConnectionClosedReason::Idle); + continue; + } - conn.mark_as_in_use(self.manager.clone()); - if let Err(request) = request.fulfill(ConnectionRequestResult::Pooled(Box::new(conn))) { - // checking out thread stopped listening, indicating it hit the WaitQueue - // timeout, so we put connection back into pool. - let mut connection = request.unwrap_pooled_connection(); - connection.mark_as_available(); - self.available_connections.push_back(connection); - } + conn.mark_checked_out(self.manager.clone(), self.get_cancellation_receiver()); + if let Err(request) = + request.fulfill(ConnectionRequestResult::Pooled(Box::new(conn))) + { + // checking out thread stopped listening, indicating it hit the WaitQueue + // timeout, so we put connection back into pool. + let mut connection = request.unwrap_pooled_connection(); + connection.mark_checked_in(); + self.available_connections.push_back(connection); + } - return; + return; + } } // otherwise, attempt to create a connection. @@ -384,6 +438,7 @@ impl ConnectionPoolWorker { let manager = self.manager.clone(); let server_updater = self.server_updater.clone(); let credential = self.credential.clone(); + let cancellation_receiver = self.get_cancellation_receiver(); let handle = runtime::spawn(async move { let mut establish_result = establish_connection( @@ -397,7 +452,7 @@ impl ConnectionPoolWorker { .await; if let Ok(ref mut c) = establish_result { - c.mark_as_in_use(manager.clone()); + c.mark_checked_out(manager.clone(), cancellation_receiver); manager.handle_connection_succeeded(ConnectionSucceeded::Used { service_id: c.generation.service_id(), }); @@ -426,6 +481,8 @@ impl ConnectionPoolWorker { address: self.address.clone(), generation: self.generation.clone(), event_emitter: self.event_emitter.clone(), + time_created: Instant::now(), + cancellation_receiver: self.get_cancellation_receiver(), }; self.next_connection_id += 1; self.event_emitter @@ -452,16 +509,16 @@ impl ConnectionPoolWorker { } if let ConnectionSucceeded::ForPool(connection) = connection { let mut connection = *connection; - connection.mark_as_available(); + connection.mark_checked_in(); self.available_connections.push_back(connection); } } - fn check_in(&mut self, mut conn: Connection) { + fn check_in(&mut self, mut conn: PooledConnection) { self.event_emitter .emit_event(|| conn.checked_in_event().into()); - conn.mark_as_available(); + conn.mark_checked_in(); if conn.has_errored() { self.close_connection(conn, ConnectionClosedReason::Error); @@ -475,6 +532,13 @@ impl ConnectionPoolWorker { } fn clear(&mut self, cause: Error, service_id: Option) { + let interrupt_in_use_connections = cause.is_network_timeout(); + if interrupt_in_use_connections { + if let Some(ref cancellation_sender) = self.cancellation_sender { + let _ = cancellation_sender.send(()); + } + } + let was_ready = match (&mut self.generation, service_id) { (PoolGeneration::Normal(gen), None) => { *gen += 1; @@ -495,6 +559,7 @@ impl ConnectionPoolWorker { PoolClearedEvent { address: self.address.clone(), service_id, + interrupt_in_use_connections, } .into() }); @@ -527,7 +592,7 @@ impl ConnectionPoolWorker { /// Close a connection, emit the event for it being closed, and decrement the /// total connection count. #[allow(clippy::single_match)] - fn close_connection(&mut self, connection: Connection, reason: ConnectionClosedReason) { + fn close_connection(&mut self, connection: PooledConnection, reason: ConnectionClosedReason) { match (&mut self.generation, connection.generation.service_id()) { (PoolGeneration::LoadBalanced(gen_map), Some(sid)) => { match self.service_connection_count.get_mut(&sid) { @@ -544,7 +609,7 @@ impl ConnectionPoolWorker { (PoolGeneration::Normal(_), None) => {} _ => load_balanced_mode_mismatch!(), } - connection.close_and_drop(reason); + connection.emit_closed_event(reason); self.total_connection_count -= 1; } @@ -578,7 +643,7 @@ impl ConnectionPoolWorker { fn ensure_min_connections(&mut self) { if let Some(min_pool_size) = self.min_pool_size { while self.total_connection_count < min_pool_size - && self.pending_connection_count < MAX_CONNECTING + && self.pending_connection_count < self.max_connecting { let pending_connection = self.create_pending_connection(); let event_handler = self.event_emitter.clone(); @@ -587,7 +652,7 @@ impl ConnectionPoolWorker { let updater = self.server_updater.clone(); let credential = self.credential.clone(); - runtime::execute(async move { + runtime::spawn(async move { let connection = establish_connection( establisher, pending_connection, @@ -607,6 +672,14 @@ impl ConnectionPoolWorker { } } } + + /// Returns a receiver for the pool's cancellation sender if this pool is not in load-balanced + /// mode. The returned receiver will only receive messages sent after this method is called. + fn get_cancellation_receiver(&self) -> Option> { + self.cancellation_sender + .as_ref() + .map(|sender| sender.subscribe()) + } } /// Helper covering the common connection establishment behavior between @@ -619,7 +692,7 @@ async fn establish_connection( manager: &PoolManager, credential: Option, event_emitter: CmapEventEmitter, -) -> Result { +) -> Result { let connection_id = pending_connection.id; let address = pending_connection.address.clone(); @@ -656,6 +729,30 @@ async fn establish_connection( establish_result.map_err(|e| e.cause) } +async fn fill_pool( + requester: WeakConnectionRequester, + ack: crate::runtime::AcknowledgmentSender<()>, +) { + let mut establishing = vec![]; + loop { + let result = requester.request_warm_pool().await; + match result { + None => break, + Some(ConnectionRequestResult::Establishing(handle)) => { + // Let connections finish establishing in parallel. + establishing.push(crate::runtime::spawn(async move { + let _ = handle.await; + // The connection is dropped here, returning it to the pool. + })); + } + _ => break, + }; + } + // Wait for all connections to finish establishing before reporting completion. + futures_util::future::join_all(establishing).await; + ack.acknowledge(()); +} + /// Enum modeling the possible pool states as described in the CMAP spec. /// /// The "closed" state is omitted here because the pool considered closed only diff --git a/src/coll.rs b/src/coll.rs new file mode 100644 index 000000000..ff579d0c5 --- /dev/null +++ b/src/coll.rs @@ -0,0 +1,293 @@ +mod action; +pub mod options; + +use std::{fmt, fmt::Debug, str::FromStr, sync::Arc}; + +use crate::bson::rawdoc; +use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; + +use self::options::*; +use crate::{ + client::options::ServerAddress, + cmap::conn::PinnedConnectionHandle, + concern::{ReadConcern, WriteConcern}, + error::{Error, Result}, + selection_criteria::SelectionCriteria, + Client, + Database, +}; + +/// `Collection` is the client-side abstraction of a MongoDB Collection. It can be used to +/// perform collection-level operations such as CRUD operations. A `Collection` can be obtained +/// through a [`Database`](struct.Database.html) by calling either +/// [`Database::collection`](struct.Database.html#method.collection) or +/// [`Database::collection_with_options`](struct.Database.html#method.collection_with_options). +/// +/// A [`Collection`] can be parameterized with any type that implements the +/// `Serialize` and `Deserialize` traits from the [`serde`](https://serde.rs/) crate. This includes but +/// is not limited to just `Document`. The various methods that accept or return instances of the +/// documents in the collection will accept/return instances of the generic parameter (e.g. +/// [`Collection::insert_one`] accepts it as an argument, [`Collection::find_one`] returns an +/// `Option` of it). It is recommended to define types that model your data which you can +/// parameterize your [`Collection`]s with instead of `Document`, since doing so eliminates a lot of +/// boilerplate deserialization code and is often more performant. +/// +/// `Collection` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, +/// so it can safely be shared across threads or async tasks. +/// +/// # Example +/// ```rust +/// # use mongodb::{ +/// # bson::doc, +/// # error::Result, +/// # }; +/// # +/// # async fn start_workers() -> Result<()> { +/// # use mongodb::Client; +/// # +/// # let client = Client::with_uri_str("mongodb://example.com").await?; +/// use serde::{Deserialize, Serialize}; +/// +/// /// Define a type that models our data. +/// #[derive(Clone, Debug, Deserialize, Serialize)] +/// struct Item { +/// id: u32, +/// } +/// +/// // Parameterize our collection with the model. +/// let coll = client.database("items").collection::("in_stock"); +/// +/// for i in 0..5 { +/// let coll_ref = coll.clone(); +/// +/// // Spawn several tasks that operate on the same collection concurrently. +/// tokio::task::spawn(async move { +/// // Perform operations with `coll_ref` that work with directly our model. +/// coll_ref.insert_one(Item { id: i }).await; +/// }); +/// } +/// # +/// # Ok(()) +/// # } +/// ``` +#[derive(Debug)] +pub struct Collection +where + T: Send + Sync, +{ + inner: Arc, + _phantom: std::marker::PhantomData T>, +} + +// Because derive is too conservative, derive only implements Clone if T is Clone. +// Collection does not actually store any value of type T (so T does not need to be clone). +impl Clone for Collection +where + T: Send + Sync, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + _phantom: Default::default(), + } + } +} + +#[derive(Debug, Clone)] +struct CollectionInner { + client: Client, + db: Database, + name: String, + selection_criteria: Option, + read_concern: Option, + write_concern: Option, +} + +impl Collection +where + T: Send + Sync, +{ + pub(crate) fn new(db: Database, name: &str, options: Option) -> Self { + let options = options.unwrap_or_default(); + let selection_criteria = options + .selection_criteria + .or_else(|| db.selection_criteria().cloned()); + + let read_concern = options.read_concern.or_else(|| db.read_concern().cloned()); + + let write_concern = options + .write_concern + .or_else(|| db.write_concern().cloned()); + + Self { + inner: Arc::new(CollectionInner { + client: db.client().clone(), + db, + name: name.to_string(), + selection_criteria, + read_concern, + write_concern, + }), + _phantom: Default::default(), + } + } + + /// Gets a clone of the `Collection` with a different type `U`. + pub fn clone_with_type(&self) -> Collection { + Collection { + inner: self.inner.clone(), + _phantom: Default::default(), + } + } + + pub(crate) fn clone_unconcerned(&self) -> Self { + let mut new_inner = CollectionInner::clone(&self.inner); + new_inner.write_concern = None; + new_inner.read_concern = None; + Self { + inner: Arc::new(new_inner), + _phantom: Default::default(), + } + } + + /// Get the `Client` that this collection descended from. + pub fn client(&self) -> &Client { + &self.inner.client + } + + /// Gets the name of the `Collection`. + pub fn name(&self) -> &str { + &self.inner.name + } + + /// Gets the namespace of the `Collection`. + /// + /// The namespace of a MongoDB collection is the concatenation of the name of the database + /// containing it, the '.' character, and the name of the collection itself. For example, if a + /// collection named "bar" is created in a database named "foo", the namespace of the collection + /// is "foo.bar". + pub fn namespace(&self) -> Namespace { + Namespace { + db: self.inner.db.name().into(), + coll: self.name().into(), + } + } + + /// Gets the selection criteria of the `Collection`. + pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.inner.selection_criteria.as_ref() + } + + /// Gets the read concern of the `Collection`. + pub fn read_concern(&self) -> Option<&ReadConcern> { + self.inner.read_concern.as_ref() + } + + /// Gets the write concern of the `Collection`. + pub fn write_concern(&self) -> Option<&WriteConcern> { + self.inner.write_concern.as_ref() + } + + /// Kill the server side cursor that id corresponds to. + pub(super) async fn kill_cursor( + &self, + cursor_id: i64, + pinned_connection: Option<&PinnedConnectionHandle>, + drop_address: Option, + ) -> Result<()> { + let ns = self.namespace(); + + let op = crate::operation::run_command::RunCommand::new( + ns.db, + rawdoc! { + "killCursors": ns.coll.as_str(), + "cursors": [cursor_id] + }, + drop_address.map(SelectionCriteria::from_address), + pinned_connection, + ); + self.client().execute_operation(op, None).await?; + Ok(()) + } +} + +/// A struct modeling the canonical name for a collection in MongoDB. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Namespace { + /// The name of the database associated with this namespace. + pub db: String, + + /// The name of the collection this namespace corresponds to. + pub coll: String, +} + +impl Namespace { + /// Construct a `Namespace` with the given database and collection. + pub fn new(db: impl Into, coll: impl Into) -> Self { + Self { + db: db.into(), + coll: coll.into(), + } + } + + pub(crate) fn from_str(s: &str) -> Option { + let mut parts = s.split('.'); + + let db = parts.next(); + let coll = parts.collect::>().join("."); + + match (db, coll) { + (Some(db), coll) if !coll.is_empty() => Some(Self { + db: db.to_string(), + coll, + }), + _ => None, + } + } +} + +impl fmt::Display for Namespace { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{}.{}", self.db, self.coll) + } +} + +impl<'de> Deserialize<'de> for Namespace { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + Self::from_str(&s) + .ok_or_else(|| D::Error::custom("Missing one or more fields in namespace")) + } +} + +impl Serialize for Namespace { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_str(&(self.db.clone() + "." + &self.coll)) + } +} + +impl FromStr for Namespace { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut parts = s.split('.'); + + let db = parts.next(); + let coll = parts.collect::>().join("."); + + match (db, coll) { + (Some(db), coll) if !coll.is_empty() => Ok(Self { + db: db.to_string(), + coll, + }), + _ => Err(Self::Err::invalid_argument( + "Missing one or more fields in namespace", + )), + } + } +} diff --git a/src/coll/action.rs b/src/coll/action.rs new file mode 100644 index 000000000..e39e3e963 --- /dev/null +++ b/src/coll/action.rs @@ -0,0 +1 @@ +mod drop; diff --git a/src/coll/action/drop.rs b/src/coll/action/drop.rs new file mode 100644 index 000000000..9d712fa0a --- /dev/null +++ b/src/coll/action/drop.rs @@ -0,0 +1,97 @@ +use crate::{ + action::{action_impl, DropCollection}, + error::Result, + operation::drop_collection as op, +}; + +#[action_impl] +impl<'a> Action for DropCollection<'a> { + type Future = DropCollectionFuture; + + async fn execute(mut self) -> Result<()> { + resolve_options!(self.cr, self.options, [write_concern]); + + #[cfg(feature = "in-use-encryption")] + self.cr + .drop_aux_collections(self.options.as_ref(), self.session.as_deref_mut()) + .await?; + + let drop = op::DropCollection::new(self.cr.namespace(), self.options); + self.cr + .client() + .execute_operation(drop, self.session.as_deref_mut()) + .await + } +} + +#[cfg(feature = "in-use-encryption")] +impl crate::Collection +where + T: Send + Sync, +{ + #[allow(clippy::needless_option_as_deref)] + async fn drop_aux_collections( + &self, + options: Option<&crate::coll::DropCollectionOptions>, + mut session: Option<&mut crate::ClientSession>, + ) -> Result<()> { + use crate::bson::doc; + use futures_util::TryStreamExt; + + // Find associated `encrypted_fields`: + // * from options to this call + let mut enc_fields = options.and_then(|o| o.encrypted_fields.as_ref()); + let enc_opts = self.client().auto_encryption_opts().await; + // * from client-wide `encrypted_fields_map`: + let client_enc_fields = enc_opts + .as_ref() + .and_then(|eo| eo.encrypted_fields_map.as_ref()); + if enc_fields.is_none() { + enc_fields = + client_enc_fields.and_then(|efm| efm.get(&format!("{}", self.namespace()))); + } + // * from a `list_collections` call: + let found; + if enc_fields.is_none() && enc_opts.is_some() { + let filter = doc! { "name": self.name() }; + let mut specs: Vec<_> = match session.as_deref_mut() { + Some(s) => { + let mut cursor = self + .inner + .db + .list_collections() + .filter(filter) + .session(&mut *s) + .await?; + cursor.stream(s).try_collect().await? + } + None => { + self.inner + .db + .list_collections() + .filter(filter) + .await? + .try_collect() + .await? + } + }; + if let Some(spec) = specs.pop() { + if let Some(enc) = spec.options.encrypted_fields { + found = enc; + enc_fields = Some(&found); + } + } + } + + // Drop the collections. + if let Some(enc_fields) = enc_fields { + for ns in crate::client::csfle::aux_collections(&self.namespace(), enc_fields)? { + let drop = op::DropCollection::new(ns, options.cloned()); + self.client() + .execute_operation(drop, session.as_deref_mut()) + .await?; + } + } + Ok(()) + } +} diff --git a/src/coll/mod.rs b/src/coll/mod.rs deleted file mode 100644 index a07d2bac8..000000000 --- a/src/coll/mod.rs +++ /dev/null @@ -1,1523 +0,0 @@ -pub mod options; - -use std::{borrow::Borrow, collections::HashSet, fmt, fmt::Debug, str::FromStr, sync::Arc}; - -use futures_util::{ - future, - stream::{StreamExt, TryStreamExt}, -}; -use serde::{ - de::{DeserializeOwned, Error as DeError}, - Deserialize, - Deserializer, - Serialize, -}; - -use self::options::*; -use crate::{ - bson::{doc, to_document, Bson, Document}, - bson_util, - change_stream::{ - event::ChangeStreamEvent, - options::ChangeStreamOptions, - session::SessionChangeStream, - ChangeStream, - }, - client::options::ServerAddress, - cmap::conn::PinnedConnectionHandle, - concern::{ReadConcern, WriteConcern}, - error::{convert_bulk_errors, BulkWriteError, BulkWriteFailure, Error, ErrorKind, Result}, - index::IndexModel, - operation::{ - Aggregate, - Count, - CountDocuments, - CreateIndexes, - Delete, - Distinct, - DropCollection, - DropIndexes, - Find, - FindAndModify, - Insert, - ListIndexes, - Update, - }, - results::{ - CreateIndexResult, - CreateIndexesResult, - DeleteResult, - InsertManyResult, - InsertOneResult, - UpdateResult, - }, - selection_criteria::SelectionCriteria, - Client, - ClientSession, - Cursor, - Database, - SessionCursor, -}; - -/// `Collection` is the client-side abstraction of a MongoDB Collection. It can be used to -/// perform collection-level operations such as CRUD operations. A `Collection` can be obtained -/// through a [`Database`](struct.Database.html) by calling either -/// [`Database::collection`](struct.Database.html#method.collection) or -/// [`Database::collection_with_options`](struct.Database.html#method.collection_with_options). -/// -/// A [`Collection`] can be parameterized with any type that implements the -/// `Serialize` and `Deserialize` traits from the [`serde`](https://serde.rs/) crate. This includes but -/// is not limited to just `Document`. The various methods that accept or return instances of the -/// documents in the collection will accept/return instances of the generic parameter (e.g. -/// [`Collection::insert_one`] accepts it as an argument, [`Collection::find_one`] returns an -/// `Option` of it). It is recommended to define types that model your data which you can -/// parameterize your [`Collection`]s with instead of `Document`, since doing so eliminates a lot of -/// boilerplate deserialization code and is often more performant. -/// -/// `Collection` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, -/// so it can safely be shared across threads or async tasks. -/// -/// # Example -/// ```rust -/// # use mongodb::{ -/// # bson::doc, -/// # error::Result, -/// # }; -/// # #[cfg(feature = "async-std-runtime")] -/// # use async_std::task; -/// # #[cfg(feature = "tokio-runtime")] -/// # use tokio::task; -/// # -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # async fn start_workers() -> Result<()> { -/// # use mongodb::Client; -/// # -/// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// use serde::{Deserialize, Serialize}; -/// -/// /// Define a type that models our data. -/// #[derive(Clone, Debug, Deserialize, Serialize)] -/// struct Item { -/// id: u32, -/// } -/// -/// // Parameterize our collection with the model. -/// let coll = client.database("items").collection::("in_stock"); -/// -/// for i in 0..5 { -/// let coll_ref = coll.clone(); -/// -/// // Spawn several tasks that operate on the same collection concurrently. -/// task::spawn(async move { -/// // Perform operations with `coll_ref` that work with directly our model. -/// coll_ref.insert_one(Item { id: i }, None).await; -/// }); -/// } -/// # -/// # Ok(()) -/// # } -/// ``` - -#[derive(Debug)] -pub struct Collection { - inner: Arc, - _phantom: std::marker::PhantomData, -} - -// Because derive is too conservative, derive only implements Clone if T is Clone. -// Collection does not actually store any value of type T (so T does not need to be clone). -impl Clone for Collection { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - _phantom: Default::default(), - } - } -} - -#[derive(Debug)] -struct CollectionInner { - client: Client, - db: Database, - name: String, - selection_criteria: Option, - read_concern: Option, - write_concern: Option, -} - -impl Collection { - pub(crate) fn new(db: Database, name: &str, options: Option) -> Self { - let options = options.unwrap_or_default(); - let selection_criteria = options - .selection_criteria - .or_else(|| db.selection_criteria().cloned()); - - let read_concern = options.read_concern.or_else(|| db.read_concern().cloned()); - - let write_concern = options - .write_concern - .or_else(|| db.write_concern().cloned()); - - Self { - inner: Arc::new(CollectionInner { - client: db.client().clone(), - db, - name: name.to_string(), - selection_criteria, - read_concern, - write_concern, - }), - _phantom: Default::default(), - } - } - - /// Gets a clone of the `Collection` with a different type `U`. - pub fn clone_with_type(&self) -> Collection { - Collection { - inner: self.inner.clone(), - _phantom: Default::default(), - } - } - - /// Get the `Client` that this collection descended from. - pub fn client(&self) -> &Client { - &self.inner.client - } - - /// Gets the name of the `Collection`. - pub fn name(&self) -> &str { - &self.inner.name - } - - /// Gets the namespace of the `Collection`. - /// - /// The namespace of a MongoDB collection is the concatenation of the name of the database - /// containing it, the '.' character, and the name of the collection itself. For example, if a - /// collection named "bar" is created in a database named "foo", the namespace of the collection - /// is "foo.bar". - pub fn namespace(&self) -> Namespace { - Namespace { - db: self.inner.db.name().into(), - coll: self.name().into(), - } - } - - /// Gets the selection criteria of the `Collection`. - pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.inner.selection_criteria.as_ref() - } - - /// Gets the read concern of the `Collection`. - pub fn read_concern(&self) -> Option<&ReadConcern> { - self.inner.read_concern.as_ref() - } - - /// Gets the write concern of the `Collection`. - pub fn write_concern(&self) -> Option<&WriteConcern> { - self.inner.write_concern.as_ref() - } - - #[allow(clippy::needless_option_as_deref)] - async fn drop_common( - &self, - options: impl Into>, - session: impl Into>, - ) -> Result<()> { - let mut session = session.into(); - - let mut options: Option = options.into(); - resolve_options!(self, options, [write_concern]); - - #[cfg(feature = "in-use-encryption-unstable")] - self.drop_aux_collections(options.as_ref(), session.as_deref_mut()) - .await?; - - let drop = DropCollection::new(self.namespace(), options); - self.client() - .execute_operation(drop, session.as_deref_mut()) - .await - } - - /// Drops the collection, deleting all data and indexes stored in it. - pub async fn drop(&self, options: impl Into>) -> Result<()> { - self.drop_common(options, None).await - } - - /// Drops the collection, deleting all data and indexes stored in it using the provided - /// `ClientSession`. - pub async fn drop_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - self.drop_common(options, session).await - } - - #[cfg(feature = "in-use-encryption-unstable")] - #[allow(clippy::needless_option_as_deref)] - async fn drop_aux_collections( - &self, - options: Option<&DropCollectionOptions>, - mut session: Option<&mut ClientSession>, - ) -> Result<()> { - // Find associated `encrypted_fields`: - // * from options to this call - let mut enc_fields = options.and_then(|o| o.encrypted_fields.as_ref()); - let enc_opts = self.client().auto_encryption_opts().await; - // * from client-wide `encrypted_fields_map`: - let client_enc_fields = enc_opts - .as_ref() - .and_then(|eo| eo.encrypted_fields_map.as_ref()); - if enc_fields.is_none() { - enc_fields = - client_enc_fields.and_then(|efm| efm.get(&format!("{}", self.namespace()))); - } - // * from a `list_collections` call: - let found; - if enc_fields.is_none() && client_enc_fields.is_some() { - let filter = doc! { "name": self.name() }; - let mut specs: Vec<_> = match session.as_deref_mut() { - Some(s) => { - let mut cursor = self - .inner - .db - .list_collections_with_session(filter, None, s) - .await?; - cursor.stream(s).try_collect().await? - } - None => { - self.inner - .db - .list_collections(filter, None) - .await? - .try_collect() - .await? - } - }; - if let Some(spec) = specs.pop() { - if let Some(enc) = spec.options.encrypted_fields { - found = enc; - enc_fields = Some(&found); - } - } - } - - // Drop the collections. - if let Some(enc_fields) = enc_fields { - for ns in crate::client::csfle::aux_collections(&self.namespace(), enc_fields)? { - let drop = DropCollection::new(ns, options.cloned()); - self.client() - .execute_operation(drop, session.as_deref_mut()) - .await?; - } - } - Ok(()) - } - - /// Runs an aggregation operation. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub async fn aggregate( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result> { - let mut options = options.into(); - resolve_options!( - self, - options, - [read_concern, write_concern, selection_criteria] - ); - - let aggregate = Aggregate::new(self.namespace(), pipeline, options); - let client = self.client(); - client.execute_cursor_operation(aggregate).await - } - - /// Runs an aggregation operation using the provided `ClientSession`. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub async fn aggregate_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_write_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - - let aggregate = Aggregate::new(self.namespace(), pipeline, options); - let client = self.client(); - client - .execute_session_cursor_operation(aggregate, session) - .await - } - - /// Estimates the number of documents in the collection using collection metadata. - /// - /// Due to an oversight in versions 5.0.0 - 5.0.7 of MongoDB, the `count` server command, - /// which `estimatedDocumentCount` uses in its implementation, was not included in v1 of the - /// Stable API. Users of the Stable API with `estimatedDocumentCount` are recommended to - /// upgrade their cluster to 5.0.8+ or set - /// [`ServerApi::strict`](crate::options::ServerApi::strict) to false to avoid encountering - /// errors. - /// - /// For more information on the behavior of the `count` server command, see - /// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior). - pub async fn estimated_document_count( - &self, - options: impl Into>, - ) -> Result { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - - let op = Count::new(self.namespace(), options); - - self.client().execute_operation(op, None).await - } - - async fn count_documents_common( - &self, - filter: impl Into>, - options: impl Into>, - session: impl Into>, - ) -> Result { - let session = session.into(); - - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, session.as_ref())?; - resolve_selection_criteria_with_session!(self, options, session.as_ref())?; - - let op = CountDocuments::new(self.namespace(), filter.into(), options)?; - self.client().execute_operation(op, session).await - } - - /// Gets the number of documents matching `filter`. - /// - /// Note that using [`Collection::estimated_document_count`](#method.estimated_document_count) - /// is recommended instead of this method is most cases. - pub async fn count_documents( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result { - self.count_documents_common(filter, options, None).await - } - - /// Gets the number of documents matching `filter` using the provided `ClientSession`. - /// - /// Note that using [`Collection::estimated_document_count`](#method.estimated_document_count) - /// is recommended instead of this method is most cases. - pub async fn count_documents_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.count_documents_common(filter, options, session).await - } - - async fn delete_many_common( - &self, - query: Document, - options: impl Into>, - session: impl Into>, - ) -> Result { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let delete = Delete::new(self.namespace(), query, None, options); - self.client().execute_operation(delete, session).await - } - - async fn create_indexes_common( - &self, - indexes: impl IntoIterator, - options: impl Into>, - session: impl Into>, - ) -> Result { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let indexes: Vec = indexes.into_iter().collect(); - - let create_indexes = CreateIndexes::new(self.namespace(), indexes, options); - self.client() - .execute_operation(create_indexes, session) - .await - } - - pub(crate) async fn create_index_common( - &self, - index: IndexModel, - options: impl Into>, - session: impl Into>, - ) -> Result { - let response = self - .create_indexes_common(vec![index], options, session) - .await?; - Ok(response.into_create_index_result()) - } - - /// Creates the given index on this collection. - pub async fn create_index( - &self, - index: IndexModel, - options: impl Into>, - ) -> Result { - self.create_index_common(index, options, None).await - } - - /// Creates the given index on this collection using the provided `ClientSession`. - pub async fn create_index_with_session( - &self, - index: IndexModel, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.create_index_common(index, options, session).await - } - - /// Creates the given indexes on this collection. - pub async fn create_indexes( - &self, - indexes: impl IntoIterator, - options: impl Into>, - ) -> Result { - self.create_indexes_common(indexes, options, None).await - } - - /// Creates the given indexes on this collection using the provided `ClientSession`. - pub async fn create_indexes_with_session( - &self, - indexes: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.create_indexes_common(indexes, options, session).await - } - - /// Deletes all documents stored in the collection matching `query`. - pub async fn delete_many( - &self, - query: Document, - options: impl Into>, - ) -> Result { - self.delete_many_common(query, options, None).await - } - - /// Deletes all documents stored in the collection matching `query` using the provided - /// `ClientSession`. - pub async fn delete_many_with_session( - &self, - query: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.delete_many_common(query, options, session).await - } - - async fn delete_one_common( - &self, - query: Document, - options: impl Into>, - session: impl Into>, - ) -> Result { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let delete = Delete::new(self.namespace(), query, Some(1), options); - self.client().execute_operation(delete, session).await - } - - /// Deletes up to one document found matching `query`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn delete_one( - &self, - query: Document, - options: impl Into>, - ) -> Result { - self.delete_one_common(query, options, None).await - } - - /// Deletes up to one document found matching `query` using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn delete_one_with_session( - &self, - query: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.delete_one_common(query, options, session).await - } - - async fn distinct_common( - &self, - field_name: impl AsRef, - filter: impl Into>, - options: impl Into>, - session: impl Into>, - ) -> Result> { - let session = session.into(); - - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, session.as_ref())?; - resolve_selection_criteria_with_session!(self, options, session.as_ref())?; - - let op = Distinct::new( - self.namespace(), - field_name.as_ref().to_string(), - filter.into(), - options, - ); - self.client().execute_operation(op, session).await - } - - /// Finds the distinct values of the field specified by `field_name` across the collection. - pub async fn distinct( - &self, - field_name: impl AsRef, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - self.distinct_common(field_name, filter, options, None) - .await - } - - /// Finds the distinct values of the field specified by `field_name` across the collection using - /// the provided `ClientSession`. - pub async fn distinct_with_session( - &self, - field_name: impl AsRef, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - self.distinct_common(field_name, filter, options, session) - .await - } - - async fn drop_indexes_common( - &self, - name: impl Into>, - options: impl Into>, - session: impl Into>, - ) -> Result<()> { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - // If there is no provided name, that means we should drop all indexes. - let index_name = name.into().unwrap_or("*").to_string(); - - let drop_index = DropIndexes::new(self.namespace(), index_name, options); - self.client().execute_operation(drop_index, session).await - } - - async fn drop_index_common( - &self, - name: impl AsRef, - options: impl Into>, - session: impl Into>, - ) -> Result<()> { - let name = name.as_ref(); - if name == "*" { - return Err(ErrorKind::InvalidArgument { - message: "Cannot pass name \"*\" to drop_index since more than one index would be \ - dropped." - .to_string(), - } - .into()); - } - self.drop_indexes_common(name, options, session).await - } - - /// Drops the index specified by `name` from this collection. - pub async fn drop_index( - &self, - name: impl AsRef, - options: impl Into>, - ) -> Result<()> { - self.drop_index_common(name, options, None).await - } - - /// Drops the index specified by `name` from this collection using the provided `ClientSession`. - pub async fn drop_index_with_session( - &self, - name: impl AsRef, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - self.drop_index_common(name, options, session).await - } - - /// Drops all indexes associated with this collection. - pub async fn drop_indexes(&self, options: impl Into>) -> Result<()> { - self.drop_indexes_common(None, options, None).await - } - - /// Drops all indexes associated with this collection using the provided `ClientSession`. - pub async fn drop_indexes_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - self.drop_indexes_common(None, options, session).await - } - - /// Lists all indexes on this collection. - pub async fn list_indexes( - &self, - options: impl Into>, - ) -> Result> { - let list_indexes = ListIndexes::new(self.namespace(), options.into()); - let client = self.client(); - client.execute_cursor_operation(list_indexes).await - } - - /// Lists all indexes on this collection using the provided `ClientSession`. - pub async fn list_indexes_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let list_indexes = ListIndexes::new(self.namespace(), options.into()); - let client = self.client(); - client - .execute_session_cursor_operation(list_indexes, session) - .await - } - - async fn list_index_names_common( - &self, - cursor: impl TryStreamExt, - ) -> Result> { - cursor - .try_filter_map(|index| future::ok(index.get_name())) - .try_collect() - .await - } - - /// Gets the names of all indexes on the collection. - pub async fn list_index_names(&self) -> Result> { - let cursor = self.list_indexes(None).await?; - self.list_index_names_common(cursor).await - } - - /// Gets the names of all indexes on the collection using the provided `ClientSession`. - pub async fn list_index_names_with_session( - &self, - session: &mut ClientSession, - ) -> Result> { - let mut cursor = self.list_indexes_with_session(None, session).await?; - self.list_index_names_common(cursor.stream(session)).await - } - - async fn update_many_common( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: impl Into>, - ) -> Result { - let update = update.into(); - - if let UpdateModifications::Document(ref d) = update { - bson_util::update_document_check(d)?; - } - - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let update = Update::new(self.namespace(), query, update, true, options); - self.client().execute_operation(update, session).await - } - - /// Updates all documents matching `query` in the collection. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - pub async fn update_many( - &self, - query: Document, - update: impl Into, - options: impl Into>, - ) -> Result { - self.update_many_common(query, update, options, None).await - } - - /// Updates all documents matching `query` in the collection using the provided `ClientSession`. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - pub async fn update_many_with_session( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.update_many_common(query, update, options, session) - .await - } - - async fn update_one_common( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: impl Into>, - ) -> Result { - let update = update.into(); - if let UpdateModifications::Document(ref d) = update { - bson_util::update_document_check(d)?; - } - - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let update = Update::new(self.namespace(), query, update, false, options); - self.client().execute_operation(update, session).await - } - - /// Updates up to one document matching `query` in the collection. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn update_one( - &self, - query: Document, - update: impl Into, - options: impl Into>, - ) -> Result { - self.update_one_common(query, update, options, None).await - } - - /// Updates up to one document matching `query` in the collection using the provided - /// `ClientSession`. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn update_one_with_session( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.update_one_common(query, update, options, session) - .await - } - - /// Kill the server side cursor that id corresponds to. - pub(super) async fn kill_cursor( - &self, - cursor_id: i64, - pinned_connection: Option<&PinnedConnectionHandle>, - drop_address: Option, - ) -> Result<()> { - let ns = self.namespace(); - - self.client() - .database(ns.db.as_str()) - .run_command_common( - doc! { - "killCursors": ns.coll.as_str(), - "cursors": [cursor_id] - }, - drop_address.map(SelectionCriteria::from_address), - None, - pinned_connection, - ) - .await?; - Ok(()) - } - - /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events - /// for all changes in this collection. A - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) cannot be started on system - /// collections. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read concern. Anything else - /// will cause a server error. - /// - /// Also note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` - /// fields will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub async fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> - where - T: DeserializeOwned + Unpin + Send + Sync, - { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - let target = self.namespace().into(); - self.client() - .execute_watch(pipeline, options, target, None) - .await - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in this collection - /// using the provided [`ClientSession`]. See [`Client::watch`] for more information. - pub async fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> - where - T: DeserializeOwned + Unpin + Send + Sync, - { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - let target = self.namespace().into(); - self.client() - .execute_watch_with_session(pipeline, options, target, None, session) - .await - } - - /// Finds the documents in the collection matching `filter`. - pub async fn find( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - - let find = Find::new(self.namespace(), filter.into(), options); - let client = self.client(); - - client.execute_cursor_operation(find).await - } - - /// Finds the documents in the collection matching `filter` using the provided `ClientSession`. - pub async fn find_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - - let find = Find::new(self.namespace(), filter.into(), options); - let client = self.client(); - - client.execute_session_cursor_operation(find, session).await - } -} - -impl Collection -where - T: DeserializeOwned + Unpin + Send + Sync, -{ - /// Finds a single document in the collection matching `filter`. - pub async fn find_one( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - - let options: FindOptions = options.map(Into::into).unwrap_or_else(Default::default); - let mut cursor = self.find(filter, Some(options)).await?; - cursor.next().await.transpose() - } - - /// Finds a single document in the collection matching `filter` using the provided - /// `ClientSession`. - pub async fn find_one_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - - let options: FindOptions = options.map(Into::into).unwrap_or_else(Default::default); - let mut cursor = self - .find_with_session(filter, Some(options), session) - .await?; - let mut cursor = cursor.stream(session); - cursor.next().await.transpose() - } -} - -impl Collection -where - T: DeserializeOwned, -{ - async fn find_one_and_delete_common( - &self, - filter: Document, - options: impl Into>, - session: impl Into>, - ) -> Result> { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let op = FindAndModify::::with_delete(self.namespace(), filter, options); - self.client().execute_operation(op, session).await - } - - /// Atomically finds up to one document in the collection matching `filter` and deletes it. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_delete( - &self, - filter: Document, - options: impl Into>, - ) -> Result> { - self.find_one_and_delete_common(filter, options, None).await - } - - /// Atomically finds up to one document in the collection matching `filter` and deletes it using - /// the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_delete_with_session( - &self, - filter: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - self.find_one_and_delete_common(filter, options, session) - .await - } - - async fn find_one_and_update_common( - &self, - filter: Document, - update: impl Into, - options: impl Into>, - session: impl Into>, - ) -> Result> { - let update = update.into(); - - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let op = FindAndModify::::with_update(self.namespace(), filter, update, options)?; - self.client().execute_operation(op, session).await - } - - /// Atomically finds up to one document in the collection matching `filter` and updates it. - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_update( - &self, - filter: Document, - update: impl Into, - options: impl Into>, - ) -> Result> { - self.find_one_and_update_common(filter, update, options, None) - .await - } - - /// Atomically finds up to one document in the collection matching `filter` and updates it using - /// the provided `ClientSession`. Both `Document` and `Vec` implement - /// `Into`, so either can be passed in place of constructing the enum - /// case. Note: pipeline updates are only supported in MongoDB 4.2+. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_update_with_session( - &self, - filter: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - self.find_one_and_update_common(filter, update, options, session) - .await - } -} - -impl Collection -where - T: Serialize + DeserializeOwned, -{ - async fn find_one_and_replace_common( - &self, - filter: Document, - replacement: impl Borrow, - options: impl Into>, - session: impl Into>, - ) -> Result> { - let replacement = to_document(replacement.borrow())?; - - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let op = FindAndModify::::with_replace(self.namespace(), filter, replacement, options)?; - self.client().execute_operation(op, session).await - } - - /// Atomically finds up to one document in the collection matching `filter` and replaces it with - /// `replacement`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_replace( - &self, - filter: Document, - replacement: impl Borrow, - options: impl Into>, - ) -> Result> { - self.find_one_and_replace_common(filter, replacement, options, None) - .await - } - - /// Atomically finds up to one document in the collection matching `filter` and replaces it with - /// `replacement` using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn find_one_and_replace_with_session( - &self, - filter: Document, - replacement: impl Borrow, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - self.find_one_and_replace_common(filter, replacement, options, session) - .await - } -} - -impl Collection -where - T: Serialize, -{ - #[allow(clippy::needless_option_as_deref)] - async fn insert_many_common( - &self, - docs: impl IntoIterator>, - options: impl Into>, - mut session: Option<&mut ClientSession>, - ) -> Result { - let ds: Vec<_> = docs.into_iter().collect(); - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - if ds.is_empty() { - return Err(ErrorKind::InvalidArgument { - message: "No documents provided to insert_many".to_string(), - } - .into()); - } - - let ordered = options.as_ref().and_then(|o| o.ordered).unwrap_or(true); - #[cfg(feature = "in-use-encryption-unstable")] - let encrypted = self.client().auto_encryption_opts().await.is_some(); - #[cfg(not(feature = "in-use-encryption-unstable"))] - let encrypted = false; - - let mut cumulative_failure: Option = None; - let mut error_labels: HashSet = Default::default(); - let mut cumulative_result: Option = None; - - let mut n_attempted = 0; - - while n_attempted < ds.len() { - let docs: Vec<&T> = ds.iter().skip(n_attempted).map(Borrow::borrow).collect(); - let insert = Insert::new_encrypted(self.namespace(), docs, options.clone(), encrypted); - - match self - .client() - .execute_operation(insert, session.as_deref_mut()) - .await - { - Ok(result) => { - let current_batch_size = result.inserted_ids.len(); - - let cumulative_result = - cumulative_result.get_or_insert_with(InsertManyResult::new); - for (index, id) in result.inserted_ids { - cumulative_result - .inserted_ids - .insert(index + n_attempted, id); - } - - n_attempted += current_batch_size; - } - Err(e) => { - let labels = e.labels().clone(); - match *e.kind { - ErrorKind::BulkWrite(bw) => { - // for ordered inserts this size will be incorrect, but knowing the - // batch size isn't needed for ordered - // failures since we return immediately from - // them anyways. - let current_batch_size = bw.inserted_ids.len() - + bw.write_errors.as_ref().map(|we| we.len()).unwrap_or(0); - - let failure_ref = - cumulative_failure.get_or_insert_with(BulkWriteFailure::new); - if let Some(write_errors) = bw.write_errors { - for err in write_errors { - let index = n_attempted + err.index; - - failure_ref - .write_errors - .get_or_insert_with(Default::default) - .push(BulkWriteError { index, ..err }); - } - } - - if let Some(wc_error) = bw.write_concern_error { - failure_ref.write_concern_error = Some(wc_error); - } - - error_labels.extend(labels); - - if ordered { - // this will always be true since we invoked get_or_insert_with - // above. - if let Some(failure) = cumulative_failure { - return Err(Error::new( - ErrorKind::BulkWrite(failure), - Some(error_labels), - )); - } - } - n_attempted += current_batch_size; - } - _ => return Err(e), - } - } - } - } - - match cumulative_failure { - Some(failure) => Err(Error::new( - ErrorKind::BulkWrite(failure), - Some(error_labels), - )), - None => Ok(cumulative_result.unwrap_or_else(InsertManyResult::new)), - } - } - - /// Inserts the data in `docs` into the collection. - /// - /// Note that this method accepts both owned and borrowed values, so the input documents - /// do not need to be cloned in order to be passed in. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn insert_many( - &self, - docs: impl IntoIterator>, - options: impl Into>, - ) -> Result { - self.insert_many_common(docs, options, None).await - } - - /// Inserts the data in `docs` into the collection using the provided `ClientSession`. - /// - /// Note that this method accepts both owned and borrowed values, so the input documents - /// do not need to be cloned in order to be passed in. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn insert_many_with_session( - &self, - docs: impl IntoIterator>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.insert_many_common(docs, options, Some(session)).await - } - - async fn insert_one_common( - &self, - doc: &T, - options: impl Into>, - session: impl Into>, - ) -> Result { - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let insert = Insert::new( - self.namespace(), - vec![doc], - options.map(InsertManyOptions::from_insert_one_options), - ); - self.client() - .execute_operation(insert, session) - .await - .map(InsertOneResult::from_insert_many_result) - .map_err(convert_bulk_errors) - } - - /// Inserts `doc` into the collection. - /// - /// Note that either an owned or borrowed value can be inserted here, so the input document - /// does not need to be cloned to be passed in. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn insert_one( - &self, - doc: impl Borrow, - options: impl Into>, - ) -> Result { - self.insert_one_common(doc.borrow(), options, None).await - } - - /// Inserts `doc` into the collection using the provided `ClientSession`. - /// - /// Note that either an owned or borrowed value can be inserted here, so the input document - /// does not need to be cloned to be passed in. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn insert_one_with_session( - &self, - doc: impl Borrow, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.insert_one_common(doc.borrow(), options, session).await - } - - async fn replace_one_common( - &self, - query: Document, - replacement: impl Borrow, - options: impl Into>, - session: impl Into>, - ) -> Result { - let replacement = to_document(replacement.borrow())?; - - bson_util::replacement_document_check(&replacement)?; - - let session = session.into(); - - let mut options = options.into(); - resolve_write_concern_with_session!(self, options, session.as_ref())?; - - let update = Update::new( - self.namespace(), - query, - UpdateModifications::Document(replacement), - false, - options.map(UpdateOptions::from_replace_options), - ); - self.client().execute_operation(update, session).await - } - - /// Replaces up to one document matching `query` in the collection with `replacement`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn replace_one( - &self, - query: Document, - replacement: impl Borrow, - options: impl Into>, - ) -> Result { - self.replace_one_common(query, replacement, options, None) - .await - } - - /// Replaces up to one document matching `query` in the collection with `replacement` using the - /// provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub async fn replace_one_with_session( - &self, - query: Document, - replacement: impl Borrow, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - self.replace_one_common(query, replacement, options, session) - .await - } -} - -/// A struct modeling the canonical name for a collection in MongoDB. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Namespace { - /// The name of the database associated with this namespace. - pub db: String, - - /// The name of the collection this namespace corresponds to. - pub coll: String, -} - -impl Namespace { - /// Construct a `Namespace` with the given database and collection. - pub fn new(db: impl Into, coll: impl Into) -> Self { - Self { - db: db.into(), - coll: coll.into(), - } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - Self { - db: String::new(), - coll: String::new(), - } - } - - pub(crate) fn from_str(s: &str) -> Option { - let mut parts = s.split('.'); - - let db = parts.next(); - let coll = parts.collect::>().join("."); - - match (db, coll) { - (Some(db), coll) if !coll.is_empty() => Some(Self { - db: db.to_string(), - coll, - }), - _ => None, - } - } -} - -impl fmt::Display for Namespace { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{}.{}", self.db, self.coll) - } -} - -impl<'de> Deserialize<'de> for Namespace { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - let s: String = Deserialize::deserialize(deserializer)?; - Self::from_str(&s) - .ok_or_else(|| D::Error::custom("Missing one or more fields in namespace")) - } -} - -impl Serialize for Namespace { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - serializer.serialize_str(&(self.db.clone() + "." + &self.coll)) - } -} - -impl FromStr for Namespace { - type Err = Error; - fn from_str(s: &str) -> Result { - let mut parts = s.split('.'); - - let db = parts.next(); - let coll = parts.collect::>().join("."); - - match (db, coll) { - (Some(db), coll) if !coll.is_empty() => Ok(Self { - db: db.to_string(), - coll, - }), - _ => Err(Self::Err::invalid_argument( - "Missing one or more fields in namespace", - )), - } - } -} diff --git a/src/coll/options.rs b/src/coll/options.rs index 706bab49d..a6c08f83f 100644 --- a/src/coll/options.rs +++ b/src/coll/options.rs @@ -1,20 +1,21 @@ use std::time::Duration; -use bson::serde_helpers; +use macro_magic::export_tokens; use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use serde_with::skip_serializing_none; use typed_builder::TypedBuilder; use crate::{ - bson::{doc, Bson, Document}, - bson_util, + bson::{doc, Bson, Document, RawBson, RawDocumentBuf}, concern::{ReadConcern, WriteConcern}, + error::Result, options::Collation, selection_criteria::SelectionCriteria, + serde_util::{self, write_concern_is_empty}, }; -/// These are the valid options for creating a [`Collection`](../struct.Collection.html) with -/// [`Database::collection_with_options`](../struct.Database.html#method.collection_with_options). +/// These are the valid options for creating a [`Collection`](crate::Collection) with +/// [`Database::collection_with_options`](crate::Database::collection_with_options). #[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] @@ -43,6 +44,15 @@ pub enum ReturnDocument { Before, } +impl ReturnDocument { + pub(crate) fn as_bool(&self) -> bool { + match self { + ReturnDocument::After => true, + ReturnDocument::Before => false, + } + } +} + impl<'de> Deserialize<'de> for ReturnDocument { fn deserialize>(deserializer: D) -> std::result::Result { let s = String::deserialize(deserializer)?; @@ -58,7 +68,7 @@ impl<'de> Deserialize<'de> for ReturnDocument { } /// Specifies the index to use for an operation. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(untagged)] #[non_exhaustive] pub enum Hint { @@ -69,16 +79,17 @@ pub enum Hint { } impl Hint { - pub(crate) fn to_bson(&self) -> Bson { - match self { - Hint::Keys(ref d) => Bson::Document(d.clone()), - Hint::Name(ref s) => Bson::String(s.clone()), - } + pub(crate) fn to_raw_bson(&self) -> Result { + Ok(match self { + Hint::Keys(ref d) => RawBson::Document(RawDocumentBuf::from_document(d)?), + Hint::Name(ref s) => RawBson::String(s.clone()), + }) } } /// Specifies the type of cursor to return from a find operation. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Deserialize)] +#[serde(rename_all = "camelCase")] #[non_exhaustive] pub enum CursorType { /// Default; close the cursor after the last document is received from the server. @@ -100,11 +111,13 @@ pub enum CursorType { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct InsertOneOptions { /// Opt out of document-level validation. pub bypass_document_validation: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the @@ -121,6 +134,7 @@ pub struct InsertOneOptions { #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] +#[export_tokens] pub struct InsertManyOptions { /// Opt out of document-level validation. pub bypass_document_validation: Option, @@ -132,7 +146,7 @@ pub struct InsertManyOptions { pub ordered: Option, /// The write concern for the operation. - #[serde(skip_deserializing)] + #[serde(skip_deserializing, skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the @@ -168,17 +182,6 @@ pub enum UpdateModifications { Pipeline(Vec), } -impl UpdateModifications { - pub(crate) fn to_bson(&self) -> Bson { - match self { - UpdateModifications::Document(ref d) => Bson::Document(d.clone()), - UpdateModifications::Pipeline(ref p) => { - Bson::Array(p.iter().map(|d| Bson::Document(d.clone())).collect()) - } - } - } -} - impl From for UpdateModifications { fn from(item: Document) -> Self { UpdateModifications::Document(item) @@ -199,6 +202,7 @@ impl From> for UpdateModifications { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct UpdateOptions { /// A set of filters specifying to which array elements an update should apply. /// @@ -240,6 +244,12 @@ pub struct UpdateOptions { /// /// This option is only available on server versions 4.4+. pub comment: Option, + + /// Specify which document the operation updates if the query matches multiple + /// documents. The first document matched by the sort order will be updated. + /// + /// Only available in MongoDB 8.0+. + pub sort: Option, } impl UpdateOptions { @@ -252,6 +262,7 @@ impl UpdateOptions { collation: options.collation, let_vars: options.let_vars, comment: options.comment, + sort: options.sort, ..Default::default() } } @@ -264,6 +275,7 @@ impl UpdateOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct ReplaceOptions { /// Opt out of document-level validation. pub bypass_document_validation: Option, @@ -284,6 +296,7 @@ pub struct ReplaceOptions { pub hint: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Map of parameter names and values. Values must be constant or closed @@ -299,6 +312,12 @@ pub struct ReplaceOptions { /// /// This option is only available on server versions 4.4+. pub comment: Option, + + /// Specify which document the operation replaces if the query matches multiple + /// documents. The first document matched by the sort order will be replaced. + /// + /// Only available in MongoDB 8.0+. + pub sort: Option, } /// Specifies the options to a @@ -309,6 +328,7 @@ pub struct ReplaceOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct DeleteOptions { /// The collation to use for the operation. /// @@ -317,6 +337,7 @@ pub struct DeleteOptions { pub collation: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// The index to use for the operation. @@ -346,6 +367,7 @@ pub struct DeleteOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct FindOneAndDeleteOptions { /// The maximum amount of time to allow the query to run. /// @@ -360,6 +382,7 @@ pub struct FindOneAndDeleteOptions { pub sort: Option, /// The level of the write concern + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// The collation to use for the operation. @@ -395,6 +418,7 @@ pub struct FindOneAndDeleteOptions { #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] +#[export_tokens] pub struct FindOneAndReplaceOptions { /// Opt out of document-level validation. pub bypass_document_validation: Option, @@ -418,6 +442,7 @@ pub struct FindOneAndReplaceOptions { pub upsert: Option, /// The level of the write concern + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// The collation to use for the operation. @@ -453,6 +478,7 @@ pub struct FindOneAndReplaceOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct FindOneAndUpdateOptions { /// A set of filters specifying to which array elements an update should apply. /// @@ -482,6 +508,7 @@ pub struct FindOneAndUpdateOptions { pub upsert: Option, /// The level of the write concern + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// The collation to use for the operation. @@ -516,6 +543,7 @@ pub struct FindOneAndUpdateOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct AggregateOptions { /// Enables writing to temporary files. When set to true, aggregation stages can write data to /// the _tmp subdirectory in the dbPath directory. @@ -528,7 +556,7 @@ pub struct AggregateOptions { /// number of round trips needed to return the entire set of documents returned by the /// query). #[serde( - serialize_with = "bson_util::serialize_u32_option_as_batch_size", + serialize_with = "serde_util::serialize_u32_option_as_batch_size", rename(serialize = "cursor") )] pub batch_size: Option, @@ -542,22 +570,11 @@ pub struct AggregateOptions { /// information on how to use this option. pub collation: Option, - /// Tags the query with an arbitrary string to help trace the operation through the - /// database profiler, currentOp and logs. - /// - /// If both this option and `comment_bson` are specified, `comment_bson` will take precedence. - // TODO RUST-1364: Update this field to be of type Option - #[serde(skip_serializing)] - pub comment: Option, - /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the /// database profiler, currentOp and logs. /// - /// This option is only supported on server versions 4.4+. Use the `comment` option on - /// older server versions. - // TODO RUST-1364: Remove this field - #[serde(rename(serialize = "comment"))] - pub comment_bson: Option, + /// For server versions less than 4.4, only a string value may be provided. + pub comment: Option, /// The index to use for the operation. pub hint: Option, @@ -568,7 +585,7 @@ pub struct AggregateOptions { /// This option will have no effect on non-tailable cursors that result from this operation. #[serde( skip_serializing, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis", default )] pub max_await_time: Option, @@ -578,9 +595,9 @@ pub struct AggregateOptions { /// This options maps to the `maxTimeMS` MongoDB query option, so the duration will be sent /// across the wire as an integer number of milliseconds. #[serde( - serialize_with = "bson_util::serialize_duration_option_as_int_millis", + serialize_with = "serde_util::serialize_duration_option_as_int_millis", rename = "maxTimeMS", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis", default )] pub max_time: Option, @@ -604,6 +621,7 @@ pub struct AggregateOptions { /// /// If none is specified, the write concern defined on the object executing this operation will /// be used. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// A document with any amount of parameter names, each followed by definitions of constants in @@ -623,6 +641,7 @@ pub struct AggregateOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct CountOptions { /// The index to use for the operation. pub hint: Option, @@ -636,7 +655,7 @@ pub struct CountOptions { /// across the wire as an integer number of milliseconds. #[serde( default, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, @@ -677,6 +696,7 @@ pub struct CountOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct EstimatedDocumentCountOptions { /// The maximum amount of time to allow the query to run. /// @@ -684,9 +704,9 @@ pub struct EstimatedDocumentCountOptions { /// across the wire as an integer number of milliseconds. #[serde( default, - serialize_with = "bson_util::serialize_duration_option_as_int_millis", + serialize_with = "serde_util::serialize_duration_option_as_int_millis", rename = "maxTimeMS", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, @@ -716,6 +736,7 @@ pub struct EstimatedDocumentCountOptions { #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] +#[export_tokens] pub struct DistinctOptions { /// The maximum amount of time to allow the query to run. /// @@ -723,9 +744,9 @@ pub struct DistinctOptions { /// across the wire as an integer number of milliseconds. #[serde( default, - serialize_with = "bson_util::serialize_duration_option_as_int_millis", + serialize_with = "serde_util::serialize_duration_option_as_int_millis", rename = "maxTimeMS", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, @@ -750,6 +771,10 @@ pub struct DistinctOptions { /// /// This option is only available on server versions 4.4+. pub comment: Option, + + /// A document or string that specifies the index to use to support the query predicate. + /// Available on server versions 7.1+. + pub hint: Option, } /// Specifies the options to a [`Collection::find`](../struct.Collection.html#method.find) @@ -759,6 +784,7 @@ pub struct DistinctOptions { #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] +#[export_tokens] pub struct FindOptions { /// Enables writing to temporary files by the server. When set to true, the find operation can /// write data to the _tmp subdirectory in the dbPath directory. Only supported in server @@ -775,25 +801,14 @@ pub struct FindOptions { /// only the number of documents kept in memory at a given time (and by extension, the /// number of round trips needed to return the entire set of documents returned by the /// query. - #[serde(serialize_with = "bson_util::serialize_u32_option_as_i32")] + #[serde(serialize_with = "serde_util::serialize_u32_option_as_i32")] pub batch_size: Option, - /// Tags the query with an arbitrary string to help trace the operation through the - /// database profiler, currentOp and logs. - /// - /// If both this option and `comment_bson` are specified, `comment_bson` will take precedence. - // TODO RUST-1364: Update this field to be of type Option - #[serde(skip_serializing)] - pub comment: Option, - /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the /// database profiler, currentOp and logs. /// - /// This option is only supported on server versions 4.4+. Use the `comment` option on - /// older server versions. - // TODO RUST-1364: Remove this field - #[serde(rename(serialize = "comment"))] - pub comment_bson: Option, + /// For server versions less than 4.4, only a string value may be provided. + pub comment: Option, /// The type of cursor to return. #[serde(skip)] @@ -820,7 +835,7 @@ pub struct FindOptions { /// /// Note: this option is deprecated starting in MongoDB version 4.0 and removed in MongoDB 4.2. /// Use the maxTimeMS option instead. - #[serde(serialize_with = "bson_util::serialize_u64_option_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_option_as_i64")] pub max_scan: Option, /// The maximum amount of time to allow the query to run. @@ -829,7 +844,7 @@ pub struct FindOptions { /// across the wire as an integer number of milliseconds. #[serde( rename = "maxTimeMS", - serialize_with = "bson_util::serialize_duration_option_as_int_millis" + serialize_with = "serde_util::serialize_duration_option_as_int_millis" )] pub max_time: Option, @@ -839,7 +854,13 @@ pub struct FindOptions { /// Whether the server should close the cursor after a period of inactivity. pub no_cursor_timeout: Option, - /// Limits the fields of the document being returned. + /// The [projection document](https://www.mongodb.com/docs/manual/reference/method/db.collection.find/#projection) + /// to determine which fields to include in the returned document. + /// + /// Specifying this option may cause deserialization errors if the returned fields do not fit + /// the schema of the collection's generic type. The + /// [`clone_with_type`](crate::Collection::clone_with_type) method can be used to retrieve a + /// handle to the collection with a different generic type configured. pub projection: Option, /// The read concern to use for this find query. @@ -857,11 +878,14 @@ pub struct FindOptions { #[serde(skip)] pub selection_criteria: Option, - /// Whether to return the record identifier for each document. + /// Whether to return the record identifier for each document. If true, adds a `$recordId` + /// field to the returned documents. + /// + /// Defaults to false. pub show_record_id: Option, /// The number of documents to skip before counting. - #[serde(serialize_with = "bson_util::serialize_u64_option_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_option_as_i64")] pub skip: Option, /// The order of the documents for the purposes of the operation. @@ -889,7 +913,6 @@ impl From for FindOptions { allow_partial_results: options.allow_partial_results, collation: options.collation, comment: options.comment, - comment_bson: options.comment_bson, hint: options.hint, max: options.max, max_scan: options.max_scan, @@ -932,6 +955,7 @@ where #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct FindOneOptions { /// If true, partial results will be returned from a mongos rather than an error being /// returned if one or more shards is down. @@ -943,20 +967,11 @@ pub struct FindOneOptions { /// information on how to use this option. pub collation: Option, - /// Tags the query with an arbitrary string value to help trace the operation through the - /// database profiler, currentOp and logs. - /// - /// If both this option and `comment_bson` are specified, `comment_bson` will take precedence. - // TODO RUST-1364: Update this field to be of type Option - pub comment: Option, - /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the /// database profiler, currentOp and logs. /// - /// This option is only supported on server versions 4.4+. Use the `comment` option on - /// older server versions. - // TODO RUST-1364: Remove this field - pub comment_bson: Option, + /// For server versions less than 4.4, only a string value may be provided. + pub comment: Option, /// The index to use for the operation. pub hint: Option, @@ -977,7 +992,7 @@ pub struct FindOneOptions { /// across the wire as an integer number of milliseconds. #[serde( default, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, @@ -1030,6 +1045,7 @@ pub struct FindOneOptions { #[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[non_exhaustive] +#[export_tokens] pub struct CreateIndexOptions { /// Specify the commit quorum needed to mark an `index` as ready. pub commit_quorum: Option, @@ -1041,12 +1057,13 @@ pub struct CreateIndexOptions { #[serde( rename = "maxTimeMS", default, - serialize_with = "bson_util::serialize_duration_option_as_int_millis", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + serialize_with = "serde_util::serialize_duration_option_as_int_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the @@ -1063,14 +1080,16 @@ pub struct CreateIndexOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct DropCollectionOptions { /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Map of encrypted fields for the collection. // Serialization is skipped because the server doesn't accept this option; it's needed for // preprocessing. Deserialization needs to remain because it's used in test files. - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] #[serde(skip_serializing)] pub encrypted_fields: Option, } @@ -1083,6 +1102,7 @@ pub struct DropCollectionOptions { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct DropIndexOptions { /// The maximum amount of time to allow the index to drop. /// @@ -1091,12 +1111,13 @@ pub struct DropIndexOptions { #[serde( rename = "maxTimeMS", default, - serialize_with = "bson_util::serialize_duration_option_as_int_millis", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + serialize_with = "serde_util::serialize_duration_option_as_int_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the @@ -1113,6 +1134,7 @@ pub struct DropIndexOptions { #[serde(rename_all = "camelCase", deny_unknown_fields)] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct ListIndexesOptions { /// The maximum amount of time to search for the index. /// @@ -1121,8 +1143,8 @@ pub struct ListIndexesOptions { #[serde( rename = "maxTimeMS", default, - serialize_with = "bson_util::serialize_duration_option_as_int_millis", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" + serialize_with = "serde_util::serialize_duration_option_as_int_millis", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" )] pub max_time: Option, @@ -1164,7 +1186,7 @@ impl Serialize for CommitQuorum { S: Serializer, { match self { - CommitQuorum::Nodes(n) => serde_helpers::serialize_u32_as_i32(n, serializer), + CommitQuorum::Nodes(n) => serde_util::serialize_u32_as_i32(n, serializer), CommitQuorum::VotingMembers => serializer.serialize_str("votingMembers"), CommitQuorum::Majority => serializer.serialize_str("majority"), CommitQuorum::Custom(s) => serializer.serialize_str(s), diff --git a/src/collation.rs b/src/collation.rs index bae93d02b..95816cc64 100644 --- a/src/collation.rs +++ b/src/collation.rs @@ -11,46 +11,39 @@ use crate::error::{Error, ErrorKind}; #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Default, Serialize, Deserialize, TypedBuilder)] #[serde(rename_all = "camelCase")] -#[builder(field_defaults(setter(into)))] +#[builder(field_defaults(default, setter(into)))] #[non_exhaustive] pub struct Collation { /// The ICU locale. /// /// See the list of supported languages and locales [here](https://www.mongodb.com/docs/manual/reference/collation-locales-defaults/#collation-languages-locales). + #[builder(!default)] pub locale: String, /// The level of comparison to perform. Corresponds to [ICU Comparison Levels](http://userguide.icu-project.org/collation/concepts#TOC-Comparison-Levels). - #[builder(default)] pub strength: Option, /// Whether to include a separate level for case differences. See [ICU Collation: CaseLevel](http://userguide.icu-project.org/collation/concepts#TOC-CaseLevel) for more information. - #[builder(default)] pub case_level: Option, /// The sort order of case differences during tertiary level comparisons. - #[builder(default)] pub case_first: Option, /// Whether to compare numeric strings as numbers or strings. - #[builder(default)] pub numeric_ordering: Option, /// Whether collation should consider whitespace and punctuation as base characters for /// purposes of comparison. - #[builder(default)] pub alternate: Option, /// Up to which characters are considered ignorable when `alternate` is "shifted". Has no /// effect if `alternate` is set to "non-ignorable". - #[builder(default)] pub max_variable: Option, /// Whether to check if text require normalization and to perform it. - #[builder(default)] pub normalization: Option, /// Whether strings with diacritics sort from the back of the string. - #[builder(default)] pub backwards: Option, } @@ -128,7 +121,7 @@ impl Serialize for CollationStrength { S: serde::Serializer, { let level = u32::from(*self); - serializer.serialize_i32(level as i32) + serializer.serialize_i32(level.try_into().map_err(serde::ser::Error::custom)?) } } diff --git a/src/compression.rs b/src/compression.rs new file mode 100644 index 000000000..0d8f2c748 --- /dev/null +++ b/src/compression.rs @@ -0,0 +1,21 @@ +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +pub(crate) mod compress; +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +pub(crate) mod compressors; +pub(crate) mod decompress; + +const NOOP_COMPRESSOR_ID: u8 = 0; +#[cfg(feature = "snappy-compression")] +const SNAPPY_COMPRESSOR_ID: u8 = 1; +#[cfg(feature = "zlib-compression")] +const ZLIB_COMPRESSOR_ID: u8 = 2; +#[cfg(feature = "zstd-compression")] +const ZSTD_COMPRESSOR_ID: u8 = 3; diff --git a/src/compression/compress.rs b/src/compression/compress.rs new file mode 100644 index 000000000..6715290fd --- /dev/null +++ b/src/compression/compress.rs @@ -0,0 +1,80 @@ +use crate::{ + error::{ErrorKind, Result}, + options::Compressor, +}; + +impl Compressor { + pub(crate) fn compress(&self, flag_bytes: &[u8], section_bytes: &[u8]) -> Result> { + let result = match *self { + #[cfg(feature = "zstd-compression")] + Self::Zstd { level } => compress_zstd(level, flag_bytes, section_bytes), + #[cfg(feature = "zlib-compression")] + Self::Zlib { level } => compress_zlib(level, flag_bytes, section_bytes), + #[cfg(feature = "snappy-compression")] + Self::Snappy => compress_snappy(flag_bytes, section_bytes), + }; + + result.map_err(|error| { + ErrorKind::Internal { + message: format!( + "Failed to compress message with {} compression: {}", + self.name(), + error + ), + } + .into() + }) + } +} + +#[cfg(feature = "zstd-compression")] +fn compress_zstd( + level: Option, + flag_bytes: &[u8], + section_bytes: &[u8], +) -> std::io::Result> { + use std::io::Write; + + use zstd::{Encoder, DEFAULT_COMPRESSION_LEVEL}; + + let level = level.unwrap_or(DEFAULT_COMPRESSION_LEVEL); + let mut encoder = Encoder::new(Vec::new(), level)?; + + encoder.write_all(flag_bytes)?; + encoder.write_all(section_bytes)?; + + encoder.finish() +} + +#[cfg(feature = "zlib-compression")] +fn compress_zlib( + level: Option, + flag_bytes: &[u8], + section_bytes: &[u8], +) -> std::io::Result> { + use std::io::Write; + + use flate2::{write::ZlibEncoder, Compression}; + + let level = match level { + Some(level) => Compression::new(level), + None => Compression::default(), + }; + let mut encoder = ZlibEncoder::new(Vec::new(), level); + + encoder.write_all(flag_bytes)?; + encoder.write_all(section_bytes)?; + + encoder.finish() +} + +#[cfg(feature = "snappy-compression")] +fn compress_snappy(flag_bytes: &[u8], section_bytes: &[u8]) -> std::io::Result> { + use snap::raw::Encoder; + + let mut uncompressed = flag_bytes.to_vec(); + uncompressed.extend_from_slice(section_bytes); + + let mut encoder = Encoder::new(); + Ok(encoder.compress_vec(&uncompressed)?) +} diff --git a/src/compression/compressors.rs b/src/compression/compressors.rs new file mode 100644 index 000000000..9e6bc2e29 --- /dev/null +++ b/src/compression/compressors.rs @@ -0,0 +1,140 @@ +use std::str::FromStr; + +use crate::error::{Error, ErrorKind, Result}; + +/// The compressors that may be used to compress messages sent to and decompress messages returned +/// from the server. Note that each variant requires enabling a corresponding feature flag. +#[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] +pub enum Compressor { + /// `zstd` compression. See [the `zstd` manual](http://facebook.github.io/zstd/zstd_manual.html) + /// for more information. + #[cfg(feature = "zstd-compression")] + Zstd { + /// The compression level to use. It is an error to specify a value outside of the + /// supported compression levels returned by [zstd::compression_level_range]. If no value + /// is specified, the default value ([zstd::DEFAULT_COMPRESSION_LEVEL]) will be used. + /// Higher levels correlate to smaller compression but slower performance. + level: Option, + }, + /// `zlib` compression. See [the `zlib` documentation](https://zlib.net/) for more information. + #[cfg(feature = "zlib-compression")] + Zlib { + /// The compression level to use. If no value is specified, the default value + /// ([flate2::Compression::default]) will be used. Higher levels correlate to smaller + /// compression but slower performance. + level: Option, + }, + /// `snappy` compression. See [the `snappy` documentation](http://google.github.io/snappy/) + /// for more information. + #[cfg(feature = "snappy-compression")] + Snappy, +} + +impl Compressor { + pub(crate) fn name(&self) -> &'static str { + match *self { + #[cfg(feature = "zstd-compression")] + Compressor::Zstd { .. } => "zstd", + #[cfg(feature = "zlib-compression")] + Compressor::Zlib { .. } => "zlib", + #[cfg(feature = "snappy-compression")] + Compressor::Snappy => "snappy", + } + } + + pub(crate) fn id(&self) -> u8 { + match self { + #[cfg(feature = "zstd-compression")] + Self::Zstd { .. } => super::ZSTD_COMPRESSOR_ID, + #[cfg(feature = "zlib-compression")] + Self::Zlib { .. } => super::ZLIB_COMPRESSOR_ID, + #[cfg(feature = "snappy-compression")] + Self::Snappy => super::SNAPPY_COMPRESSOR_ID, + } + } + + pub(crate) fn validate(&self) -> Result<()> { + #[cfg(feature = "zstd-compression")] + if let Self::Zstd { level: Some(level) } = self { + let valid_levels = zstd::compression_level_range(); + if !valid_levels.contains(level) { + return Err(ErrorKind::InvalidArgument { + message: format!( + "Invalid zstd compression level {}: compression level must be within the \ + range {:?}", + level, valid_levels + ), + } + .into()); + } + } + + #[cfg(feature = "zlib-compression")] + if let Self::Zlib { level: Some(level) } = self { + if *level > 9 { + return Err(ErrorKind::InvalidArgument { + message: format!( + "Invalid zlib compression level {}: compression level must be between 0 \ + and 9 (inclusive)", + level + ), + } + .into()); + } + } + + Ok(()) + } + + #[cfg(feature = "zlib-compression")] + pub(crate) fn write_zlib_level(&mut self, uri_level: i32) -> Result<()> { + // This pattern is irrefutable when only zlib-compression is enabled. + #[allow(irrefutable_let_patterns)] + if let Compressor::Zlib { ref mut level } = *self { + if uri_level == -1 { + *level = None; + } else { + let zlib_compression_level = + u32::try_from(uri_level).map_err(|_| ErrorKind::InvalidArgument { + message: format!( + "Invalid zlib compression level specified: {}\nzlib compression level \ + must be a nonnegative integer or -1 to use the default compression \ + level", + uri_level + ), + })?; + *level = Some(zlib_compression_level); + } + } + Ok(()) + } +} + +impl FromStr for Compressor { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + #[cfg(feature = "zstd-compression")] + "zstd" => Ok(Self::Zstd { level: None }), + #[cfg(feature = "zlib-compression")] + "zlib" => Ok(Self::Zlib { level: None }), + #[cfg(feature = "snappy-compression")] + "snappy" => Ok(Self::Snappy), + other if other == "zstd" || other == "zlib" || other == "snappy" => { + Err(ErrorKind::InvalidArgument { + message: format!( + "Enable the {}-compression feature flag to use {} compression", + other, other + ), + } + .into()) + } + other => Err(ErrorKind::InvalidArgument { + message: format!("Unsupported compressor: {}", other), + } + .into()), + } + } +} diff --git a/src/compression/decompress.rs b/src/compression/decompress.rs new file mode 100644 index 000000000..f57f6546b --- /dev/null +++ b/src/compression/decompress.rs @@ -0,0 +1,70 @@ +use crate::error::{ErrorKind, Result}; + +/// Decompresses the given message with the decompression algorithm indicated by the given +/// ID. Returns an error if decompression fails or if the ID is unsupported. +pub(crate) fn decompress_message(message: &[u8], compressor_id: u8) -> Result> { + if compressor_id == super::NOOP_COMPRESSOR_ID { + return Ok(message.into()); + } + + #[cfg(feature = "zstd-compression")] + if compressor_id == super::ZSTD_COMPRESSOR_ID { + return decompress_zstd(message); + } + + #[cfg(feature = "zlib-compression")] + if compressor_id == super::ZLIB_COMPRESSOR_ID { + return decompress_zlib(message); + } + + #[cfg(feature = "snappy-compression")] + if compressor_id == super::SNAPPY_COMPRESSOR_ID { + return decompress_snappy(message); + } + + Err(ErrorKind::InvalidResponse { + message: format!( + "Unsupported compressor ID returned from the server: {}", + compressor_id + ), + } + .into()) +} + +#[cfg(feature = "zstd-compression")] +fn decompress_zstd(message: &[u8]) -> Result> { + let mut decompressed = Vec::new(); + zstd::stream::copy_decode(message, &mut decompressed).map_err(|error| ErrorKind::Internal { + message: format!("Could not decompress message with zstd: {}", error), + })?; + Ok(decompressed) +} + +#[cfg(feature = "zlib-compression")] +fn decompress_zlib(message: &[u8]) -> Result> { + use std::io::Write; + + use flate2::write::ZlibDecoder; + + let mut decoder = ZlibDecoder::new(Vec::new()); + decoder.write_all(message)?; + decoder.finish().map_err(|error| { + ErrorKind::Internal { + message: format!("Could not decompress message with zlib: {}", error), + } + .into() + }) +} + +#[cfg(feature = "snappy-compression")] +fn decompress_snappy(message: &[u8]) -> Result> { + use snap::raw::Decoder; + + let mut decoder = Decoder::new(); + decoder.decompress_vec(message).map_err(|error| { + ErrorKind::Internal { + message: format!("Could not decompress message with snappy: {}", error), + } + .into() + }) +} diff --git a/src/compression/mod.rs b/src/compression/mod.rs deleted file mode 100644 index 7f0071ea2..000000000 --- a/src/compression/mod.rs +++ /dev/null @@ -1,323 +0,0 @@ -#[cfg(any( - feature = "zstd-compression", - feature = "zlib-compression", - feature = "snappy-compression" -))] -#[cfg(test)] -mod test; - -#[cfg(feature = "zlib-compression")] -use flate2::{ - write::{ZlibDecoder, ZlibEncoder}, - Compression, -}; - -#[cfg(feature = "zlib-compression")] -use std::convert::TryInto; - -#[cfg(any( - feature = "zstd-compression", - feature = "zlib-compression", - feature = "snappy-compression" -))] -use std::io::Write; - -use crate::error::{Error, ErrorKind, Result}; - -#[derive(Clone, Debug, PartialEq)] -pub(crate) enum CompressorId { - Noop = 0, - #[cfg(feature = "snappy-compression")] - Snappy = 1, - #[cfg(feature = "zlib-compression")] - Zlib = 2, - #[cfg(feature = "zstd-compression")] - Zstd = 3, -} - -impl CompressorId { - pub(crate) fn from_u8(id: u8) -> Result { - match id { - 0 => Ok(CompressorId::Noop), - #[cfg(feature = "snappy-compression")] - 1 => Ok(CompressorId::Snappy), - #[cfg(feature = "zlib-compression")] - 2 => Ok(CompressorId::Zlib), - #[cfg(feature = "zstd-compression")] - 3 => Ok(CompressorId::Zstd), - other => Err(ErrorKind::InvalidResponse { - message: format!("Invalid compressor id: {}", other), - } - .into()), - } - } -} - -/// Enum representing supported compressor algorithms. -/// Used for compressing and decompressing messages sent to and read from the server. -/// For compressors that take a `level`, use `None` to indicate the default level. -/// Higher `level` indicates more compression (and slower). -/// Requires `zstd-compression` feature flag to use `Zstd` compressor, -/// `zlib-compression` feature flag to use `Zlib` compressor, and -/// `snappy-compression` feature flag to use `Snappy` Compressor. -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub enum Compressor { - /// Zstd compressor. Requires Rust version 1.54. - /// See [`Zstd`](http://facebook.github.io/zstd/zstd_manual.html) for more information - #[cfg(feature = "zstd-compression")] - Zstd { - /// Zstd compression level - level: Option, - }, - /// Zlib compressor. - /// See [`Zlib`](https://zlib.net/) for more information. - #[cfg(feature = "zlib-compression")] - Zlib { - /// Zlib compression level - level: Option, - }, - /// Snappy compressor. - /// See [`Snappy`](http://google.github.io/snappy/) for more information. - #[cfg(feature = "snappy-compression")] - Snappy, -} - -impl Compressor { - #[allow(unused_variables)] - pub(crate) fn write_zlib_level(&mut self, level: i32) { - #[cfg(feature = "zlib-compression")] - if let Compressor::Zlib { - level: ref mut zlib_level, - } = *self - { - *zlib_level = if level == -1 { None } else { Some(level) } - } - } - - pub(crate) fn parse_str(s: &str) -> Result { - match s.to_lowercase().as_str() { - #[cfg(feature = "zlib-compression")] - "zlib" => Ok(Compressor::Zlib { level: None }), - #[cfg(feature = "zstd-compression")] - "zstd" => Ok(Compressor::Zstd { level: None }), - #[cfg(feature = "snappy-compression")] - "snappy" => Ok(Compressor::Snappy), - other => Err(Error::from(ErrorKind::InvalidArgument { - message: format!("Invalid compressor: {} was supplied but is invalid", other), - })), - } - } - - pub(crate) fn name(&self) -> &'static str { - match *self { - #[cfg(feature = "zstd-compression")] - Compressor::Zstd { .. } => "zstd", - #[cfg(feature = "zlib-compression")] - Compressor::Zlib { .. } => "zlib", - #[cfg(feature = "snappy-compression")] - Compressor::Snappy => "snappy", - } - } - - pub(crate) fn id(&self) -> CompressorId { - match *self { - #[cfg(feature = "zstd-compression")] - Compressor::Zstd { level: _ } => CompressorId::Zstd, - #[cfg(feature = "zlib-compression")] - Compressor::Zlib { level: _ } => CompressorId::Zlib, - #[cfg(feature = "snappy-compression")] - Compressor::Snappy => CompressorId::Snappy, - } - } - - pub(crate) fn validate(&self) -> Result<()> { - #[allow(unreachable_patterns)] - match *self { - #[cfg(feature = "zstd-compression")] - Compressor::Zstd { level: Some(level) } - if !zstd::compression_level_range().contains(&level) => - { - Err(Error::from(ErrorKind::InvalidArgument { - message: format!("invalid zstd level: {}", level), - })) - } - #[cfg(feature = "zlib-compression")] - Compressor::Zlib { level: Some(level) } if !(-1..10).contains(&level) => { - Err(Error::from(ErrorKind::InvalidArgument { - message: format!("invalid zlib level: {}", level), - })) - } - _ => Ok(()), - } - } - - pub(crate) fn to_encoder(&self) -> Result { - match *self { - #[cfg(feature = "zstd-compression")] - Compressor::Zstd { level } => { - let encoder = - zstd::Encoder::new(vec![], level.unwrap_or(zstd::DEFAULT_COMPRESSION_LEVEL)) - .map_err(|e| { - Error::from(ErrorKind::Internal { - message: format!( - "an error occurred getting a new zstd encoder: {}", - e - ), - }) - })?; - - Ok(Encoder::Zstd { encoder }) - } - #[cfg(feature = "zlib-compression")] - Compressor::Zlib { level } => { - let level = match level { - Some(level) => Compression::new(level.try_into().map_err(|e| { - Error::from(ErrorKind::Internal { - message: format!("an invalid zlib compression level was given: {}", e), - }) - })?), - _ => Compression::default(), - }; - let encoder = ZlibEncoder::new(vec![], level); - Ok(Encoder::Zlib { encoder }) - } - #[cfg(feature = "snappy-compression")] - Compressor::Snappy => Ok(Encoder::Snappy { bytes: vec![] }), - } - } -} - -pub(crate) enum Encoder { - #[cfg(feature = "zstd-compression")] - Zstd { - encoder: zstd::Encoder<'static, Vec>, - }, - #[cfg(feature = "zlib-compression")] - Zlib { encoder: ZlibEncoder> }, - #[cfg(feature = "snappy-compression")] - Snappy { bytes: Vec }, -} - -#[allow(unused_variables)] -impl Encoder { - pub(crate) fn write_all(&mut self, buf: &[u8]) -> Result<()> { - match *self { - #[cfg(feature = "zstd-compression")] - Encoder::Zstd { ref mut encoder } => encoder.write_all(buf).map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred writing to the zstd encoder: {}", e), - } - .into() - }), - #[cfg(feature = "zlib-compression")] - Encoder::Zlib { ref mut encoder } => encoder.write_all(buf).map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred writing to the zlib encoder: {}", e), - } - .into() - }), - #[cfg(feature = "snappy-compression")] - Encoder::Snappy { ref mut bytes } => bytes.write_all(buf).map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred writing to the snappy encoder: {}", e), - } - .into() - }), - } - } - - pub(crate) fn finish(self) -> Result> { - match self { - #[cfg(feature = "zstd-compression")] - Encoder::Zstd { encoder } => encoder.finish().map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred finishing zstd encoder: {}", e), - } - .into() - }), - #[cfg(feature = "zlib-compression")] - Encoder::Zlib { encoder } => encoder.finish().map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred finishing zlib encoder: {}", e), - } - .into() - }), - #[cfg(feature = "snappy-compression")] - Encoder::Snappy { bytes } => { - // The server doesn't use snappy frame format, so we need to use snap::raw::Encoder - // rather than snap::write::FrameEncoder. Likewise for decoding. - let mut compressor = snap::raw::Encoder::new(); - compressor.compress_vec(bytes.as_slice()).map_err(|e| { - ErrorKind::Internal { - message: format!("an error occurred finishing snappy encoder: {}", e), - } - .into() - }) - } - } - } -} - -#[derive(Clone, Debug)] -pub(crate) enum Decoder { - #[cfg(feature = "zstd-compression")] - Zstd, - #[cfg(feature = "zlib-compression")] - Zlib, - #[cfg(feature = "snappy-compression")] - Snappy, - Noop, -} - -impl Decoder { - pub(crate) fn decode(self, source: &[u8]) -> Result> { - match self { - #[cfg(feature = "zstd-compression")] - Decoder::Zstd => { - let mut ret = Vec::new(); - zstd::stream::copy_decode(source, &mut ret).map_err(|e| { - Error::from(ErrorKind::Internal { - message: format!("Could not decode using zstd decoder: {}", e), - }) - })?; - Ok(ret) - } - #[cfg(feature = "zlib-compression")] - Decoder::Zlib => { - let mut decoder = ZlibDecoder::new(vec![]); - decoder.write_all(source)?; - decoder.finish().map_err(|e| { - ErrorKind::Internal { - message: format!("Could not decode using zlib decoder: {}", e), - } - .into() - }) - } - #[cfg(feature = "snappy-compression")] - Decoder::Snappy => { - let mut decompressor = snap::raw::Decoder::new(); - decompressor.decompress_vec(source).map_err(|e| { - ErrorKind::Internal { - message: format!("Could not decode using snappy decoder: {}", e), - } - .into() - }) - } - Decoder::Noop => Ok(source.to_vec()), - } - } - - pub(crate) fn from_u8(id: u8) -> Result { - let compressor_id = CompressorId::from_u8(id)?; - match compressor_id { - CompressorId::Noop => Ok(Decoder::Noop), - #[cfg(feature = "snappy-compression")] - CompressorId::Snappy => Ok(Decoder::Snappy), - #[cfg(feature = "zlib-compression")] - CompressorId::Zlib => Ok(Decoder::Zlib), - #[cfg(feature = "zstd-compression")] - CompressorId::Zstd => Ok(Decoder::Zstd), - } - } -} diff --git a/src/compression/test.rs b/src/compression/test.rs deleted file mode 100644 index 5749f84c8..000000000 --- a/src/compression/test.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Tests OP_COMPRESSED. To actually test compression you need to look at -// server logs to see if decompression is happening. Even if these tests -// are run against a server that does not support compression -// these tests won't fail because the messages will be sent without compression -// (as indicated in the specs). - -use bson::{doc, Bson}; - -use crate::{ - client::options::ClientOptions, - compression::{Compressor, CompressorId, Decoder}, - test::{TestClient, CLIENT_OPTIONS, LOCK}, -}; - -use tokio::sync::RwLockReadGuard; - -#[cfg(feature = "zlib-compression")] -#[test] -fn test_zlib_compressor() { - let zlib_compressor = Compressor::Zlib { level: Some(4) }; - assert_eq!(CompressorId::Zlib, zlib_compressor.id()); - let mut encoder = zlib_compressor.to_encoder().unwrap(); - assert!(encoder.write_all(b"foo").is_ok()); - assert!(encoder.write_all(b"bar").is_ok()); - assert!(encoder.write_all(b"ZLIB").is_ok()); - - let compressed_bytes = encoder.finish().unwrap(); - - let decoder = Decoder::from_u8(CompressorId::Zlib as u8).unwrap(); - let original_bytes = decoder.decode(compressed_bytes.as_slice()).unwrap(); - assert_eq!(b"foobarZLIB", original_bytes.as_slice()); -} - -#[cfg(feature = "zstd-compression")] -#[test] -fn test_zstd_compressor() { - let zstd_compressor = Compressor::Zstd { level: None }; - assert_eq!(CompressorId::Zstd, zstd_compressor.id()); - let mut encoder = zstd_compressor.to_encoder().unwrap(); - assert!(encoder.write_all(b"foo").is_ok()); - assert!(encoder.write_all(b"bar").is_ok()); - assert!(encoder.write_all(b"ZSTD").is_ok()); - - let compressed_bytes = encoder.finish().unwrap(); - - let decoder = Decoder::from_u8(CompressorId::Zstd as u8).unwrap(); - let original_bytes = decoder.decode(compressed_bytes.as_slice()).unwrap(); - assert_eq!(b"foobarZSTD", original_bytes.as_slice()); -} - -#[cfg(feature = "snappy-compression")] -#[test] -fn test_snappy_compressor() { - let snappy_compressor = Compressor::Snappy; - assert_eq!(CompressorId::Snappy, snappy_compressor.id()); - let mut encoder = snappy_compressor.to_encoder().unwrap(); - assert!(encoder.write_all(b"foo").is_ok()); - assert!(encoder.write_all(b"bar").is_ok()); - assert!(encoder.write_all(b"SNAPPY").is_ok()); - - let compressed_bytes = encoder.finish().unwrap(); - - let decoder = Decoder::from_u8(CompressorId::Snappy as u8).unwrap(); - let original_bytes = decoder.decode(compressed_bytes.as_slice()).unwrap(); - assert_eq!(b"foobarSNAPPY", original_bytes.as_slice()); -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[cfg(feature = "zlib-compression")] -async fn ping_server_with_zlib_compression() { - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - client_options.compressors = Some(vec![Compressor::Zlib { level: Some(4) }]); - send_ping_with_compression(client_options).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[cfg(feature = "zstd-compression")] -async fn ping_server_with_zstd_compression() { - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - client_options.compressors = Some(vec![Compressor::Zstd { level: None }]); - send_ping_with_compression(client_options).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[cfg(feature = "snappy-compression")] -async fn ping_server_with_snappy_compression() { - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - client_options.compressors = Some(vec![Compressor::Snappy]); - send_ping_with_compression(client_options).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[cfg(all( - feature = "zstd-compression", - feature = "zlib-compression", - feature = "snappy-compression" -))] -async fn ping_server_with_all_compressors() { - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - client_options.compressors = Some(vec![ - Compressor::Zlib { level: None }, - Compressor::Snappy, - Compressor::Zstd { level: None }, - ]); - send_ping_with_compression(client_options).await; -} - -async fn send_ping_with_compression(client_options: ClientOptions) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::with_options(Some(client_options)).await; - let ret = client - .database("admin") - .run_command(doc! {"ping": 1}, None) - .await; - - assert!(ret.is_ok()); - let ret = ret.unwrap(); - assert_eq!(ret.get("ok"), Some(Bson::Double(1.0)).as_ref()); -} diff --git a/src/concern.rs b/src/concern.rs new file mode 100644 index 000000000..40b5d436c --- /dev/null +++ b/src/concern.rs @@ -0,0 +1,381 @@ +//! Contains the types for read concerns and write concerns. + +#[cfg(test)] +mod test; + +use std::time::Duration; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde_with::skip_serializing_none; +use typed_builder::TypedBuilder; + +use crate::{ + bson::{doc, Timestamp}, + error::{ErrorKind, Result}, + serde_util, +}; + +/// Specifies the consistency and isolation properties of read operations from replica sets and +/// replica set shards. +/// +/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/read-concern/) for more +/// information about read concerns. +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ReadConcern { + /// The level of the read concern. + pub level: ReadConcernLevel, +} + +/// An internal-only read concern type that allows the omission of a "level" as well as +/// specification of "atClusterTime" and "afterClusterTime". +#[skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +#[serde(rename = "readConcern")] +pub(crate) struct ReadConcernInternal { + /// The level of the read concern. + pub(crate) level: Option, + + /// The snapshot read timestamp. + pub(crate) at_cluster_time: Option, + + /// The time of most recent operation using this session. + /// Used for providing causal consistency. + pub(crate) after_cluster_time: Option, +} + +impl ReadConcern { + /// Creates a read concern with level "majority". + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-majority/). + pub fn majority() -> Self { + ReadConcernLevel::Majority.into() + } + + /// Creates a read concern with level "local". + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-local/). + pub fn local() -> Self { + ReadConcernLevel::Local.into() + } + + /// Creates a read concern with level "linearizable". + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/). + pub fn linearizable() -> Self { + ReadConcernLevel::Linearizable.into() + } + + /// Creates a read concern with level "available". + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-available/). + pub fn available() -> Self { + ReadConcernLevel::Available.into() + } + + /// Creates a read concern with level "snapshot". + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-snapshot/). + pub fn snapshot() -> Self { + ReadConcernLevel::Snapshot.into() + } + + /// Creates a read concern with a custom read concern level. This is present to provide forwards + /// compatibility with any future read concerns which may be added to new versions of + /// MongoDB. + pub fn custom(level: impl AsRef) -> Self { + ReadConcernLevel::from_str(level.as_ref()).into() + } + + #[cfg(test)] + pub(crate) fn serialize_for_client_options( + read_concern: &Option, + serializer: S, + ) -> std::result::Result + where + S: Serializer, + { + #[derive(Serialize)] + struct ReadConcernHelper<'a> { + readconcernlevel: &'a str, + } + + let state = read_concern.as_ref().map(|concern| ReadConcernHelper { + readconcernlevel: concern.level.as_str(), + }); + state.serialize(serializer) + } +} + +impl From for ReadConcernInternal { + fn from(rc: ReadConcern) -> Self { + ReadConcernInternal { + level: Some(rc.level), + at_cluster_time: None, + after_cluster_time: None, + } + } +} + +impl From for ReadConcern { + fn from(level: ReadConcernLevel) -> Self { + Self { level } + } +} + +/// Specifies the level consistency and isolation properties of a given `ReadCocnern`. +/// +/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/read-concern/) for more +/// information about read concerns. +#[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] +pub enum ReadConcernLevel { + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-local/). + Local, + + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-majority/). + Majority, + + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/). + Linearizable, + + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-available/). + Available, + + /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-snapshot/). + Snapshot, + + /// Specify a custom read concern level. This is present to provide forwards compatibility with + /// any future read concerns which may be added to new versions of MongoDB. + Custom(String), +} + +impl ReadConcernLevel { + pub(crate) fn from_str(s: &str) -> Self { + match s { + "local" => ReadConcernLevel::Local, + "majority" => ReadConcernLevel::Majority, + "linearizable" => ReadConcernLevel::Linearizable, + "available" => ReadConcernLevel::Available, + "snapshot" => ReadConcernLevel::Snapshot, + s => ReadConcernLevel::Custom(s.to_string()), + } + } + + /// Gets the string representation of the `ReadConcernLevel`. + pub(crate) fn as_str(&self) -> &str { + match self { + ReadConcernLevel::Local => "local", + ReadConcernLevel::Majority => "majority", + ReadConcernLevel::Linearizable => "linearizable", + ReadConcernLevel::Available => "available", + ReadConcernLevel::Snapshot => "snapshot", + ReadConcernLevel::Custom(ref s) => s, + } + } +} + +impl<'de> Deserialize<'de> for ReadConcernLevel { + fn deserialize>(deserializer: D) -> std::result::Result { + let s = String::deserialize(deserializer)?; + Ok(ReadConcernLevel::from_str(&s)) + } +} + +impl Serialize for ReadConcernLevel { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + self.as_str().serialize(serializer) + } +} + +/// Specifies the level of acknowledgement requested from the server for write operations. +/// +/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/write-concern/) for more +/// information about write concerns. +#[skip_serializing_none] +#[derive(Clone, Debug, Default, PartialEq, TypedBuilder, Serialize, Deserialize)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct WriteConcern { + /// Requests acknowledgement that the operation has propagated to a specific number or variety + /// of servers. + pub w: Option, + + /// Specifies a time limit for the write concern. If an operation has not propagated to the + /// requested level within the time limit, an error will return. + /// + /// Note that an error being returned due to a write concern error does not imply that the + /// write would not have finished propagating if allowed more time to finish, and the + /// server will not roll back the writes that occurred before the timeout was reached. + #[serde(rename = "wtimeout", alias = "wtimeoutMS")] + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + #[serde(deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis")] + #[serde(default)] + pub w_timeout: Option, + + /// Requests acknowledgement that the operation has propagated to the on-disk journal. + #[serde(rename = "j", alias = "journal")] + pub journal: Option, +} + +/// The type of the `w` field in a [`WriteConcern`](struct.WriteConcern.html). +#[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] +pub enum Acknowledgment { + /// Requires acknowledgement that the write has reached the specified number of nodes. + /// + /// Note: specifying 0 here indicates that the write concern is unacknowledged, which is + /// currently unsupported and will result in an error during operation execution. + Nodes(u32), + + /// Requires acknowledgement that the write has reached the majority of nodes. + Majority, + + /// Requires acknowledgement according to the given custom write concern. See [here](https://www.mongodb.com/docs/manual/tutorial/configure-replica-set-tag-sets/#tag-sets-and-custom-write-concern-behavior) + /// for more information. + Custom(String), +} + +impl Serialize for Acknowledgment { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + match self { + Acknowledgment::Majority => serializer.serialize_str("majority"), + Acknowledgment::Nodes(n) => serde_util::serialize_u32_as_i32(n, serializer), + Acknowledgment::Custom(name) => serializer.serialize_str(name), + } + } +} + +impl<'de> Deserialize<'de> for Acknowledgment { + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum IntOrString { + Int(u32), + String(String), + } + match IntOrString::deserialize(deserializer)? { + IntOrString::String(s) => Ok(s.into()), + IntOrString::Int(i) => Ok(i.into()), + } + } +} + +impl From for Acknowledgment { + fn from(i: u32) -> Self { + Acknowledgment::Nodes(i) + } +} + +impl From<&str> for Acknowledgment { + fn from(s: &str) -> Self { + if s == "majority" { + Acknowledgment::Majority + } else { + Acknowledgment::Custom(s.to_string()) + } + } +} + +impl From for Acknowledgment { + fn from(s: String) -> Self { + if s == "majority" { + Acknowledgment::Majority + } else { + Acknowledgment::Custom(s) + } + } +} + +impl WriteConcern { + /// A 'WriteConcern' requesting [`Acknowledgment::Nodes`]. + pub fn nodes(v: u32) -> Self { + Acknowledgment::Nodes(v).into() + } + + /// A `WriteConcern` requesting [`Acknowledgment::Majority`]. + pub fn majority() -> Self { + Acknowledgment::Majority.into() + } + + /// A `WriteConcern` with a custom acknowledgment. + pub fn custom(s: impl AsRef) -> Self { + Acknowledgment::from(s.as_ref()).into() + } + + pub(crate) fn is_acknowledged(&self) -> bool { + self.w != Some(Acknowledgment::Nodes(0)) || self.journal == Some(true) + } + + /// Whether the write concern was created with no values specified. If true, the write concern + /// should be considered the server's default. + pub(crate) fn is_empty(&self) -> bool { + self.w.is_none() && self.w_timeout.is_none() && self.journal.is_none() + } + + /// Validates that the write concern. A write concern is invalid if both the `w` field is 0 + /// and the `j` field is `true`. + pub(crate) fn validate(&self) -> Result<()> { + if self.w == Some(Acknowledgment::Nodes(0)) && self.journal == Some(true) { + return Err(ErrorKind::InvalidArgument { + message: "write concern cannot have w=0 and j=true".to_string(), + } + .into()); + } + + if let Some(w_timeout) = self.w_timeout { + if w_timeout < Duration::from_millis(0) { + return Err(ErrorKind::InvalidArgument { + message: "write concern `w_timeout` field cannot be negative".to_string(), + } + .into()); + } + } + + Ok(()) + } + + #[cfg(test)] + pub(crate) fn serialize_for_client_options( + write_concern: &Option, + serializer: S, + ) -> std::result::Result + where + S: Serializer, + { + #[derive(Serialize)] + struct WriteConcernHelper<'a> { + w: Option<&'a Acknowledgment>, + + #[serde(serialize_with = "serde_util::serialize_duration_option_as_int_millis")] + wtimeoutms: Option, + + journal: Option, + } + + let state = write_concern.as_ref().map(|concern| WriteConcernHelper { + w: concern.w.as_ref(), + wtimeoutms: concern.w_timeout, + journal: concern.journal, + }); + + state.serialize(serializer) + } +} + +impl From for WriteConcern { + fn from(w: Acknowledgment) -> Self { + WriteConcern { + w: Some(w), + w_timeout: None, + journal: None, + } + } +} diff --git a/src/concern/mod.rs b/src/concern/mod.rs deleted file mode 100644 index 8079b350c..000000000 --- a/src/concern/mod.rs +++ /dev/null @@ -1,383 +0,0 @@ -//! Contains the types for read concerns and write concerns. - -#[cfg(test)] -mod test; - -use std::time::Duration; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use serde_with::skip_serializing_none; -use typed_builder::TypedBuilder; - -use crate::{ - bson::{doc, serde_helpers, Timestamp}, - bson_util, - error::{ErrorKind, Result}, -}; - -/// Specifies the consistency and isolation properties of read operations from replica sets and -/// replica set shards. -/// -/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/read-concern/) for more -/// information about read concerns. -#[skip_serializing_none] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ReadConcern { - /// The level of the read concern. - pub level: ReadConcernLevel, -} - -impl ReadConcern { - /// A `ReadConcern` with level `ReadConcernLevel::Local`. - pub const LOCAL: ReadConcern = ReadConcern { - level: ReadConcernLevel::Local, - }; - /// A `ReadConcern` with level `ReadConcernLevel::Majority`. - pub const MAJORITY: ReadConcern = ReadConcern { - level: ReadConcernLevel::Majority, - }; - /// A `ReadConcern` with level `ReadConcernLevel::Linearizable`. - pub const LINEARIZABLE: ReadConcern = ReadConcern { - level: ReadConcernLevel::Linearizable, - }; - /// A `ReadConcern` with level `ReadConcernLevel::Available`. - pub const AVAILABLE: ReadConcern = ReadConcern { - level: ReadConcernLevel::Available, - }; - /// A `ReadConcern` with level `ReadConcernLevel::Snapshot`. - pub const SNAPSHOT: ReadConcern = ReadConcern { - level: ReadConcernLevel::Snapshot, - }; -} - -/// An internal-only read concern type that allows the omission of a "level" as well as -/// specification of "atClusterTime" and "afterClusterTime". -#[skip_serializing_none] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] -#[serde(rename_all = "camelCase")] -#[serde(rename = "readConcern")] -pub(crate) struct ReadConcernInternal { - /// The level of the read concern. - pub(crate) level: Option, - - /// The snapshot read timestamp. - pub(crate) at_cluster_time: Option, - - /// The time of most recent operation using this session. - /// Used for providing causal consistency. - pub(crate) after_cluster_time: Option, -} - -impl ReadConcern { - /// Creates a read concern with level "majority". - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-majority/). - pub fn majority() -> Self { - ReadConcernLevel::Majority.into() - } - - /// Creates a read concern with level "local". - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-local/). - pub fn local() -> Self { - ReadConcernLevel::Local.into() - } - - /// Creates a read concern with level "linearizable". - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/). - pub fn linearizable() -> Self { - ReadConcernLevel::Linearizable.into() - } - - /// Creates a read concern with level "available". - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-available/). - pub fn available() -> Self { - ReadConcernLevel::Available.into() - } - - /// Creates a read concern with level "snapshot". - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-snapshot/). - pub fn snapshot() -> Self { - ReadConcernLevel::Snapshot.into() - } - - /// Creates a read concern with a custom read concern level. This is present to provide forwards - /// compatibility with any future read concerns which may be added to new versions of - /// MongoDB. - pub fn custom(level: String) -> Self { - ReadConcernLevel::from_str(level.as_str()).into() - } - - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - read_concern: &Option, - serializer: S, - ) -> std::result::Result - where - S: Serializer, - { - #[derive(Serialize)] - struct ReadConcernHelper<'a> { - readconcernlevel: &'a str, - } - - let state = read_concern.as_ref().map(|concern| ReadConcernHelper { - readconcernlevel: concern.level.as_str(), - }); - state.serialize(serializer) - } -} - -impl From for ReadConcernInternal { - fn from(rc: ReadConcern) -> Self { - ReadConcernInternal { - level: Some(rc.level), - at_cluster_time: None, - after_cluster_time: None, - } - } -} - -impl From for ReadConcern { - fn from(level: ReadConcernLevel) -> Self { - Self { level } - } -} - -/// Specifies the level consistency and isolation properties of a given `ReadCocnern`. -/// -/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/read-concern/) for more -/// information about read concerns. -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub enum ReadConcernLevel { - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-local/). - Local, - - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-majority/). - Majority, - - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-linearizable/). - Linearizable, - - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-available/). - Available, - - /// See the specific documentation for this read concern level [here](https://www.mongodb.com/docs/manual/reference/read-concern-snapshot/). - Snapshot, - - /// Specify a custom read concern level. This is present to provide forwards compatibility with - /// any future read concerns which may be added to new versions of MongoDB. - Custom(String), -} - -impl ReadConcernLevel { - pub(crate) fn from_str(s: &str) -> Self { - match s { - "local" => ReadConcernLevel::Local, - "majority" => ReadConcernLevel::Majority, - "linearizable" => ReadConcernLevel::Linearizable, - "available" => ReadConcernLevel::Available, - "snapshot" => ReadConcernLevel::Snapshot, - s => ReadConcernLevel::Custom(s.to_string()), - } - } - - /// Gets the string representation of the `ReadConcernLevel`. - pub(crate) fn as_str(&self) -> &str { - match self { - ReadConcernLevel::Local => "local", - ReadConcernLevel::Majority => "majority", - ReadConcernLevel::Linearizable => "linearizable", - ReadConcernLevel::Available => "available", - ReadConcernLevel::Snapshot => "snapshot", - ReadConcernLevel::Custom(ref s) => s, - } - } -} - -impl<'de> Deserialize<'de> for ReadConcernLevel { - fn deserialize>(deserializer: D) -> std::result::Result { - let s = String::deserialize(deserializer)?; - Ok(ReadConcernLevel::from_str(&s)) - } -} - -impl Serialize for ReadConcernLevel { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - self.as_str().serialize(serializer) - } -} - -/// Specifies the level of acknowledgement requested from the server for write operations. -/// -/// See the documentation [here](https://www.mongodb.com/docs/manual/reference/write-concern/) for more -/// information about write concerns. -#[skip_serializing_none] -#[derive(Clone, Debug, Default, PartialEq, TypedBuilder, Serialize, Deserialize)] -#[builder(field_defaults(default, setter(into)))] -#[non_exhaustive] -pub struct WriteConcern { - /// Requests acknowledgement that the operation has propagated to a specific number or variety - /// of servers. - pub w: Option, - - /// Specifies a time limit for the write concern. If an operation has not propagated to the - /// requested level within the time limit, an error will return. - /// - /// Note that an error being returned due to a write concern error does not imply that the - /// write would not have finished propagating if allowed more time to finish, and the - /// server will not roll back the writes that occurred before the timeout was reached. - #[serde(rename = "wtimeout", alias = "wtimeoutMS")] - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - #[serde(deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis")] - #[serde(default)] - pub w_timeout: Option, - - /// Requests acknowledgement that the operation has propagated to the on-disk journal. - #[serde(rename = "j", alias = "journal")] - pub journal: Option, -} - -impl WriteConcern { - // Can't use `Default::default()` in const contexts yet :( - const DEFAULT: WriteConcern = WriteConcern { - w: None, - w_timeout: None, - journal: None, - }; - /// A `WriteConcern` requesting `Acknowledgment::Majority`. - pub const MAJORITY: WriteConcern = WriteConcern { - w: Some(Acknowledgment::Majority), - ..WriteConcern::DEFAULT - }; -} - -/// The type of the `w` field in a [`WriteConcern`](struct.WriteConcern.html). -#[derive(Clone, Debug, PartialEq)] -#[non_exhaustive] -pub enum Acknowledgment { - /// Requires acknowledgement that the write has reached the specified number of nodes. - /// - /// Note: specifying 0 here indicates that the write concern is unacknowledged, which is - /// currently unsupported and will result in an error during operation execution. - Nodes(u32), - - /// Requires acknowledgement that the write has reached the majority of nodes. - Majority, - - /// Requires acknowledgement according to the given custom write concern. See [here](https://www.mongodb.com/docs/manual/tutorial/configure-replica-set-tag-sets/#tag-sets-and-custom-write-concern-behavior) - /// for more information. - Custom(String), -} - -impl Serialize for Acknowledgment { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - match self { - Acknowledgment::Majority => serializer.serialize_str("majority"), - Acknowledgment::Nodes(n) => serde_helpers::serialize_u32_as_i32(n, serializer), - Acknowledgment::Custom(name) => serializer.serialize_str(name), - } - } -} - -impl<'de> Deserialize<'de> for Acknowledgment { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum IntOrString { - Int(u32), - String(String), - } - match IntOrString::deserialize(deserializer)? { - IntOrString::String(s) => Ok(s.into()), - IntOrString::Int(i) => Ok(i.into()), - } - } -} - -impl From for Acknowledgment { - fn from(i: u32) -> Self { - Acknowledgment::Nodes(i) - } -} - -impl From for Acknowledgment { - fn from(s: String) -> Self { - if s == "majority" { - Acknowledgment::Majority - } else { - Acknowledgment::Custom(s) - } - } -} - -impl WriteConcern { - pub(crate) fn is_acknowledged(&self) -> bool { - self.w != Some(Acknowledgment::Nodes(0)) || self.journal == Some(true) - } - - /// Whether the write concern was created with no values specified. If true, the write concern - /// should be considered the server's default. - pub(crate) fn is_empty(&self) -> bool { - self.w.is_none() && self.w_timeout.is_none() && self.journal.is_none() - } - - /// Validates that the write concern. A write concern is invalid if both the `w` field is 0 - /// and the `j` field is `true`. - pub(crate) fn validate(&self) -> Result<()> { - if self.w == Some(Acknowledgment::Nodes(0)) && self.journal == Some(true) { - return Err(ErrorKind::InvalidArgument { - message: "write concern cannot have w=0 and j=true".to_string(), - } - .into()); - } - - if let Some(w_timeout) = self.w_timeout { - if w_timeout < Duration::from_millis(0) { - return Err(ErrorKind::InvalidArgument { - message: "write concern `w_timeout` field cannot be negative".to_string(), - } - .into()); - } - } - - Ok(()) - } - - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - write_concern: &Option, - serializer: S, - ) -> std::result::Result - where - S: Serializer, - { - #[derive(Serialize)] - struct WriteConcernHelper<'a> { - w: Option<&'a Acknowledgment>, - - #[serde(serialize_with = "bson_util::serialize_duration_option_as_int_millis")] - wtimeoutms: Option, - - journal: Option, - } - - let state = write_concern.as_ref().map(|concern| WriteConcernHelper { - w: concern.w.as_ref(), - wtimeoutms: concern.w_timeout, - journal: concern.journal, - }); - - state.serialize(serializer) - } -} diff --git a/src/concern/test.rs b/src/concern/test.rs index d5e094cc9..d897c95fe 100644 --- a/src/concern/test.rs +++ b/src/concern/test.rs @@ -1,28 +1,11 @@ use std::time::Duration; -use tokio::sync::RwLockReadGuard; use crate::{ bson::{doc, Bson, Document}, error::ErrorKind, - options::{ - Acknowledgment, - AggregateOptions, - CreateCollectionOptions, - DeleteOptions, - DropCollectionOptions, - FindOneAndDeleteOptions, - FindOneAndReplaceOptions, - FindOneAndUpdateOptions, - FindOneOptions, - InsertManyOptions, - InsertOneOptions, - ReadConcern, - ReplaceOptions, - TransactionOptions, - UpdateOptions, - WriteConcern, - }, - test::{EventClient, TestClient, LOCK}, + options::{Acknowledgment, ReadConcern, WriteConcern}, + test::{server_version_lt, transactions_supported, EventClient}, + Client, Collection, }; @@ -62,7 +45,7 @@ fn write_concern_is_acknowledged() { #[test] fn write_concern_deserialize() { let w_1 = doc! { "w": 1 }; - let wc: WriteConcern = bson::from_bson(Bson::Document(w_1)).unwrap(); + let wc: WriteConcern = crate::bson_compat::deserialize_from_bson(Bson::Document(w_1)).unwrap(); assert_eq!( wc, WriteConcern { @@ -73,7 +56,8 @@ fn write_concern_deserialize() { ); let w_majority = doc! { "w": "majority" }; - let wc: WriteConcern = bson::from_bson(Bson::Document(w_majority)).unwrap(); + let wc: WriteConcern = + crate::bson_compat::deserialize_from_bson(Bson::Document(w_majority)).unwrap(); assert_eq!( wc, WriteConcern { @@ -84,7 +68,8 @@ fn write_concern_deserialize() { ); let w_timeout = doc! { "w": "majority", "wtimeout": 100 }; - let wc: WriteConcern = bson::from_bson(Bson::Document(w_timeout)).unwrap(); + let wc: WriteConcern = + crate::bson_compat::deserialize_from_bson(Bson::Document(w_timeout)).unwrap(); assert_eq!( wc, WriteConcern { @@ -95,7 +80,8 @@ fn write_concern_deserialize() { ); let journal = doc! { "w": "majority", "j": true }; - let wc: WriteConcern = bson::from_bson(Bson::Document(journal)).unwrap(); + let wc: WriteConcern = + crate::bson_compat::deserialize_from_bson(Bson::Document(journal)).unwrap(); assert_eq!( wc, WriteConcern { @@ -106,26 +92,11 @@ fn write_concern_deserialize() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn inconsistent_write_concern_rejected() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - let error = db - .run_command( - doc! { - "insert": function_name!(), - "documents": [ {} ], - "writeConcern": { "w": 0, "j": true } - }, - None, - ) - .await - .expect_err("insert should fail"); - assert!(matches!(*error.kind, ErrorKind::InvalidArgument { .. })); let coll = db.collection(function_name!()); let wc = WriteConcern { @@ -133,69 +104,63 @@ async fn inconsistent_write_concern_rejected() { journal: true.into(), w_timeout: None, }; - let options = InsertOneOptions::builder().write_concern(wc).build(); let error = coll - .insert_one(doc! {}, options) + .insert_one(doc! {}) + .write_concern(wc) .await .expect_err("insert should fail"); assert!(matches!(*error.kind, ErrorKind::InvalidArgument { .. })); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn unacknowledged_write_concern_rejected() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - let error = db - .run_command( - doc! { - "insert": function_name!(), - "documents": [ {} ], - "writeConcern": { "w": 0 } - }, - None, - ) + let coll = db.collection(function_name!()); + let wc = WriteConcern { + w: Acknowledgment::Nodes(0).into(), + journal: false.into(), + w_timeout: None, + }; + let error = coll + .insert_one(doc! {}) + .write_concern(wc) .await .expect_err("insert should fail"); assert!(matches!(*error.kind, ErrorKind::InvalidArgument { .. })); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn snapshot_read_concern() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; // snapshot read concern was introduced in 4.0 - if client.server_version_lt(4, 0) { + if server_version_lt(4, 0).await { return; } + let client = Client::for_test().monitor_events().await; + let coll = client .database(function_name!()) .collection::(function_name!()); - if client.supports_transactions() { - let mut session = client.start_session(None).await.unwrap(); - let options = TransactionOptions::builder() + if transactions_supported().await { + let mut session = client.start_session().await.unwrap(); + session + .start_transaction() .read_concern(ReadConcern::snapshot()) - .build(); - session.start_transaction(options).await.unwrap(); - let result = coll.find_one_with_session(None, None, &mut session).await; + .await + .unwrap(); + let result = coll.find_one(doc! {}).session(&mut session).await; assert!(result.is_ok()); assert_event_contains_read_concern(&client).await; } - if client.server_version_lt(4, 9) { - let options = FindOneOptions::builder() - .read_concern(ReadConcern::snapshot()) - .build(); + if server_version_lt(4, 9).await { let error = coll - .find_one(None, options) + .find_one(doc! {}) + .read_concern(ReadConcern::snapshot()) .await .expect_err("non-transaction find one with snapshot read concern should fail"); // ensure that an error from the server is returned @@ -206,6 +171,7 @@ async fn snapshot_read_concern() { async fn assert_event_contains_read_concern(client: &EventClient) { let event = client + .events .get_command_started_events(&["find"]) .into_iter() .next() @@ -221,41 +187,31 @@ async fn assert_event_contains_read_concern(client: &EventClient) { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_insert_one() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_one( - doc! { "foo": "bar" }, - InsertOneOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.insert_one( - doc! { "foo": "bar" }, - InsertOneOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + coll.insert_one(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.insert_one(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "insert"), @@ -272,41 +228,31 @@ async fn command_contains_write_concern_insert_one() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_insert_many() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many( - &[doc! { "foo": "bar" }], - InsertManyOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.insert_many( - &[doc! { "foo": "bar" }], - InsertManyOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }]) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }]) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "insert"), @@ -323,44 +269,32 @@ async fn command_contains_write_concern_insert_many() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_update_one() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_one(doc! { "foo": "bar" }, None).await.unwrap(); - coll.update_one( - doc! { "foo": "bar" }, - doc! { "$set": { "foo": "baz" } }, - UpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.update_one( - doc! { "foo": "baz" }, - doc! { "$set": { "foo": "quux" } }, - UpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + coll.insert_one(doc! { "foo": "bar" }).await.unwrap(); + coll.update_one(doc! { "foo": "bar" }, doc! { "$set": { "foo": "baz" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.update_one(doc! { "foo": "baz" }, doc! { "$set": { "foo": "quux" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "update"), @@ -377,46 +311,34 @@ async fn command_contains_write_concern_update_one() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_update_many() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.update_many(doc! { "foo": "bar" }, doc! { "$set": { "foo": "baz" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.update_many(doc! { "foo": "baz" }, doc! { "$set": { "foo": "quux" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.update_many( - doc! { "foo": "bar" }, - doc! { "$set": { "foo": "baz" } }, - UpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.update_many( - doc! { "foo": "baz" }, - doc! { "$set": { "foo": "quux" } }, - UpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "update"), @@ -433,44 +355,32 @@ async fn command_contains_write_concern_update_many() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_replace_one() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_one(doc! { "foo": "bar" }, None).await.unwrap(); - coll.replace_one( - doc! { "foo": "bar" }, - doc! { "baz": "fun" }, - ReplaceOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.replace_one( - doc! { "foo": "bar" }, - doc! { "baz": "fun" }, - ReplaceOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + coll.insert_one(doc! { "foo": "bar" }).await.unwrap(); + coll.replace_one(doc! { "foo": "bar" }, doc! { "baz": "fun" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.replace_one(doc! { "foo": "bar" }, doc! { "baz": "fun" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "update"), @@ -487,44 +397,34 @@ async fn command_contains_write_concern_replace_one() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_delete_one() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.delete_one(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.delete_one(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.delete_one( - doc! { "foo": "bar" }, - DeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.delete_one( - doc! { "foo": "bar" }, - DeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "delete"), @@ -541,47 +441,37 @@ async fn command_contains_write_concern_delete_one() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_delete_many() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.delete_many(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.delete_many(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.delete_many( - doc! { "foo": "bar" }, - DeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) - .await - .unwrap(); - coll.delete_many( - doc! { "foo": "bar" }, - DeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "delete"), @@ -598,44 +488,34 @@ async fn command_contains_write_concern_delete_many() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_find_one_and_delete() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.find_one_and_delete(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.find_one_and_delete(doc! { "foo": "bar" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.find_one_and_delete( - doc! { "foo": "bar" }, - FindOneAndDeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.find_one_and_delete( - doc! { "foo": "bar" }, - FindOneAndDeleteOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "findAndModify"), @@ -652,46 +532,34 @@ async fn command_contains_write_concern_find_one_and_delete() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_find_one_and_replace() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.find_one_and_replace(doc! { "foo": "bar" }, doc! { "baz": "fun" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.find_one_and_replace(doc! { "foo": "bar" }, doc! { "baz": "fun" }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.find_one_and_replace( - doc! { "foo": "bar" }, - doc! { "baz": "fun" }, - FindOneAndReplaceOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.find_one_and_replace( - doc! { "foo": "bar" }, - doc! { "baz": "fun" }, - FindOneAndReplaceOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "findAndModify"), @@ -708,46 +576,34 @@ async fn command_contains_write_concern_find_one_and_replace() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_find_one_and_update() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }], None) + coll.drop().await.unwrap(); + coll.insert_many(&[doc! { "foo": "bar" }, doc! { "foo": "bar" }]) + .await + .unwrap(); + coll.find_one_and_update(doc! { "foo": "bar" }, doc! { "$set": { "foo": "fun" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.find_one_and_update(doc! { "foo": "bar" }, doc! { "$set": { "foo": "fun" } }) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) .await .unwrap(); - coll.find_one_and_update( - doc! { "foo": "bar" }, - doc! { "$set": { "foo": "fun" } }, - FindOneAndUpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.find_one_and_update( - doc! { "foo": "bar" }, - doc! { "$set": { "foo": "fun" } }, - FindOneAndUpdateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); assert_eq!( command_write_concerns(&client, "findAndModify"), @@ -764,46 +620,36 @@ async fn command_contains_write_concern_find_one_and_update() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_aggregate() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - coll.insert_one(doc! { "foo": "bar" }, None).await.unwrap(); - coll.aggregate( - vec![ - doc! { "$match": { "foo": "bar" } }, - doc! { "$addFields": { "foo": "baz" } }, - doc! { "$out": format!("{}-out", function_name!()) }, - ], - AggregateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) + coll.drop().await.unwrap(); + coll.insert_one(doc! { "foo": "bar" }).await.unwrap(); + coll.aggregate(vec![ + doc! { "$match": { "foo": "bar" } }, + doc! { "$addFields": { "foo": "baz" } }, + doc! { "$out": format!("{}-out", function_name!()) }, + ]) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) .build(), ) .await .unwrap(); - coll.aggregate( - vec![ - doc! { "$match": { "foo": "bar" } }, - doc! { "$addFields": { "foo": "baz" } }, - doc! { "$out": format!("{}-out", function_name!()) }, - ], - AggregateOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) + coll.aggregate(vec![ + doc! { "$match": { "foo": "bar" } }, + doc! { "$addFields": { "foo": "baz" } }, + doc! { "$out": format!("{}-out", function_name!()) }, + ]) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) .build(), ) .await @@ -824,42 +670,36 @@ async fn command_contains_write_concern_aggregate() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_drop() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll: Collection = client.database("test").collection(function_name!()); - coll.drop(None).await.unwrap(); - client.clear_cached_events(); - coll.insert_one(doc! { "foo": "bar" }, None).await.unwrap(); - coll.drop( - DropCollectionOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.insert_one(doc! { "foo": "bar" }, None).await.unwrap(); - coll.drop( - DropCollectionOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + + let mut events = client.events.clone(); + events.clear_cached_events(); + coll.insert_one(doc! { "foo": "bar" }).await.unwrap(); + coll.drop() + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.insert_one(doc! { "foo": "bar" }).await.unwrap(); + coll.drop() + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "drop"), @@ -876,43 +716,33 @@ async fn command_contains_write_concern_drop() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn command_contains_write_concern_create_collection() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let db = client.database("test"); let coll: Collection = db.collection(function_name!()); - coll.drop(None).await.unwrap(); - db.create_collection( - function_name!(), - CreateCollectionOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(true) - .build(), - ) - .build(), - ) - .await - .unwrap(); - coll.drop(None).await.unwrap(); - db.create_collection( - function_name!(), - CreateCollectionOptions::builder() - .write_concern( - WriteConcern::builder() - .w(Acknowledgment::Nodes(1)) - .journal(false) - .build(), - ) - .build(), - ) - .await - .unwrap(); + coll.drop().await.unwrap(); + db.create_collection(function_name!()) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(true) + .build(), + ) + .await + .unwrap(); + coll.drop().await.unwrap(); + db.create_collection(function_name!()) + .write_concern( + WriteConcern::builder() + .w(Acknowledgment::Nodes(1)) + .journal(false) + .build(), + ) + .await + .unwrap(); assert_eq!( command_write_concerns(&client, "create"), @@ -931,6 +761,7 @@ async fn command_contains_write_concern_create_collection() { fn command_write_concerns(client: &EventClient, key: &str) -> Vec { client + .events .get_command_started_events(&[key]) .into_iter() .map(|d| d.command.get_document("writeConcern").unwrap().clone()) diff --git a/src/cursor.rs b/src/cursor.rs new file mode 100644 index 000000000..7080235a7 --- /dev/null +++ b/src/cursor.rs @@ -0,0 +1,360 @@ +mod common; +pub(crate) mod session; + +#[cfg(test)] +use std::collections::VecDeque; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use crate::bson::RawDocument; + +#[cfg(test)] +use crate::bson::RawDocumentBuf; +use derive_where::derive_where; +use futures_core::Stream; +use serde::{de::DeserializeOwned, Deserialize}; +#[cfg(test)] +use tokio::sync::oneshot; + +use crate::{ + change_stream::event::ResumeToken, + client::{options::ServerAddress, AsyncDropToken}, + cmap::conn::PinnedConnectionHandle, + cursor::common::ImplicitClientSessionHandle, + error::{Error, Result}, + Client, + ClientSession, +}; +use common::{kill_cursor, GenericCursor}; +pub(crate) use common::{ + stream_poll_next, + BatchValue, + CursorInformation, + CursorSpecification, + CursorStream, + NextInBatchFuture, + PinnedConnection, +}; + +/// A [`Cursor`] streams the result of a query. When a query is made, the returned [`Cursor`] will +/// contain the first batch of results from the server; the individual results will then be returned +/// as the [`Cursor`] is iterated. When the batch is exhausted and if there are more results, the +/// [`Cursor`] will fetch the next batch of documents, and so forth until the results are exhausted. +/// Note that because of this batching, additional network I/O may occur on any given call to +/// `next`. Because of this, a [`Cursor`] iterates over `Result` items rather than +/// simply `T` items. +/// +/// The batch size of the `Cursor` can be configured using the options to the method that returns +/// it. For example, setting the `batch_size` field of +/// [`FindOptions`](options/struct.FindOptions.html) will set the batch size of the +/// `Cursor` returned by [`Collection::find`](struct.Collection.html#method.find). +/// +/// Note that the batch size determines both the number of documents stored in memory by the +/// `Cursor` at a given time as well as the total number of network round-trips needed to fetch all +/// results from the server; both of these factors should be taken into account when choosing the +/// optimal batch size. +/// +/// [`Cursor`] implements [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html), which means +/// it can be iterated over much in the same way that an `Iterator` can be in synchronous Rust. In +/// order to do so, the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) trait must +/// be imported. Because a [`Cursor`] iterates over a `Result`, it also has access to the +/// potentially more ergonomic functionality provided by +/// [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html), which can be +/// imported instead of or in addition to +/// [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html). The methods from +/// [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html) are especially useful when +/// used in conjunction with the `?` operator. +/// +/// ```rust +/// # use mongodb::{bson::{Document, doc}, Client, error::Result}; +/// # +/// # async fn do_stuff() -> Result<()> { +/// # let client = Client::with_uri_str("mongodb://example.com").await?; +/// # let coll = client.database("foo").collection::("bar"); +/// # +/// use futures::stream::{StreamExt, TryStreamExt}; +/// +/// let mut cursor = coll.find(doc! {}).await?; +/// // regular Stream uses next() and iterates over Option> +/// while let Some(doc) = cursor.next().await { +/// println!("{}", doc?) +/// } +/// // regular Stream uses collect() and collects into a Vec> +/// let v: Vec> = cursor.collect().await; +/// +/// let mut cursor = coll.find(doc! {}).await?; +/// // TryStream uses try_next() and iterates over Result> +/// while let Some(doc) = cursor.try_next().await? { +/// println!("{}", doc) +/// } +/// // TryStream uses try_collect() and collects into a Result> +/// let v: Vec<_> = cursor.try_collect().await?; +/// # +/// # Ok(()) +/// # } +/// ``` +/// +/// If a [`Cursor`] is still open when it goes out of scope, it will automatically be closed via an +/// asynchronous [killCursors](https://www.mongodb.com/docs/manual/reference/command/killCursors/) command executed +/// from its [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation. +#[derive_where(Debug)] +pub struct Cursor { + client: Client, + drop_token: AsyncDropToken, + // `wrapped_cursor` is an `Option` so that it can be `None` for the `drop` impl for a cursor + // that's had `with_type` called; in all other circumstances it will be `Some`. + wrapped_cursor: Option, + drop_address: Option, + #[cfg(test)] + kill_watcher: Option>, + #[derive_where(skip)] + _phantom: std::marker::PhantomData T>, +} + +impl Cursor { + pub(crate) fn new( + client: Client, + spec: CursorSpecification, + session: Option, + pin: Option, + ) -> Self { + Self { + client: client.clone(), + drop_token: client.register_async_drop(), + wrapped_cursor: Some(ImplicitSessionCursor::with_implicit_session( + client, + spec, + PinnedConnection::new(pin), + ImplicitClientSessionHandle(session), + )), + drop_address: None, + #[cfg(test)] + kill_watcher: None, + _phantom: Default::default(), + } + } + + pub(crate) fn post_batch_resume_token(&self) -> Option<&ResumeToken> { + self.wrapped_cursor + .as_ref() + .and_then(|c| c.post_batch_resume_token()) + } + + /// Whether this cursor has exhausted all of its getMore calls. The cursor may have more + /// items remaining in the buffer. + pub(crate) fn is_exhausted(&self) -> bool { + self.wrapped_cursor.as_ref().unwrap().is_exhausted() + } + + /// Whether this cursor has any additional items to return. + pub(crate) fn has_next(&self) -> bool { + !self.is_exhausted() + || !self + .wrapped_cursor + .as_ref() + .unwrap() + .state() + .buffer + .is_empty() + } + + pub(crate) fn client(&self) -> &Client { + &self.client + } + + pub(crate) fn address(&self) -> &ServerAddress { + self.wrapped_cursor.as_ref().unwrap().address() + } + + pub(crate) fn set_drop_address(&mut self, address: ServerAddress) { + self.drop_address = Some(address); + } + + pub(crate) fn take_implicit_session(&mut self) -> Option { + self.wrapped_cursor + .as_mut() + .and_then(|c| c.take_implicit_session()) + } + + /// Move the cursor forward, potentially triggering requests to the database for more results + /// if the local buffer has been exhausted. + /// + /// This will keep requesting data from the server until either the cursor is exhausted + /// or batch with results in it has been received. + /// + /// The return value indicates whether new results were successfully returned (true) or if + /// the cursor has been closed (false). + /// + /// Note: [`Cursor::current`] and [`Cursor::deserialize_current`] must only be called after + /// [`Cursor::advance`] returned `Ok(true)`. It is an error to call either of them without + /// calling [`Cursor::advance`] first or after [`Cursor::advance`] returns an error / false. + /// + /// ``` + /// # use mongodb::{Client, bson::{Document, doc}, error::Result}; + /// # async fn foo() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; + /// # let coll = client.database("stuff").collection::("stuff"); + /// let mut cursor = coll.find(doc! {}).await?; + /// while cursor.advance().await? { + /// println!("{:?}", cursor.current()); + /// } + /// # Ok(()) + /// # } + /// ``` + pub async fn advance(&mut self) -> Result { + self.wrapped_cursor.as_mut().unwrap().advance().await + } + + #[cfg(test)] + pub(crate) async fn try_advance(&mut self) -> Result<()> { + self.wrapped_cursor + .as_mut() + .unwrap() + .try_advance() + .await + .map(|_| ()) + } + + /// Returns a reference to the current result in the cursor. + /// + /// # Panics + /// [`Cursor::advance`] must return `Ok(true)` before [`Cursor::current`] can be + /// invoked. Calling [`Cursor::current`] after [`Cursor::advance`] does not return true + /// or without calling [`Cursor::advance`] at all may result in a panic. + /// + /// ``` + /// # use mongodb::{Client, bson::{Document, doc}, error::Result}; + /// # async fn foo() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; + /// # let coll = client.database("stuff").collection::("stuff"); + /// let mut cursor = coll.find(doc! {}).await?; + /// while cursor.advance().await? { + /// println!("{:?}", cursor.current()); + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn current(&self) -> &RawDocument { + self.wrapped_cursor.as_ref().unwrap().current().unwrap() + } + + /// Deserialize the current result to the generic type associated with this cursor. + /// + /// # Panics + /// [`Cursor::advance`] must return `Ok(true)` before [`Cursor::deserialize_current`] can be + /// invoked. Calling [`Cursor::deserialize_current`] after [`Cursor::advance`] does not return + /// true or without calling [`Cursor::advance`] at all may result in a panic. + /// + /// ``` + /// # use mongodb::{Client, error::Result, bson::doc}; + /// # async fn foo() -> Result<()> { + /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; + /// # let db = client.database("foo"); + /// use serde::Deserialize; + /// + /// #[derive(Debug, Deserialize)] + /// struct Cat<'a> { + /// #[serde(borrow)] + /// name: &'a str + /// } + /// + /// let coll = db.collection::("cat"); + /// let mut cursor = coll.find(doc! {}).await?; + /// while cursor.advance().await? { + /// println!("{:?}", cursor.deserialize_current()?); + /// } + /// # Ok(()) + /// # } + /// ``` + pub fn deserialize_current<'a>(&'a self) -> Result + where + T: Deserialize<'a>, + { + crate::bson_compat::deserialize_from_slice(self.current().as_bytes()).map_err(Error::from) + } + + /// Update the type streamed values will be parsed as. + pub fn with_type<'a, D>(mut self) -> Cursor + where + D: Deserialize<'a>, + { + Cursor { + client: self.client.clone(), + drop_token: self.drop_token.take(), + wrapped_cursor: self.wrapped_cursor.take(), + drop_address: self.drop_address.take(), + #[cfg(test)] + kill_watcher: self.kill_watcher.take(), + _phantom: Default::default(), + } + } + + /// Some tests need to be able to observe the events generated by `killCommand` execution; + /// however, because that happens asynchronously on `drop`, the test runner can conclude before + /// the event is published. To fix that, tests can set a "kill watcher" on cursors - a + /// one-shot channel with a `()` value pushed after `killCommand` is run that the test can wait + /// on. + #[cfg(test)] + pub(crate) fn set_kill_watcher(&mut self, tx: oneshot::Sender<()>) { + assert!( + self.kill_watcher.is_none(), + "cursor already has a kill_watcher" + ); + self.kill_watcher = Some(tx); + } + + #[cfg(test)] + pub(crate) fn current_batch(&self) -> &VecDeque { + self.wrapped_cursor.as_ref().unwrap().current_batch() + } +} + +impl CursorStream for Cursor +where + T: DeserializeOwned, +{ + fn poll_next_in_batch(&mut self, cx: &mut Context<'_>) -> Poll> { + self.wrapped_cursor.as_mut().unwrap().poll_next_in_batch(cx) + } +} + +impl Stream for Cursor +where + T: DeserializeOwned, +{ + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // This `unwrap` is safe because `wrapped_cursor` is always `Some` outside of `drop`. + stream_poll_next(self.wrapped_cursor.as_mut().unwrap(), cx) + } +} + +impl Drop for Cursor { + fn drop(&mut self) { + let wrapped_cursor = match &self.wrapped_cursor { + None => return, + Some(c) => c, + }; + if wrapped_cursor.is_exhausted() { + return; + } + + kill_cursor( + self.client.clone(), + &mut self.drop_token, + wrapped_cursor.namespace(), + wrapped_cursor.id(), + wrapped_cursor.pinned_connection().replicate(), + self.drop_address.take(), + #[cfg(test)] + self.kill_watcher.take(), + ); + } +} + +/// A `GenericCursor` that optionally owns its own sessions. +/// This is to be used by cursors associated with implicit sessions. +type ImplicitSessionCursor = GenericCursor<'static, ImplicitClientSessionHandle>; diff --git a/src/cursor/common.rs b/src/cursor/common.rs index 508ccbf02..092bd2c83 100644 --- a/src/cursor/common.rs +++ b/src/cursor/common.rs @@ -5,8 +5,8 @@ use std::{ time::Duration, }; -use bson::{RawDocument, RawDocumentBuf}; -use derivative::Derivative; +use crate::bson::{RawDocument, RawDocumentBuf}; +use derive_where::derive_where; use futures_core::{future::BoxFuture, Future}; #[cfg(test)] use tokio::sync::oneshot; @@ -14,25 +14,31 @@ use tokio::sync::oneshot; use crate::{ bson::{Bson, Document}, change_stream::event::ResumeToken, + client::{session::ClientSession, AsyncDropToken}, cmap::conn::PinnedConnectionHandle, error::{Error, ErrorKind, Result}, - operation, + operation::{self, GetMore}, options::ServerAddress, results::GetMoreResult, - runtime, Client, Namespace, }; +/// The result of one attempt to advance a cursor. +pub(super) enum AdvanceResult { + /// The cursor was successfully advanced and the buffer has at least one item. + Advanced, + /// The cursor does not have any more items and will not return any more in the future. + Exhausted, + /// The cursor does not currently have any items, but future calls to getMore may yield more. + Waiting, +} + /// An internal cursor that can be used in a variety of contexts depending on its `GetMoreProvider`. -#[derive(Derivative)] -#[derivative(Debug)] -pub(super) struct GenericCursor

-where - P: GetMoreProvider, -{ - #[derivative(Debug = "ignore")] - provider: P, +#[derive_where(Debug)] +pub(super) struct GenericCursor<'s, S> { + #[derive_where(skip)] + provider: GetMoreProvider<'s, S>, client: Client, info: CursorInformation, /// This is an `Option` to allow it to be "taken" when the cursor is no longer needed @@ -40,20 +46,21 @@ where state: Option, } -impl

GenericCursor

-where - P: GetMoreProvider, -{ - pub(super) fn new( +impl GenericCursor<'static, ImplicitClientSessionHandle> { + pub(super) fn with_implicit_session( client: Client, spec: CursorSpecification, pinned_connection: PinnedConnection, - get_more_provider: P, + session: ImplicitClientSessionHandle, ) -> Self { let exhausted = spec.id() == 0; Self { client, - provider: get_more_provider, + provider: if exhausted { + GetMoreProvider::Done + } else { + GetMoreProvider::Idle(Box::new(session)) + }, info: spec.info, state: Some(CursorState { buffer: CursorBuffer::new(spec.initial_buffer), @@ -64,20 +71,29 @@ where } } - pub(super) fn from_state( + /// Extracts the stored implicit [`ClientSession`], if any. + pub(super) fn take_implicit_session(&mut self) -> Option { + self.provider.take_implicit_session() + } +} + +impl<'s> GenericCursor<'s, ExplicitClientSessionHandle<'s>> { + pub(super) fn with_explicit_session( state: CursorState, client: Client, info: CursorInformation, - provider: P, + session: ExplicitClientSessionHandle<'s>, ) -> Self { Self { - provider, + provider: GetMoreProvider::Idle(Box::new(session)), client, info, state: state.into(), } } +} +impl<'s, S: ClientSessionHandle<'s>> GenericCursor<'s, S> { pub(super) fn current(&self) -> Option<&RawDocument> { self.state().buffer.current() } @@ -95,33 +111,46 @@ where self.state.as_ref().unwrap() } - /// Advance the cursor forward to the next document. - /// If there are no documents cached locally, perform getMores until - /// the cursor is exhausted or a result/error has been received. + /// Attempt to advance the cursor forward to the next item. If there are no items cached + /// locally, perform getMores until the cursor is exhausted or the buffer has been refilled. + /// Return whether or not the cursor has been advanced. pub(super) async fn advance(&mut self) -> Result { loop { - self.state_mut().buffer.advance(); - - if !self.state().buffer.is_empty() { - break; + match self.try_advance().await? { + AdvanceResult::Advanced => return Ok(true), + AdvanceResult::Exhausted => return Ok(false), + AdvanceResult::Waiting => continue, } + } + } - // if moving the offset puts us at the end of the buffer, perform another - // getMore if the cursor is still alive. + /// Attempt to advance the cursor forward to the next item. If there are no items cached + /// locally, perform a single getMore to attempt to retrieve more. + pub(super) async fn try_advance(&mut self) -> Result { + if self.state_mut().buffer.advance() { + return Ok(AdvanceResult::Advanced); + } else if self.is_exhausted() { + return Ok(AdvanceResult::Exhausted); + } - if self.state().exhausted { - return Ok(false); + // If the buffer is empty but the cursor is not exhausted, perform a getMore. + let client = self.client.clone(); + let spec = self.info.clone(); + let pin = self.state().pinned_connection.replicate(); + + let result = self.provider.execute(spec, client, pin).await; + self.handle_get_more_result(result)?; + + match self.state_mut().buffer.advance() { + true => Ok(AdvanceResult::Advanced), + false => { + if self.is_exhausted() { + Ok(AdvanceResult::Exhausted) + } else { + Ok(AdvanceResult::Waiting) + } } - - let client = self.client.clone(); - let spec = self.info.clone(); - let pin = self.state().pinned_connection.replicate(); - - let result = self.provider.execute(spec, client, pin).await; - self.handle_get_more_result(result)?; } - - Ok(true) } pub(super) fn take_state(&mut self) -> CursorState { @@ -163,6 +192,10 @@ where if get_more.exhausted { self.mark_exhausted(); } + if get_more.id != 0 { + self.info.id = get_more.id + } + self.info.ns = get_more.ns; self.state_mut().buffer = CursorBuffer::new(get_more.batch); self.state_mut().post_batch_resume_token = get_more.post_batch_resume_token; @@ -183,10 +216,6 @@ where } } } - - pub(super) fn provider_mut(&mut self) -> &mut P { - &mut self.provider - } } pub(crate) trait CursorStream { @@ -199,20 +228,18 @@ pub(crate) enum BatchValue { Exhausted, } -impl

CursorStream for GenericCursor

-where - P: GetMoreProvider, -{ +impl<'s, S: ClientSessionHandle<'s>> CursorStream for GenericCursor<'s, S> { fn poll_next_in_batch(&mut self, cx: &mut Context<'_>) -> Poll> { // If there is a get more in flight, check on its status. if let Some(future) = self.provider.executing_future() { match Pin::new(future).poll(cx) { // If a result is ready, retrieve the buffer and update the exhausted status. - Poll::Ready(get_more_result) => { - let (result, session) = get_more_result.into_parts(); - let output = self.handle_get_more_result(result); - self.provider - .clear_execution(session, self.state().exhausted); + Poll::Ready(get_more_result_and_session) => { + let output = self.handle_get_more_result(get_more_result_and_session.result); + self.provider.clear_execution( + get_more_result_and_session.session, + self.state().exhausted, + ); output?; } Poll::Pending => return Poll::Pending, @@ -250,7 +277,9 @@ where Poll::Pending => return Poll::Pending, Poll::Ready(bv) => match bv? { BatchValue::Some { doc, .. } => { - return Poll::Ready(Some(Ok(bson::from_slice(doc.as_bytes())?))) + return Poll::Ready(Some(Ok(crate::bson_compat::deserialize_from_slice( + doc.as_bytes(), + )?))) } BatchValue::Empty => continue, BatchValue::Exhausted => return Poll::Ready(None), @@ -270,7 +299,7 @@ where } } -impl<'a, C> Future for NextInBatchFuture<'a, C> +impl Future for NextInBatchFuture<'_, C> where C: CursorStream, { @@ -281,52 +310,105 @@ where } } -/// A trait implemented by objects that can provide batches of documents to a cursor via the getMore -/// command. -pub(super) trait GetMoreProvider: Unpin { - /// The result type that the future running the getMore evaluates to. - type ResultType: GetMoreProviderResult; +/// Provides batches of documents to a cursor via the `getMore` command. +enum GetMoreProvider<'s, S> { + Executing(BoxFuture<'s, GetMoreResultAndSession>), + // `Box` is used to make the size of `Idle` similar to that of the other variants. + Idle(Box), + Done, +} - /// The type of future created by this provider when running a getMore. - type GetMoreFuture: Future + Unpin; +impl GetMoreProvider<'static, ImplicitClientSessionHandle> { + /// Extracts the stored implicit [`ClientSession`], if any. + /// The provider cannot be started again after this call. + fn take_implicit_session(&mut self) -> Option { + match self { + Self::Idle(session) => session.take_implicit_session(), + Self::Executing(..) | Self::Done => None, + } + } +} +impl<'s, S: ClientSessionHandle<'s>> GetMoreProvider<'s, S> { /// Get the future being evaluated, if there is one. - fn executing_future(&mut self) -> Option<&mut Self::GetMoreFuture>; + fn executing_future(&mut self) -> Option<&mut BoxFuture<'s, GetMoreResultAndSession>> { + if let Self::Executing(future) = self { + Some(future) + } else { + None + } + } - /// Clear out any state remaining from previous getMore executions. - fn clear_execution( - &mut self, - session: ::Session, - exhausted: bool, - ); + /// Clear out any state remaining from previous `getMore` executions. + fn clear_execution(&mut self, session: S, exhausted: bool) { + if exhausted && session.is_implicit() { + *self = Self::Done + } else { + *self = Self::Idle(Box::new(session)) + } + } - /// Start executing a new getMore if one isn't already in flight. + /// Start executing a new `getMore` if one is not already in flight. fn start_execution( &mut self, - spec: CursorInformation, + info: CursorInformation, client: Client, pinned_connection: Option<&PinnedConnectionHandle>, - ); + ) { + take_mut::take(self, |self_| { + if let Self::Idle(mut session) = self_ { + let pinned_connection = pinned_connection.map(|c| c.replicate()); + let future = Box::pin(async move { + let get_more = GetMore::new(info, pinned_connection.as_ref()); + let get_more_result = client + .execute_operation(get_more, session.borrow_mut()) + .await; + GetMoreResultAndSession { + result: get_more_result, + session: *session, + } + }); + Self::Executing(future) + } else { + self_ + } + }) + } - /// Return a future that will execute the getMore when polled. - /// This is useful in async functions that can await the entire getMore process. - /// `start_execution` and `clear_execution` should be used for contexts where the futures - /// need to be polled manually. + /// Return a future that will execute the `getMore` when polled. + /// This is useful in `async` functions that can `.await` the entire `getMore` process. + /// [`GetMoreProvider::start_execution`] and [`GetMoreProvider::clear_execution`] + /// should be used for contexts where the futures need to be [`poll`](Future::poll)ed manually. fn execute( &mut self, - _spec: CursorInformation, - _client: Client, - _pinned_conn: PinnedConnection, - ) -> BoxFuture<'_, Result>; + info: CursorInformation, + client: Client, + pinned_connection: PinnedConnection, + ) -> BoxFuture<'_, Result> { + match self { + Self::Idle(ref mut session) => Box::pin(async move { + let get_more = GetMore::new(info, pinned_connection.handle()); + client + .execute_operation(get_more, session.borrow_mut()) + .await + }), + Self::Executing(_fut) => Box::pin(async { + Err(Error::internal( + "streaming the cursor was cancelled while a request was in progress and must \ + be continued before iterating manually", + )) + }), + Self::Done => { + // this should never happen + Box::pin(async { Err(Error::internal("cursor iterated after already exhausted")) }) + } + } + } } -/// Trait describing results returned from a `GetMoreProvider`. -pub(crate) trait GetMoreProviderResult { - type Session; - - fn as_ref(&self) -> std::result::Result<&GetMoreResult, &Error>; - - fn into_parts(self) -> (Result, Self::Session); +struct GetMoreResultAndSession { + result: Result, + session: S, } /// Specification used to create a new cursor. @@ -362,21 +444,6 @@ impl CursorSpecification { pub(crate) fn id(&self) -> i64 { self.info.id } - - #[cfg(test)] - pub(crate) fn address(&self) -> &ServerAddress { - &self.info.address - } - - #[cfg(test)] - pub(crate) fn batch_size(&self) -> Option { - self.info.batch_size - } - - #[cfg(test)] - pub(crate) fn max_time(&self) -> Option { - self.info.max_time - } } /// Static information about a cursor. @@ -440,6 +507,7 @@ impl PinnedConnection { pub(super) fn kill_cursor( client: Client, + drop_token: &mut AsyncDropToken, ns: &Namespace, cursor_id: i64, pinned_conn: PinnedConnection, @@ -449,7 +517,7 @@ pub(super) fn kill_cursor( let coll = client .database(ns.db.as_str()) .collection::(ns.coll.as_str()); - runtime::execute(async move { + drop_token.spawn(async move { if !pinned_conn.is_invalid() { let _ = coll .kill_cursor(cursor_id, pinned_conn.handle(), drop_address) @@ -489,21 +557,27 @@ impl CursorBuffer { self.docs.is_empty() } + /// Removes and returns the document in the front of the buffer. pub(crate) fn next(&mut self) -> Option { self.fresh = false; self.docs.pop_front() } - pub(crate) fn advance(&mut self) { - // if at the front of the buffer, don't move forward as the first document - // hasn't been consumed yet. + /// Advances the buffer to the next document. Returns whether there are any documents remaining + /// in the buffer after advancing. + pub(crate) fn advance(&mut self) -> bool { + // If at the front of the buffer, don't move forward as the first document hasn't been + // consumed yet. if self.fresh { self.fresh = false; - return; + } else { + self.docs.pop_front(); } - self.next(); + !self.is_empty() } + /// Returns the item at the front of the buffer, if there is one. This method does not change + /// the state of the buffer. pub(crate) fn current(&self) -> Option<&RawDocument> { self.docs.front().map(|d| d.as_ref()) } @@ -514,3 +588,60 @@ impl AsRef> for CursorBuffer { &self.docs } } + +#[test] +fn test_buffer() { + use crate::bson::rawdoc; + + let queue: VecDeque = + [rawdoc! { "x": 1 }, rawdoc! { "x": 2 }, rawdoc! { "x": 3 }].into(); + let mut buffer = CursorBuffer::new(queue); + + assert!(buffer.advance()); + assert_eq!(buffer.current(), Some(rawdoc! { "x": 1 }.as_ref())); + + assert!(buffer.advance()); + assert_eq!(buffer.current(), Some(rawdoc! { "x": 2 }.as_ref())); + + assert!(buffer.advance()); + assert_eq!(buffer.current(), Some(rawdoc! { "x": 3 }.as_ref())); + + assert!(!buffer.advance()); + assert_eq!(buffer.current(), None); +} + +pub(super) struct ImplicitClientSessionHandle(pub(super) Option); + +impl ImplicitClientSessionHandle { + fn take_implicit_session(&mut self) -> Option { + self.0.take() + } +} + +impl ClientSessionHandle<'_> for ImplicitClientSessionHandle { + fn is_implicit(&self) -> bool { + true + } + + fn borrow_mut(&mut self) -> Option<&mut ClientSession> { + self.0.as_mut() + } +} + +pub(super) struct ExplicitClientSessionHandle<'a>(pub(super) &'a mut ClientSession); + +impl<'a> ClientSessionHandle<'a> for ExplicitClientSessionHandle<'a> { + fn is_implicit(&self) -> bool { + false + } + + fn borrow_mut(&mut self) -> Option<&mut ClientSession> { + Some(self.0) + } +} + +pub(super) trait ClientSessionHandle<'a>: Send + 'a { + fn is_implicit(&self) -> bool; + + fn borrow_mut(&mut self) -> Option<&mut ClientSession>; +} diff --git a/src/cursor/mod.rs b/src/cursor/mod.rs deleted file mode 100644 index 89ce85f0b..000000000 --- a/src/cursor/mod.rs +++ /dev/null @@ -1,464 +0,0 @@ -mod common; -pub(crate) mod session; - -#[cfg(test)] -use std::collections::VecDeque; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -use bson::RawDocument; - -#[cfg(test)] -use bson::RawDocumentBuf; -use futures_core::{future::BoxFuture, Stream}; -use serde::{de::DeserializeOwned, Deserialize}; -#[cfg(test)] -use tokio::sync::oneshot; - -use crate::{ - change_stream::event::ResumeToken, - client::options::ServerAddress, - cmap::conn::PinnedConnectionHandle, - error::{Error, Result}, - operation::GetMore, - results::GetMoreResult, - Client, - ClientSession, -}; -use common::{kill_cursor, GenericCursor, GetMoreProvider, GetMoreProviderResult}; -pub(crate) use common::{ - stream_poll_next, - BatchValue, - CursorInformation, - CursorSpecification, - CursorStream, - NextInBatchFuture, - PinnedConnection, -}; - -/// A [`Cursor`] streams the result of a query. When a query is made, the returned [`Cursor`] will -/// contain the first batch of results from the server; the individual results will then be returned -/// as the [`Cursor`] is iterated. When the batch is exhausted and if there are more results, the -/// [`Cursor`] will fetch the next batch of documents, and so forth until the results are exhausted. -/// Note that because of this batching, additional network I/O may occur on any given call to -/// `next`. Because of this, a [`Cursor`] iterates over `Result` items rather than -/// simply `T` items. -/// -/// The batch size of the `Cursor` can be configured using the options to the method that returns -/// it. For example, setting the `batch_size` field of -/// [`FindOptions`](options/struct.FindOptions.html) will set the batch size of the -/// `Cursor` returned by [`Collection::find`](struct.Collection.html#method.find). -/// -/// Note that the batch size determines both the number of documents stored in memory by the -/// `Cursor` at a given time as well as the total number of network round-trips needed to fetch all -/// results from the server; both of these factors should be taken into account when choosing the -/// optimal batch size. -/// -/// [`Cursor`] implements [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html), which means -/// it can be iterated over much in the same way that an `Iterator` can be in synchronous Rust. In -/// order to do so, the [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) trait must -/// be imported. Because a [`Cursor`] iterates over a `Result`, it also has access to the -/// potentially more ergonomic functionality provided by -/// [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html), which can be -/// imported instead of or in addition to -/// [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html). The methods from -/// [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html) are especially useful when -/// used in conjunction with the `?` operator. -/// -/// ```rust -/// # use mongodb::{bson::Document, Client, error::Result}; -/// # -/// # async fn do_stuff() -> Result<()> { -/// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// # let coll = client.database("foo").collection::("bar"); -/// # -/// use futures::stream::{StreamExt, TryStreamExt}; -/// -/// let mut cursor = coll.find(None, None).await?; -/// // regular Stream uses next() and iterates over Option> -/// while let Some(doc) = cursor.next().await { -/// println!("{}", doc?) -/// } -/// // regular Stream uses collect() and collects into a Vec> -/// let v: Vec> = cursor.collect().await; -/// -/// let mut cursor = coll.find(None, None).await?; -/// // TryStream uses try_next() and iterates over Result> -/// while let Some(doc) = cursor.try_next().await? { -/// println!("{}", doc) -/// } -/// // TryStream uses try_collect() and collects into a Result> -/// let v: Vec<_> = cursor.try_collect().await?; -/// # -/// # Ok(()) -/// # } -/// ``` -/// -/// If a [`Cursor`] is still open when it goes out of scope, it will automatically be closed via an -/// asynchronous [killCursors](https://www.mongodb.com/docs/manual/reference/command/killCursors/) command executed -/// from its [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) implementation. -#[derive(Debug)] -pub struct Cursor { - client: Client, - // `wrapped_cursor` is an `Option` so that it can be `None` for the `drop` impl for a cursor - // that's had `with_type` called; in all other circumstances it will be `Some`. - wrapped_cursor: Option, - drop_address: Option, - #[cfg(test)] - kill_watcher: Option>, - _phantom: std::marker::PhantomData T>, -} - -impl Cursor { - pub(crate) fn new( - client: Client, - spec: CursorSpecification, - session: Option, - pin: Option, - ) -> Self { - let provider = ImplicitSessionGetMoreProvider::new(&spec, session); - - Self { - client: client.clone(), - wrapped_cursor: Some(ImplicitSessionCursor::new( - client, - spec, - PinnedConnection::new(pin), - provider, - )), - drop_address: None, - #[cfg(test)] - kill_watcher: None, - _phantom: Default::default(), - } - } - - pub(crate) fn post_batch_resume_token(&self) -> Option<&ResumeToken> { - self.wrapped_cursor - .as_ref() - .and_then(|c| c.post_batch_resume_token()) - } - - /// Whether this cursor has exhausted all of its getMore calls. The cursor may have more - /// items remaining in the buffer. - pub(crate) fn is_exhausted(&self) -> bool { - self.wrapped_cursor.as_ref().unwrap().is_exhausted() - } - - /// Whether this cursor has any additional items to return. - pub(crate) fn has_next(&self) -> bool { - !self.is_exhausted() - || !self - .wrapped_cursor - .as_ref() - .unwrap() - .state() - .buffer - .is_empty() - } - - pub(crate) fn client(&self) -> &Client { - &self.client - } - - pub(crate) fn address(&self) -> &ServerAddress { - self.wrapped_cursor.as_ref().unwrap().address() - } - - pub(crate) fn set_drop_address(&mut self, address: ServerAddress) { - self.drop_address = Some(address); - } - - pub(crate) fn take_implicit_session(&mut self) -> Option { - self.wrapped_cursor - .as_mut() - .and_then(|c| c.provider_mut().take_implicit_session()) - } - - /// Move the cursor forward, potentially triggering requests to the database for more results - /// if the local buffer has been exhausted. - /// - /// This will keep requesting data from the server until either the cursor is exhausted - /// or batch with results in it has been received. - /// - /// The return value indicates whether new results were successfully returned (true) or if - /// the cursor has been closed (false). - /// - /// Note: [`Cursor::current`] and [`Cursor::deserialize_current`] must only be called after - /// [`Cursor::advance`] returned `Ok(true)`. It is an error to call either of them without - /// calling [`Cursor::advance`] first or after [`Cursor::advance`] returns an error / false. - /// - /// ``` - /// # use mongodb::{Client, bson::Document, error::Result}; - /// # async fn foo() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find(None, None).await?; - /// while cursor.advance().await? { - /// println!("{:?}", cursor.current()); - /// } - /// # Ok(()) - /// # } - /// ``` - pub async fn advance(&mut self) -> Result { - self.wrapped_cursor.as_mut().unwrap().advance().await - } - - /// Returns a reference to the current result in the cursor. - /// - /// # Panics - /// [`Cursor::advance`] must return `Ok(true)` before [`Cursor::current`] can be - /// invoked. Calling [`Cursor::current`] after [`Cursor::advance`] does not return true - /// or without calling [`Cursor::advance`] at all may result in a panic. - /// - /// ``` - /// # use mongodb::{Client, bson::Document, error::Result}; - /// # async fn foo() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find(None, None).await?; - /// while cursor.advance().await? { - /// println!("{:?}", cursor.current()); - /// } - /// # Ok(()) - /// # } - /// ``` - pub fn current(&self) -> &RawDocument { - self.wrapped_cursor.as_ref().unwrap().current().unwrap() - } - - /// Deserialize the current result to the generic type associated with this cursor. - /// - /// # Panics - /// [`Cursor::advance`] must return `Ok(true)` before [`Cursor::deserialize_current`] can be - /// invoked. Calling [`Cursor::deserialize_current`] after [`Cursor::advance`] does not return - /// true or without calling [`Cursor::advance`] at all may result in a panic. - /// - /// ``` - /// # use mongodb::{Client, error::Result}; - /// # async fn foo() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let db = client.database("foo"); - /// use serde::Deserialize; - /// - /// #[derive(Debug, Deserialize)] - /// struct Cat<'a> { - /// #[serde(borrow)] - /// name: &'a str - /// } - /// - /// let coll = db.collection::("cat"); - /// let mut cursor = coll.find(None, None).await?; - /// while cursor.advance().await? { - /// println!("{:?}", cursor.deserialize_current()?); - /// } - /// # Ok(()) - /// # } - /// ``` - pub fn deserialize_current<'a>(&'a self) -> Result - where - T: Deserialize<'a>, - { - bson::from_slice(self.current().as_bytes()).map_err(Error::from) - } - - /// Update the type streamed values will be parsed as. - pub fn with_type<'a, D>(mut self) -> Cursor - where - D: Deserialize<'a>, - { - Cursor { - client: self.client.clone(), - wrapped_cursor: self.wrapped_cursor.take(), - drop_address: self.drop_address.take(), - #[cfg(test)] - kill_watcher: self.kill_watcher.take(), - _phantom: Default::default(), - } - } - - /// Some tests need to be able to observe the events generated by `killCommand` execution; - /// however, because that happens asynchronously on `drop`, the test runner can conclude before - /// the event is published. To fix that, tests can set a "kill watcher" on cursors - a - /// one-shot channel with a `()` value pushed after `killCommand` is run that the test can wait - /// on. - #[cfg(test)] - pub(crate) fn set_kill_watcher(&mut self, tx: oneshot::Sender<()>) { - assert!( - self.kill_watcher.is_none(), - "cursor already has a kill_watcher" - ); - self.kill_watcher = Some(tx); - } - - #[cfg(test)] - pub(crate) fn current_batch(&self) -> &VecDeque { - self.wrapped_cursor.as_ref().unwrap().current_batch() - } -} - -impl CursorStream for Cursor -where - T: DeserializeOwned, -{ - fn poll_next_in_batch(&mut self, cx: &mut Context<'_>) -> Poll> { - self.wrapped_cursor.as_mut().unwrap().poll_next_in_batch(cx) - } -} - -impl Stream for Cursor -where - T: DeserializeOwned, -{ - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - // This `unwrap` is safe because `wrapped_cursor` is always `Some` outside of `drop`. - stream_poll_next(self.wrapped_cursor.as_mut().unwrap(), cx) - } -} - -impl Drop for Cursor { - fn drop(&mut self) { - let wrapped_cursor = match &self.wrapped_cursor { - None => return, - Some(c) => c, - }; - if wrapped_cursor.is_exhausted() { - return; - } - - kill_cursor( - self.client.clone(), - wrapped_cursor.namespace(), - wrapped_cursor.id(), - wrapped_cursor.pinned_connection().replicate(), - self.drop_address.take(), - #[cfg(test)] - self.kill_watcher.take(), - ); - } -} - -/// A `GenericCursor` that optionally owns its own sessions. -/// This is to be used by cursors associated with implicit sessions. -type ImplicitSessionCursor = GenericCursor; - -struct ImplicitSessionGetMoreResult { - get_more_result: Result, - session: Option>, -} - -impl GetMoreProviderResult for ImplicitSessionGetMoreResult { - type Session = Option>; - - fn as_ref(&self) -> std::result::Result<&GetMoreResult, &Error> { - self.get_more_result.as_ref() - } - - fn into_parts(self) -> (Result, Self::Session) { - (self.get_more_result, self.session) - } -} - -/// A `GetMoreProvider` that optionally owns its own session. -/// This is to be used with cursors associated with implicit sessions. -enum ImplicitSessionGetMoreProvider { - Executing(BoxFuture<'static, ImplicitSessionGetMoreResult>), - Idle(Option>), - Done, -} - -impl ImplicitSessionGetMoreProvider { - fn new(spec: &CursorSpecification, session: Option) -> Self { - let session = session.map(Box::new); - if spec.id() == 0 { - Self::Done - } else { - Self::Idle(session) - } - } - - /// Extract the stored implicit session, if any. The provider cannot be started again after - /// this call. - fn take_implicit_session(&mut self) -> Option { - match self { - ImplicitSessionGetMoreProvider::Idle(session) => session.take().map(|s| *s), - _ => None, - } - } -} - -impl GetMoreProvider for ImplicitSessionGetMoreProvider { - type ResultType = ImplicitSessionGetMoreResult; - type GetMoreFuture = BoxFuture<'static, ImplicitSessionGetMoreResult>; - - fn executing_future(&mut self) -> Option<&mut Self::GetMoreFuture> { - match self { - Self::Executing(ref mut future) => Some(future), - Self::Idle { .. } | Self::Done => None, - } - } - - fn clear_execution(&mut self, session: Option>, exhausted: bool) { - // If cursor is exhausted, immediately return implicit session to the pool. - if exhausted { - *self = Self::Done; - } else { - *self = Self::Idle(session); - } - } - - fn start_execution( - &mut self, - info: CursorInformation, - client: Client, - pinned_connection: Option<&PinnedConnectionHandle>, - ) { - take_mut::take(self, |self_| match self_ { - Self::Idle(mut session) => { - let pinned_connection = pinned_connection.map(|c| c.replicate()); - let future = Box::pin(async move { - let get_more = GetMore::new(info, pinned_connection.as_ref()); - let get_more_result = client - .execute_operation(get_more, session.as_mut().map(|b| b.as_mut())) - .await; - ImplicitSessionGetMoreResult { - get_more_result, - session, - } - }); - Self::Executing(future) - } - Self::Executing(_) | Self::Done => self_, - }) - } - - fn execute( - &mut self, - info: CursorInformation, - client: Client, - pinned_connection: PinnedConnection, - ) -> BoxFuture<'_, Result> { - match self { - Self::Idle(ref mut session) => Box::pin(async move { - let get_more = GetMore::new(info, pinned_connection.handle()); - client - .execute_operation(get_more, session.as_mut().map(|b| b.as_mut())) - .await - }), - Self::Executing(_fut) => Box::pin(async { - Err(Error::internal( - "streaming the cursor was cancelled while a request was in progress and must \ - be continued before iterating manually", - )) - }), - Self::Done => { - // this should never happen - Box::pin(async { Err(Error::internal("cursor iterated after already exhausted")) }) - } - } - } -} diff --git a/src/cursor/session.rs b/src/cursor/session.rs index 3e4259f88..a2a0b3dc6 100644 --- a/src/cursor/session.rs +++ b/src/cursor/session.rs @@ -4,8 +4,8 @@ use std::{ task::{Context, Poll}, }; -use bson::RawDocument; -use futures_core::{future::BoxFuture, Stream}; +use crate::bson::RawDocument; +use futures_core::Stream; use futures_util::StreamExt; use serde::{de::DeserializeOwned, Deserialize}; #[cfg(test)] @@ -18,8 +18,6 @@ use super::{ CursorInformation, CursorState, GenericCursor, - GetMoreProvider, - GetMoreProviderResult, PinnedConnection, }, stream_poll_next, @@ -29,12 +27,10 @@ use super::{ use crate::{ bson::Document, change_stream::event::ResumeToken, - client::options::ServerAddress, + client::{options::ServerAddress, AsyncDropToken}, cmap::conn::PinnedConnectionHandle, - cursor::CursorSpecification, + cursor::{common::ExplicitClientSessionHandle, CursorSpecification}, error::{Error, Result}, - operation::GetMore, - results::GetMoreResult, Client, ClientSession, }; @@ -44,15 +40,15 @@ use crate::{ /// [`SessionCursor::stream`]: /// /// ```rust -/// # use mongodb::{bson::Document, Client, error::Result, ClientSession, SessionCursor}; +/// # use mongodb::{bson::{Document, doc}, Client, error::Result, ClientSession, SessionCursor}; /// # /// # async fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// # let mut session = client.start_session(None).await?; +/// # let mut session = client.start_session().await?; /// # let coll = client.database("foo").collection::("bar"); /// # /// // iterate using next() -/// let mut cursor = coll.find_with_session(None, None, &mut session).await?; +/// let mut cursor = coll.find(doc! {}).session(&mut session).await?; /// while let Some(doc) = cursor.next(&mut session).await.transpose()? { /// println!("{}", doc) /// } @@ -60,7 +56,7 @@ use crate::{ /// // iterate using `Stream`: /// use futures::stream::TryStreamExt; /// -/// let mut cursor = coll.find_with_session(None, None, &mut session).await?; +/// let mut cursor = coll.find(doc! {}).session(&mut session).await?; /// let results: Vec<_> = cursor.stream(&mut session).try_collect().await?; /// # /// # Ok(()) @@ -73,6 +69,7 @@ use crate::{ #[derive(Debug)] pub struct SessionCursor { client: Client, + drop_token: AsyncDropToken, info: CursorInformation, state: Option, drop_address: Option, @@ -90,6 +87,7 @@ impl SessionCursor { let exhausted = spec.info.id == 0; Self { + drop_token: client.register_async_drop(), client, info: spec.info, drop_address: None, @@ -119,35 +117,34 @@ where /// the stream before using the session. /// /// ``` - /// # use bson::{doc, Document}; - /// # use mongodb::{Client, error::Result}; + /// # use mongodb::{Client, bson::{doc, Document}, error::Result}; /// # fn main() { /// # async { /// # let client = Client::with_uri_str("foo").await?; /// # let coll = client.database("foo").collection::("bar"); /// # let other_coll = coll.clone(); - /// # let mut session = client.start_session(None).await?; + /// # let mut session = client.start_session().await?; /// # /// use futures::stream::TryStreamExt; /// /// // iterate over the results - /// let mut cursor = coll.find_with_session(doc! { "x": 1 }, None, &mut session).await?; + /// let mut cursor = coll.find(doc! { "x": 1 }).session(&mut session).await?; /// while let Some(doc) = cursor.stream(&mut session).try_next().await? { /// println!("{}", doc); /// } /// /// // collect the results - /// let mut cursor1 = coll.find_with_session(doc! { "x": 1 }, None, &mut session).await?; + /// let mut cursor1 = coll.find(doc! { "x": 1 }).session(&mut session).await?; /// let v: Vec = cursor1.stream(&mut session).try_collect().await?; /// /// // use session between iterations - /// let mut cursor2 = coll.find_with_session(doc! { "x": 1 }, None, &mut session).await?; + /// let mut cursor2 = coll.find(doc! { "x": 1 }).session(&mut session).await?; /// loop { /// let doc = match cursor2.stream(&mut session).try_next().await? { /// Some(d) => d, /// None => break, /// }; - /// other_coll.insert_one_with_session(doc, None, &mut session).await?; + /// other_coll.insert_one(doc).session(&mut session).await?; /// } /// # Ok::<(), mongodb::error::Error>(()) /// # }; @@ -167,17 +164,16 @@ where /// functionality of `Stream` is not needed. /// /// ``` - /// # use bson::{doc, Document}; - /// # use mongodb::Client; + /// # use mongodb::{Client, bson::{doc, Document}}; /// # fn main() { /// # async { /// # let client = Client::with_uri_str("foo").await?; /// # let coll = client.database("foo").collection::("bar"); /// # let other_coll = coll.clone(); - /// # let mut session = client.start_session(None).await?; - /// let mut cursor = coll.find_with_session(doc! { "x": 1 }, None, &mut session).await?; + /// # let mut session = client.start_session().await?; + /// let mut cursor = coll.find(doc! { "x": 1 }).session(&mut session).await?; /// while let Some(doc) = cursor.next(&mut session).await.transpose()? { - /// other_coll.insert_one_with_session(doc, None, &mut session).await?; + /// other_coll.insert_one(doc).session(&mut session).await?; /// } /// # Ok::<(), mongodb::error::Error>(()) /// # }; @@ -193,16 +189,14 @@ impl SessionCursor { &mut self, session: &'session mut ClientSession, ) -> SessionCursorStream<'_, 'session, T> { - let get_more_provider = ExplicitSessionGetMoreProvider::new(session); - // Pass the state into this cursor handle for iteration. // It will be returned in the handle's `Drop` implementation. SessionCursorStream { - generic_cursor: ExplicitSessionCursor::from_state( + generic_cursor: ExplicitSessionCursor::with_explicit_session( self.take_state(), self.client.clone(), self.info.clone(), - get_more_provider, + ExplicitClientSessionHandle(session), ), session_cursor: self, } @@ -227,12 +221,12 @@ impl SessionCursor { /// [`SessionCursor::advance`] returns an error / false. /// /// ``` - /// # use mongodb::{Client, bson::Document, error::Result}; + /// # use mongodb::{Client, bson::{doc, Document}, error::Result}; /// # async fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let mut session = client.start_session(None).await?; + /// # let mut session = client.start_session().await?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find_with_session(None, None, &mut session).await?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).await?; /// while cursor.advance(&mut session).await? { /// println!("{:?}", cursor.current()); /// } @@ -243,6 +237,15 @@ impl SessionCursor { self.make_stream(session).generic_cursor.advance().await } + #[cfg(test)] + pub(crate) async fn try_advance(&mut self, session: &mut ClientSession) -> Result<()> { + self.make_stream(session) + .generic_cursor + .try_advance() + .await + .map(|_| ()) + } + /// Returns a reference to the current result in the cursor. /// /// # Panics @@ -251,12 +254,12 @@ impl SessionCursor { /// true or without calling [`SessionCursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{Client, bson::Document, error::Result}; + /// # use mongodb::{Client, bson::{Document, doc}, error::Result}; /// # async fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let mut session = client.start_session(None).await?; + /// # let mut session = client.start_session().await?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find_with_session(None, None, &mut session).await?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).await?; /// while cursor.advance(&mut session).await? { /// println!("{:?}", cursor.current()); /// } @@ -276,10 +279,10 @@ impl SessionCursor { /// true or without calling [`SessionCursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{Client, error::Result}; + /// # use mongodb::{Client, error::Result, bson::doc}; /// # async fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017").await?; - /// # let mut session = client.start_session(None).await?; + /// # let mut session = client.start_session().await?; /// # let db = client.database("foo"); /// use serde::Deserialize; /// @@ -290,7 +293,7 @@ impl SessionCursor { /// } /// /// let coll = db.collection::("cat"); - /// let mut cursor = coll.find_with_session(None, None, &mut session).await?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).await?; /// while cursor.advance(&mut session).await? { /// println!("{:?}", cursor.deserialize_current()?); /// } @@ -301,7 +304,7 @@ impl SessionCursor { where T: Deserialize<'a>, { - bson::from_slice(self.current().as_bytes()).map_err(Error::from) + crate::bson_compat::deserialize_from_slice(self.current().as_bytes()).map_err(Error::from) } /// Update the type streamed values will be parsed as. @@ -309,17 +312,16 @@ impl SessionCursor { where D: Deserialize<'a>, { - let out = SessionCursor { + SessionCursor { client: self.client.clone(), + drop_token: self.drop_token.take(), info: self.info.clone(), state: Some(self.take_state()), drop_address: self.drop_address.take(), _phantom: Default::default(), #[cfg(test)] kill_watcher: self.kill_watcher.take(), - }; - self.mark_exhausted(); // prevent a `kill_cursor` call in `drop` - out + } } pub(crate) fn address(&self) -> &ServerAddress { @@ -346,12 +348,8 @@ impl SessionCursor { } impl SessionCursor { - fn mark_exhausted(&mut self) { - self.state.as_mut().unwrap().exhausted = true; - } - pub(crate) fn is_exhausted(&self) -> bool { - self.state.as_ref().unwrap().exhausted + self.state.as_ref().is_none_or(|state| state.exhausted) } #[cfg(test)] @@ -368,6 +366,7 @@ impl Drop for SessionCursor { kill_cursor( self.client.clone(), + &mut self.drop_token, &self.info.ns, self.info.id, self.state.as_ref().unwrap().pinned_connection.replicate(), @@ -380,7 +379,8 @@ impl Drop for SessionCursor { /// A `GenericCursor` that borrows its session. /// This is to be used with cursors associated with explicit sessions borrowed from the user. -type ExplicitSessionCursor<'session> = GenericCursor>; +type ExplicitSessionCursor<'session> = + GenericCursor<'session, ExplicitClientSessionHandle<'session>>; /// A type that implements [`Stream`](https://docs.rs/futures/latest/futures/stream/index.html) which can be used to /// stream the results of a [`SessionCursor`]. Returned from [`SessionCursor::stream`]. @@ -392,7 +392,7 @@ pub struct SessionCursorStream<'cursor, 'session, T = Document> { generic_cursor: ExplicitSessionCursor<'session>, } -impl<'cursor, 'session, T> SessionCursorStream<'cursor, 'session, T> +impl SessionCursorStream<'_, '_, T> where T: DeserializeOwned, { @@ -405,7 +405,7 @@ where } } -impl<'cursor, 'session, T> Stream for SessionCursorStream<'cursor, 'session, T> +impl Stream for SessionCursorStream<'_, '_, T> where T: DeserializeOwned, { @@ -416,7 +416,7 @@ where } } -impl<'cursor, 'session, T> CursorStream for SessionCursorStream<'cursor, 'session, T> +impl CursorStream for SessionCursorStream<'_, '_, T> where T: DeserializeOwned, { @@ -425,119 +425,9 @@ where } } -impl<'cursor, 'session, T> Drop for SessionCursorStream<'cursor, 'session, T> { +impl Drop for SessionCursorStream<'_, '_, T> { fn drop(&mut self) { // Update the parent cursor's state based on any iteration performed on this handle. self.session_cursor.state = Some(self.generic_cursor.take_state()); } } - -/// Enum determining whether a `SessionCursorHandle` is excuting a getMore or not. -/// In charge of maintaining ownership of the session reference. -enum ExplicitSessionGetMoreProvider<'session> { - /// The handle is currently executing a getMore via the future. - /// - /// This future owns the reference to the session and will return it on completion. - Executing(BoxFuture<'session, ExecutionResult<'session>>), - - /// No future is being executed. - /// - /// This variant needs a `MutableSessionReference` struct that can be moved in order to - /// transition to `Executing` via `take_mut`. - Idle(MutableSessionReference<'session>), -} - -impl<'session> ExplicitSessionGetMoreProvider<'session> { - fn new(session: &'session mut ClientSession) -> Self { - Self::Idle(MutableSessionReference { reference: session }) - } -} - -impl<'session> GetMoreProvider for ExplicitSessionGetMoreProvider<'session> { - type ResultType = ExecutionResult<'session>; - type GetMoreFuture = BoxFuture<'session, ExecutionResult<'session>>; - - fn executing_future(&mut self) -> Option<&mut Self::GetMoreFuture> { - match self { - Self::Executing(future) => Some(future), - Self::Idle { .. } => None, - } - } - - fn clear_execution(&mut self, session: &'session mut ClientSession, _exhausted: bool) { - *self = Self::Idle(MutableSessionReference { reference: session }) - } - - fn start_execution( - &mut self, - info: CursorInformation, - client: Client, - pinned_connection: Option<&PinnedConnectionHandle>, - ) { - take_mut::take(self, |self_| { - if let ExplicitSessionGetMoreProvider::Idle(session) = self_ { - let pinned_connection = pinned_connection.map(|c| c.replicate()); - let future = Box::pin(async move { - let get_more = GetMore::new(info, pinned_connection.as_ref()); - let get_more_result = client - .execute_operation(get_more, Some(&mut *session.reference)) - .await; - ExecutionResult { - get_more_result, - session: session.reference, - } - }); - return ExplicitSessionGetMoreProvider::Executing(future); - } - self_ - }); - } - - fn execute( - &mut self, - info: CursorInformation, - client: Client, - pinned_connection: PinnedConnection, - ) -> BoxFuture<'_, Result> { - match self { - Self::Idle(ref mut session) => Box::pin(async move { - let get_more = GetMore::new(info, pinned_connection.handle()); - client - .execute_operation(get_more, Some(&mut *session.reference)) - .await - }), - Self::Executing(_fut) => Box::pin(async { - Err(Error::internal( - "streaming the cursor was cancelled while a request was in progress and must \ - be continued before iterating manually", - )) - }), - } - } -} - -/// Struct returned from awaiting on a `GetMoreFuture` containing the result of the getMore as -/// well as the reference to the `ClientSession` used for the getMore. -struct ExecutionResult<'session> { - get_more_result: Result, - session: &'session mut ClientSession, -} - -impl<'session> GetMoreProviderResult for ExecutionResult<'session> { - type Session = &'session mut ClientSession; - - fn as_ref(&self) -> std::result::Result<&GetMoreResult, &Error> { - self.get_more_result.as_ref() - } - - fn into_parts(self) -> (Result, Self::Session) { - (self.get_more_result, self.session) - } -} - -/// Wrapper around a mutable reference to a `ClientSession` that provides move semantics. -/// This is used to prevent re-borrowing of the session and forcing it to be moved instead -/// by moving the wrapping struct. -struct MutableSessionReference<'a> { - reference: &'a mut ClientSession, -} diff --git a/src/db.rs b/src/db.rs new file mode 100644 index 000000000..f9da393d7 --- /dev/null +++ b/src/db.rs @@ -0,0 +1,145 @@ +pub(crate) mod action; +pub mod options; + +use std::{fmt::Debug, sync::Arc}; + +use crate::{ + concern::{ReadConcern, WriteConcern}, + gridfs::{options::GridFsBucketOptions, GridFsBucket}, + options::{CollectionOptions, DatabaseOptions}, + selection_criteria::SelectionCriteria, + Client, + Collection, +}; + +/// `Database` is the client-side abstraction of a MongoDB database. It can be used to perform +/// database-level operations or to obtain handles to specific collections within the database. A +/// `Database` can only be obtained through a [`Client`](struct.Client.html) by calling either +/// [`Client::database`](struct.Client.html#method.database) or +/// [`Client::database_with_options`](struct.Client.html#method.database_with_options). +/// +/// `Database` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, +/// so it can safely be shared across threads or async tasks. For example: +/// +/// ```rust +/// +/// # use mongodb::{bson::Document, Client, error::Result}; +/// # +/// # +/// # async fn start_workers() -> Result<()> { +/// # let client = Client::with_uri_str("mongodb://example.com").await?; +/// let db = client.database("items"); +/// +/// for i in 0..5 { +/// let db_ref = db.clone(); +/// +/// tokio::task::spawn(async move { +/// let collection = db_ref.collection::(&format!("coll{}", i)); +/// +/// // Do something with the collection +/// }); +/// } +/// # +/// # Ok(()) +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct Database { + inner: Arc, +} + +#[derive(Debug)] +struct DatabaseInner { + client: Client, + name: String, + selection_criteria: Option, + read_concern: Option, + write_concern: Option, +} + +impl Database { + pub(crate) fn new(client: Client, name: &str, options: Option) -> Self { + let options = options.unwrap_or_default(); + let selection_criteria = options + .selection_criteria + .or_else(|| client.selection_criteria().cloned()); + + let read_concern = options + .read_concern + .or_else(|| client.read_concern().cloned()); + + let write_concern = options + .write_concern + .or_else(|| client.write_concern().cloned()); + + Self { + inner: Arc::new(DatabaseInner { + client, + name: name.to_string(), + selection_criteria, + read_concern, + write_concern, + }), + } + } + + /// Get the `Client` that this collection descended from. + pub fn client(&self) -> &Client { + &self.inner.client + } + + /// Gets the name of the `Database`. + pub fn name(&self) -> &str { + &self.inner.name + } + + /// Gets the read preference of the `Database`. + pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.inner.selection_criteria.as_ref() + } + + /// Gets the read concern of the `Database`. + pub fn read_concern(&self) -> Option<&ReadConcern> { + self.inner.read_concern.as_ref() + } + + /// Gets the write concern of the `Database`. + pub fn write_concern(&self) -> Option<&WriteConcern> { + self.inner.write_concern.as_ref() + } + + /// Gets a handle to a collection in this database with the provided name. The + /// [`Collection`] options (e.g. read preference and write concern) will default to those of + /// this [`Database`]. + /// + /// For more information on how the generic parameter `T` is used, check out the [`Collection`] + /// documentation. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn collection(&self, name: &str) -> Collection { + Collection::new(self.clone(), name, None) + } + + /// Gets a handle to a collection in this database with the provided name. + /// Operations done with this `Collection` will use the options specified by + /// `options` and will otherwise default to those of this [`Database`]. + /// + /// For more information on how the generic parameter `T` is used, check out the [`Collection`] + /// documentation. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn collection_with_options( + &self, + name: &str, + options: CollectionOptions, + ) -> Collection { + Collection::new(self.clone(), name, Some(options)) + } + + /// Creates a new [`GridFsBucket`] in the database with the given options. + pub fn gridfs_bucket(&self, options: impl Into>) -> GridFsBucket { + GridFsBucket::new(self.clone(), options.into().unwrap_or_default()) + } +} diff --git a/src/db/action.rs b/src/db/action.rs new file mode 100644 index 000000000..ba61d596b --- /dev/null +++ b/src/db/action.rs @@ -0,0 +1 @@ +pub(crate) mod create_collection; diff --git a/src/db/action/create_collection.rs b/src/db/action/create_collection.rs new file mode 100644 index 000000000..769896c33 --- /dev/null +++ b/src/db/action/create_collection.rs @@ -0,0 +1,137 @@ +use crate::{ + action::{action_impl, CreateCollection}, + error::Result, + operation::create as op, + Database, + Namespace, +}; + +#[action_impl] +impl<'a> Action for CreateCollection<'a> { + type Future = CreateCollectionFuture; + + async fn execute(mut self) -> Result<()> { + resolve_options!(self.db, self.options, [write_concern]); + + let ns = Namespace { + db: self.db.name().to_string(), + coll: self.name, + }; + + #[cfg(feature = "in-use-encryption")] + let has_encrypted_fields = { + self.db + .resolve_encrypted_fields(&ns, &mut self.options) + .await; + self.db + .create_aux_collections(&ns, &self.options, self.session.as_deref_mut()) + .await?; + self.options + .as_ref() + .and_then(|o| o.encrypted_fields.as_ref()) + .is_some() + }; + + let create = op::Create::new(ns.clone(), self.options); + self.db + .client() + .execute_operation(create, self.session.as_deref_mut()) + .await?; + + #[cfg(feature = "in-use-encryption")] + if has_encrypted_fields { + use crate::{ + action::Action, + bson::{doc, Document}, + }; + let coll = self.db.collection::(&ns.coll); + coll.create_index(crate::IndexModel { + keys: doc! {"__safeContent__": 1}, + options: None, + }) + .optional(self.session.as_deref_mut(), |a, s| a.session(s)) + .await?; + } + + Ok(()) + } +} + +impl Database { + #[cfg(feature = "in-use-encryption")] + async fn resolve_encrypted_fields( + &self, + base_ns: &Namespace, + options: &mut Option, + ) { + let has_encrypted_fields = options + .as_ref() + .and_then(|o| o.encrypted_fields.as_ref()) + .is_some(); + // If options does not have `associated_fields`, populate it from client-wide + // `encrypted_fields_map`: + if !has_encrypted_fields { + let enc_opts = self.client().auto_encryption_opts().await; + if let Some(enc_opts_fields) = enc_opts + .as_ref() + .and_then(|eo| eo.encrypted_fields_map.as_ref()) + .and_then(|efm| efm.get(&format!("{}", &base_ns))) + { + options + .get_or_insert_with(Default::default) + .encrypted_fields = Some(enc_opts_fields.clone()); + } + } + } + + #[cfg(feature = "in-use-encryption")] + #[allow(clippy::needless_option_as_deref)] + async fn create_aux_collections( + &self, + base_ns: &Namespace, + options: &Option, + mut session: Option<&mut crate::ClientSession>, + ) -> Result<()> { + use crate::{bson::doc, error::ErrorKind}; + + let opts = match options { + Some(o) => o, + None => return Ok(()), + }; + let enc_fields = match &opts.encrypted_fields { + Some(f) => f, + None => return Ok(()), + }; + let max_wire = match self.client().primary_description().await { + Some(p) => p.max_wire_version()?, + None => None, + }; + const SERVER_7_0_0_WIRE_VERSION: i32 = 21; + match max_wire { + None => (), + Some(v) if v >= SERVER_7_0_0_WIRE_VERSION => (), + _ => { + return Err(ErrorKind::IncompatibleServer { + message: "Driver support of Queryable Encryption is incompatible with server. \ + Upgrade server to use Queryable Encryption." + .to_string(), + } + .into()) + } + } + for ns in crate::client::csfle::aux_collections(base_ns, enc_fields)? { + let mut sub_opts = opts.clone(); + sub_opts.clustered_index = Some(crate::db::options::ClusteredIndex { + key: doc! { "_id": 1 }, + unique: true, + name: None, + v: None, + }); + let create = op::Create::new(ns, Some(sub_opts)); + self.client() + .execute_operation(create, session.as_deref_mut()) + .await?; + } + Ok(()) + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs deleted file mode 100644 index a16ec7a25..000000000 --- a/src/db/mod.rs +++ /dev/null @@ -1,607 +0,0 @@ -pub mod options; - -use std::{fmt::Debug, sync::Arc}; - -#[cfg(feature = "in-use-encryption-unstable")] -use bson::doc; -use futures_util::stream::TryStreamExt; - -use crate::{ - bson::{Bson, Document}, - change_stream::{ - event::ChangeStreamEvent, - options::ChangeStreamOptions, - session::SessionChangeStream, - ChangeStream, - }, - client::session::TransactionState, - cmap::conn::PinnedConnectionHandle, - concern::{ReadConcern, WriteConcern}, - cursor::Cursor, - error::{Error, ErrorKind, Result}, - gridfs::{options::GridFsBucketOptions, GridFsBucket}, - operation::{Aggregate, AggregateTarget, Create, DropDatabase, ListCollections, RunCommand}, - options::{ - AggregateOptions, - CollectionOptions, - CreateCollectionOptions, - DatabaseOptions, - DropDatabaseOptions, - ListCollectionsOptions, - }, - results::CollectionSpecification, - selection_criteria::SelectionCriteria, - Client, - ClientSession, - Collection, - Namespace, - SessionCursor, -}; - -/// `Database` is the client-side abstraction of a MongoDB database. It can be used to perform -/// database-level operations or to obtain handles to specific collections within the database. A -/// `Database` can only be obtained through a [`Client`](struct.Client.html) by calling either -/// [`Client::database`](struct.Client.html#method.database) or -/// [`Client::database_with_options`](struct.Client.html#method.database_with_options). -/// -/// `Database` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, -/// so it can safely be shared across threads or async tasks. For example: -/// -/// ```rust -/// -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # use mongodb::{bson::Document, Client, error::Result}; -/// # #[cfg(feature = "async-std-runtime")] -/// # use async_std::task; -/// # #[cfg(feature = "tokio-runtime")] -/// # use tokio::task; -/// # -/// # -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # async fn start_workers() -> Result<()> { -/// # let client = Client::with_uri_str("mongodb://example.com").await?; -/// let db = client.database("items"); -/// -/// for i in 0..5 { -/// let db_ref = db.clone(); -/// -/// task::spawn(async move { -/// let collection = db_ref.collection::(&format!("coll{}", i)); -/// -/// // Do something with the collection -/// }); -/// } -/// # -/// # Ok(()) -/// # } -/// ``` -#[derive(Clone, Debug)] -pub struct Database { - inner: Arc, -} - -#[derive(Debug)] -struct DatabaseInner { - client: Client, - name: String, - selection_criteria: Option, - read_concern: Option, - write_concern: Option, -} - -impl Database { - pub(crate) fn new(client: Client, name: &str, options: Option) -> Self { - let options = options.unwrap_or_default(); - let selection_criteria = options - .selection_criteria - .or_else(|| client.selection_criteria().cloned()); - - let read_concern = options - .read_concern - .or_else(|| client.read_concern().cloned()); - - let write_concern = options - .write_concern - .or_else(|| client.write_concern().cloned()); - - Self { - inner: Arc::new(DatabaseInner { - client, - name: name.to_string(), - selection_criteria, - read_concern, - write_concern, - }), - } - } - - /// Get the `Client` that this collection descended from. - pub(crate) fn client(&self) -> &Client { - &self.inner.client - } - - /// Gets the name of the `Database`. - pub fn name(&self) -> &str { - &self.inner.name - } - - /// Gets the read preference of the `Database`. - pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.inner.selection_criteria.as_ref() - } - - /// Gets the read concern of the `Database`. - pub fn read_concern(&self) -> Option<&ReadConcern> { - self.inner.read_concern.as_ref() - } - - /// Gets the write concern of the `Database`. - pub fn write_concern(&self) -> Option<&WriteConcern> { - self.inner.write_concern.as_ref() - } - - /// Gets a handle to a collection in this database with the provided name. The - /// [`Collection`] options (e.g. read preference and write concern) will default to those of - /// this [`Database`]. - /// - /// For more information on how the generic parameter `T` is used, check out the [`Collection`] - /// documentation. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn collection(&self, name: &str) -> Collection { - Collection::new(self.clone(), name, None) - } - - /// Gets a handle to a collection in this database with the provided name. - /// Operations done with this `Collection` will use the options specified by - /// `options` and will otherwise default to those of this [`Database`]. - /// - /// For more information on how the generic parameter `T` is used, check out the [`Collection`] - /// documentation. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn collection_with_options( - &self, - name: &str, - options: CollectionOptions, - ) -> Collection { - Collection::new(self.clone(), name, Some(options)) - } - - async fn drop_common( - &self, - options: impl Into>, - session: impl Into>, - ) -> Result<()> { - let mut options = options.into(); - resolve_options!(self, options, [write_concern]); - - let drop_database = DropDatabase::new(self.name().to_string(), options); - self.client() - .execute_operation(drop_database, session) - .await - } - - /// Drops the database, deleting all data, collections, and indexes stored in it. - pub async fn drop(&self, options: impl Into>) -> Result<()> { - self.drop_common(options, None).await - } - - /// Drops the database, deleting all data, collections, and indexes stored in it using the - /// provided `ClientSession`. - pub async fn drop_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - self.drop_common(options, session).await - } - - /// Gets information about each of the collections in the database. The cursor will yield a - /// document pertaining to each collection in the database. - pub async fn list_collections( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - let list_collections = ListCollections::new( - self.name().to_string(), - filter.into(), - false, - options.into(), - ); - self.client() - .execute_cursor_operation(list_collections) - .await - } - - /// Gets information about each of the collections in the database using the provided - /// `ClientSession`. The cursor will yield a document pertaining to each collection in the - /// database. - pub async fn list_collections_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let list_collections = ListCollections::new( - self.name().to_string(), - filter.into(), - false, - options.into(), - ); - self.client() - .execute_session_cursor_operation(list_collections, session) - .await - } - - async fn list_collection_names_common( - &self, - cursor: impl TryStreamExt, - ) -> Result> { - cursor - .and_then(|doc| match doc.get("name").and_then(Bson::as_str) { - Some(name) => futures_util::future::ok(name.into()), - None => futures_util::future::err( - ErrorKind::InvalidResponse { - message: "Expected name field in server response, but there was none." - .to_string(), - } - .into(), - ), - }) - .try_collect() - .await - } - - /// Gets the names of the collections in the database. - pub async fn list_collection_names( - &self, - filter: impl Into>, - ) -> Result> { - let list_collections = - ListCollections::new(self.name().to_string(), filter.into(), true, None); - let cursor: Cursor = self - .client() - .execute_cursor_operation(list_collections) - .await?; - - self.list_collection_names_common(cursor).await - } - - /// Gets the names of the collections in the database using the provided `ClientSession`. - pub async fn list_collection_names_with_session( - &self, - filter: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let list_collections = - ListCollections::new(self.name().to_string(), filter.into(), true, None); - let mut cursor: SessionCursor = self - .client() - .execute_session_cursor_operation(list_collections, &mut *session) - .await?; - - self.list_collection_names_common(cursor.stream(session)) - .await - } - - #[allow(clippy::needless_option_as_deref)] - async fn create_collection_common( - &self, - name: impl AsRef, - options: impl Into>, - session: impl Into>, - ) -> Result<()> { - let mut options: Option = options.into(); - resolve_options!(self, options, [write_concern]); - let mut session = session.into(); - - let ns = Namespace { - db: self.name().to_string(), - coll: name.as_ref().to_string(), - }; - - #[cfg(feature = "in-use-encryption-unstable")] - let has_encrypted_fields = { - self.resolve_encrypted_fields(&ns, &mut options).await; - self.create_aux_collections(&ns, &options, session.as_deref_mut()) - .await?; - options - .as_ref() - .and_then(|o| o.encrypted_fields.as_ref()) - .is_some() - }; - - let create = Create::new(ns.clone(), options); - self.client() - .execute_operation(create, session.as_deref_mut()) - .await?; - - #[cfg(feature = "in-use-encryption-unstable")] - if has_encrypted_fields { - let coll = self.collection::(&ns.coll); - coll.create_index_common( - crate::IndexModel { - keys: doc! {"__safeContent__": 1}, - options: None, - }, - None, - session.as_deref_mut(), - ) - .await?; - } - - Ok(()) - } - - #[cfg(feature = "in-use-encryption-unstable")] - async fn resolve_encrypted_fields( - &self, - base_ns: &Namespace, - options: &mut Option, - ) { - let has_encrypted_fields = options - .as_ref() - .and_then(|o| o.encrypted_fields.as_ref()) - .is_some(); - // If options does not have `associated_fields`, populate it from client-wide - // `encrypted_fields_map`: - if !has_encrypted_fields { - let enc_opts = self.client().auto_encryption_opts().await; - if let Some(enc_opts_fields) = enc_opts - .as_ref() - .and_then(|eo| eo.encrypted_fields_map.as_ref()) - .and_then(|efm| efm.get(&format!("{}", &base_ns))) - { - options - .get_or_insert_with(Default::default) - .encrypted_fields = Some(enc_opts_fields.clone()); - } - } - } - - #[cfg(feature = "in-use-encryption-unstable")] - #[allow(clippy::needless_option_as_deref)] - async fn create_aux_collections( - &self, - base_ns: &Namespace, - options: &Option, - mut session: Option<&mut ClientSession>, - ) -> Result<()> { - let opts = match options { - Some(o) => o, - None => return Ok(()), - }; - let enc_fields = match &opts.encrypted_fields { - Some(f) => f, - None => return Ok(()), - }; - let max_wire = match self.client().primary_description().await { - Some(p) => p.max_wire_version()?, - None => None, - }; - const SERVER_7_0_0_WIRE_VERSION: i32 = 21; - match max_wire { - Some(v) if v >= SERVER_7_0_0_WIRE_VERSION => (), - _ => { - return Err(ErrorKind::IncompatibleServer { - message: "Driver support of Queryable Encryption is incompatible with server. \ - Upgrade server to use Queryable Encryption." - .to_string(), - } - .into()) - } - } - for ns in crate::client::csfle::aux_collections(base_ns, enc_fields)? { - let mut sub_opts = opts.clone(); - sub_opts.clustered_index = Some(self::options::ClusteredIndex { - key: doc! { "_id": 1 }, - unique: true, - name: None, - v: None, - }); - let create = Create::new(ns, Some(sub_opts)); - self.client() - .execute_operation(create, session.as_deref_mut()) - .await?; - } - Ok(()) - } - - /// Creates a new collection in the database with the given `name` and `options`. - /// - /// Note that MongoDB creates collections implicitly when data is inserted, so this method is - /// not needed if no special options are required. - pub async fn create_collection( - &self, - name: impl AsRef, - options: impl Into>, - ) -> Result<()> { - self.create_collection_common(name, options, None).await - } - - /// Creates a new collection in the database with the given `name` and `options` using the - /// provided `ClientSession`. - /// - /// Note that MongoDB creates collections implicitly when data is inserted, so this method is - /// not needed if no special options are required. - pub async fn create_collection_with_session( - &self, - name: impl AsRef, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - self.create_collection_common(name, options, session).await - } - - pub(crate) async fn run_command_common( - &self, - command: Document, - selection_criteria: impl Into>, - session: impl Into>, - pinned_connection: Option<&PinnedConnectionHandle>, - ) -> Result { - let operation = RunCommand::new( - self.name().into(), - command, - selection_criteria.into(), - pinned_connection, - )?; - self.client().execute_operation(operation, session).await - } - - /// Runs a database-level command. - /// - /// Note that no inspection is done on `doc`, so the command will not use the database's default - /// read concern or write concern. If specific read concern or write concern is desired, it must - /// be specified manually. - pub async fn run_command( - &self, - command: Document, - selection_criteria: impl Into>, - ) -> Result { - self.run_command_common(command, selection_criteria, None, None) - .await - } - - /// Runs a database-level command using the provided `ClientSession`. - /// - /// If the `ClientSession` provided is currently in a transaction, `command` must not specify a - /// read concern. If this operation is the first operation in the transaction, the read concern - /// associated with the transaction will be inherited. - /// - /// Otherwise no inspection is done on `command`, so the command will not use the database's - /// default read concern or write concern. If specific read concern or write concern is - /// desired, it must be specified manually. - pub async fn run_command_with_session( - &self, - command: Document, - selection_criteria: impl Into>, - session: &mut ClientSession, - ) -> Result { - let mut selection_criteria = selection_criteria.into(); - match session.transaction.state { - TransactionState::Starting | TransactionState::InProgress => { - if command.contains_key("readConcern") { - return Err(ErrorKind::InvalidArgument { - message: "Cannot set read concern after starting a transaction".into(), - } - .into()); - } - selection_criteria = match selection_criteria { - Some(selection_criteria) => Some(selection_criteria), - None => { - if let Some(ref options) = session.transaction.options { - options.selection_criteria.clone() - } else { - None - } - } - }; - } - _ => {} - } - self.run_command_common(command, selection_criteria, session, None) - .await - } - - /// Runs an aggregation operation. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub async fn aggregate( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result> { - let mut options = options.into(); - resolve_options!( - self, - options, - [read_concern, write_concern, selection_criteria] - ); - - let aggregate = Aggregate::new(self.name().to_string(), pipeline, options); - let client = self.client(); - client.execute_cursor_operation(aggregate).await - } - - /// Runs an aggregation operation with the provided `ClientSession`. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub async fn aggregate_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let mut options = options.into(); - resolve_options!( - self, - options, - [read_concern, write_concern, selection_criteria] - ); - - let aggregate = Aggregate::new(self.name().to_string(), pipeline, options); - let client = self.client(); - client - .execute_session_cursor_operation(aggregate, session) - .await - } - - /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events - /// for all changes in this database. The stream does not observe changes from system - /// collections and cannot be started on "config", "local" or "admin" databases. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read - /// concern. Anything else will cause a server error. - /// - /// Note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` - /// fields will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub async fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> { - let mut options = options.into(); - resolve_options!(self, options, [read_concern, selection_criteria]); - let target = AggregateTarget::Database(self.name().to_string()); - self.client() - .execute_watch(pipeline, options, target, None) - .await - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in this database - /// using the provided [`ClientSession`]. See [`Database::watch`] for more information. - pub async fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> { - let mut options = options.into(); - resolve_read_concern_with_session!(self, options, Some(&mut *session))?; - resolve_selection_criteria_with_session!(self, options, Some(&mut *session))?; - let target = AggregateTarget::Database(self.name().to_string()); - self.client() - .execute_watch_with_session(pipeline, options, target, None, session) - .await - } - - /// Creates a new [`GridFsBucket`] in the database with the given options. - pub fn gridfs_bucket(&self, options: impl Into>) -> GridFsBucket { - GridFsBucket::new(self.clone(), options.into().unwrap_or_default()) - } -} diff --git a/src/db/options.rs b/src/db/options.rs index 44d3d8533..ee987ce28 100644 --- a/src/db/options.rs +++ b/src/db/options.rs @@ -1,16 +1,17 @@ use std::time::Duration; -use bson::doc; +use crate::bson::doc; +use macro_magic::export_tokens; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use typed_builder::TypedBuilder; use crate::{ bson::{Bson, Document}, - bson_util, concern::{ReadConcern, WriteConcern}, - options::Collation, + options::{Collation, CursorType}, selection_criteria::SelectionCriteria, + serde_util::{self, write_concern_is_empty}, }; /// These are the valid options for creating a [`Database`](../struct.Database.html) with @@ -36,19 +37,20 @@ pub struct DatabaseOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct CreateCollectionOptions { /// Whether the collection should be capped. If true, `size` must also be set. pub capped: Option, /// The maximum size (in bytes) for a capped collection. This option is ignored if `capped` is /// not set to true. - #[serde(serialize_with = "bson_util::serialize_u64_option_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_option_as_i64")] pub size: Option, /// The maximum number of documents in a capped collection. The `size` limit takes precedence /// over this option. If a capped collection reaches the size limit before it reaches the /// maximum number of documents, MongoDB removes old documents. - #[serde(serialize_with = "bson_util::serialize_u64_option_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_option_as_i64")] pub max: Option, /// The storage engine that the collection should use. The value should take the following @@ -82,6 +84,7 @@ pub struct CreateCollectionOptions { pub collation: Option, /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, /// The default configuration for indexes created on this collection, including the _id index. @@ -98,17 +101,14 @@ pub struct CreateCollectionOptions { /// Used to automatically delete documents in time series collections. See the [`create` /// command documentation](https://www.mongodb.com/docs/manual/reference/command/create/) for more /// information. - #[serde( - default, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_seconds", - serialize_with = "bson_util::serialize_duration_option_as_int_secs" - )] + #[serde(default, with = "serde_util::duration_option_as_int_seconds")] pub expire_after_seconds: Option, /// Options for supporting change stream pre- and post-images. pub change_stream_pre_and_post_images: Option, - /// Options for clustered collections. + /// Options for clustered collections. This option is only available on server versions 5.3+. + #[serde(default, deserialize_with = "ClusteredIndex::deserialize_self_or_true")] pub clustered_index: Option, /// Tags the query with an arbitrary [`Bson`] value to help trace the operation through the @@ -118,7 +118,7 @@ pub struct CreateCollectionOptions { pub comment: Option, /// Map of encrypted fields for the created collection. - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub encrypted_fields: Option, } @@ -180,8 +180,38 @@ impl Default for ClusteredIndex { } } +impl ClusteredIndex { + /// When creating a time-series collection on MongoDB Atlas the `clusteredIndex` field of the + /// collection options is given as `true` instead of as an object that deserializes to + /// `ClusteredIndex`. This custom deserializer handles that case by using the default value for + /// `ClusteredIndex`. + fn deserialize_self_or_true<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum ValueUnion { + Bool(bool), + ClusteredIndex(ClusteredIndex), + } + + let value_option: Option = Deserialize::deserialize(deserializer)?; + value_option + .map(|value| match value { + ValueUnion::Bool(true) => Ok(ClusteredIndex::default()), + ValueUnion::Bool(false) => Err(serde::de::Error::custom( + "if clusteredIndex is a boolean it must be `true`", + )), + ValueUnion::ClusteredIndex(value) => Ok(value), + }) + .transpose() + } +} + /// Specifies default configuration for indexes created on a collection, including the _id index. #[derive(Clone, Debug, TypedBuilder, PartialEq, Serialize, Deserialize)] +#[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct IndexOptionDefaults { @@ -192,8 +222,10 @@ pub struct IndexOptionDefaults { } /// Specifies options for creating a timeseries collection. +#[skip_serializing_none] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, TypedBuilder)] #[serde(rename_all = "camelCase")] +#[builder(field_defaults(default, setter(into)))] #[non_exhaustive] pub struct TimeseriesOptions { /// Name of the top-level field to be used for time. Inserted documents must have this field, @@ -208,6 +240,31 @@ pub struct TimeseriesOptions { /// The units you'd use to describe the expected interval between subsequent measurements for a /// time-series. Defaults to `TimeseriesGranularity::Seconds` if unset. pub granularity: Option, + + /// The maximum time between timestamps in the same bucket. This value must be between 1 and + /// 31,536,000 seconds. If this value is set, the same value should be set for + /// `bucket_rounding` and `granularity` should not be set. + /// + /// This option is only available on MongoDB 6.3+. + #[serde( + default, + with = "serde_util::duration_option_as_int_seconds", + rename = "bucketMaxSpanSeconds" + )] + pub bucket_max_span: Option, + + /// The time interval that determines the starting timestamp for a new bucket. When a document + /// requires a new bucket, MongoDB rounds down the document's timestamp value by this interval + /// to set the minimum time for the bucket. If this value is set, the same value should be set + /// for `bucket_max_span` and `granularity` should not be set. + /// + /// This option is only available on MongoDB 6.3+. + #[serde( + default, + with = "serde_util::duration_option_as_int_seconds", + rename = "bucketRoundingSeconds" + )] + pub bucket_rounding: Option, } /// The units you'd use to describe the expected interval between subsequent measurements for a @@ -224,13 +281,15 @@ pub enum TimeseriesGranularity { Hours, } -/// Specifies the options to a [`Database::drop`](../struct.Database.html#method.drop) operation. +/// Specifies the options to a [`Database::drop`](crate::Database::drop) operation. #[derive(Clone, Debug, Default, TypedBuilder, Serialize)] #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct DropDatabaseOptions { /// The write concern for the operation. + #[serde(skip_serializing_if = "write_concern_is_empty")] pub write_concern: Option, } @@ -241,6 +300,7 @@ pub struct DropDatabaseOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct ListCollectionsOptions { /// The number of documents the server should return per cursor batch. /// @@ -249,7 +309,7 @@ pub struct ListCollectionsOptions { /// number of round trips needed to return the entire set of documents returned by the /// query). #[serde( - serialize_with = "bson_util::serialize_u32_option_as_batch_size", + serialize_with = "serde_util::serialize_u32_option_as_batch_size", rename(serialize = "cursor") )] pub batch_size: Option, @@ -259,15 +319,24 @@ pub struct ListCollectionsOptions { /// /// This option is only available on server versions 4.4+. pub comment: Option, + + /// Filters the list operation. + pub filter: Option, + + /// When `true` and used with + /// [`list_collection_names`](crate::Database::list_collection_names), the command returns + /// only those collections for which the user has privileges. When used with + /// [`list_collections`](crate::Database::list_collections) this option has no effect. + pub authorized_collections: Option, } -/// Specifies the options to a -/// [`Client::list_databases`](../struct.Client.html#method.list_databases) operation. +/// Specifies the options to a [`Client::list_databases`](crate::Client::list_databases) operation. #[skip_serializing_none] #[derive(Clone, Debug, Default, Deserialize, TypedBuilder, Serialize)] #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct ListDatabasesOptions { /// Determines which databases to return based on the user's access privileges. This option is /// only supported on server versions 4.0.5+. @@ -278,6 +347,9 @@ pub struct ListDatabasesOptions { /// /// This option is only available on server versions 4.4+. pub comment: Option, + + /// Filters the query. + pub filter: Option, } /// Specifies how change stream pre- and post-images should be supported. @@ -289,3 +361,39 @@ pub struct ChangeStreamPreAndPostImages { /// If `true`, change streams will be able to include pre- and post-images. pub enabled: bool, } + +/// Specifies the options to a +/// [`Database::run_command`](crate::Database::run_command) operation. +#[derive(Clone, Debug, Default, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +#[export_tokens] +pub struct RunCommandOptions { + /// The default read preference for operations. + pub selection_criteria: Option, +} + +/// Specifies the options to a +/// [`Database::run_cursor_command`](crate::Database::run_cursor_command) operation. +#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[serde(default)] +#[non_exhaustive] +#[export_tokens] +pub struct RunCursorCommandOptions { + /// The default read preference for operations. + pub selection_criteria: Option, + /// The type of cursor to return. + pub cursor_type: Option, + /// Number of documents to return per batch. + pub batch_size: Option, + #[serde(rename = "maxtime", alias = "maxTimeMS")] + #[serde(deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis")] + /// Optional non-negative integer value. Use this value to configure the maxTimeMS option sent + /// on subsequent getMore commands. + pub max_time: Option, + /// Optional BSON value. Use this value to configure the comment option sent on subsequent + /// getMore commands. + pub comment: Option, +} diff --git a/src/error.rs b/src/error.rs index 068900755..f7b4641e1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,7 @@ //! Contains the `Error` and `Result` types that `mongodb` uses. +pub(crate) mod bulk_write; + use std::{ any::Any, collections::{HashMap, HashSet}, @@ -7,21 +9,28 @@ use std::{ sync::Arc, }; -use bson::Bson; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::{bson::Document, options::ServerAddress, sdam::TopologyVersion}; +use crate::{ + bson::{Bson, Document}, + options::ServerAddress, + sdam::{ServerType, TopologyVersion}, +}; + +pub use bulk_write::{BulkWriteError, PartialBulkWriteResult}; const RECOVERING_CODES: [i32; 5] = [11600, 11602, 13436, 189, 91]; const NOTWRITABLEPRIMARY_CODES: [i32; 3] = [10107, 13435, 10058]; const SHUTTING_DOWN_CODES: [i32; 2] = [11600, 91]; -const RETRYABLE_READ_CODES: [i32; 11] = - [11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001]; +const RETRYABLE_READ_CODES: [i32; 13] = [ + 11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001, 134, 262, +]; const RETRYABLE_WRITE_CODES: [i32; 12] = [ 11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001, 262, ]; const UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL_CODES: [i32; 3] = [50, 64, 91]; +const REAUTHENTICATION_REQUIRED_CODE: i32 = 391; /// Retryable write error label. This label will be added to an error when the error is /// write-retryable. @@ -38,10 +47,11 @@ pub const UNKNOWN_TRANSACTION_COMMIT_RESULT: &str = "UnknownTransactionCommitRes pub type Result = std::result::Result; /// An error that can occur in the `mongodb` crate. The inner -/// [`ErrorKind`](enum.ErrorKind.html) is wrapped in an `Arc` to allow the errors to be +/// [`ErrorKind`](enum.ErrorKind.html) is wrapped in an `Box` to allow the errors to be /// cloned. #[derive(Clone, Debug, Error)] -#[error("Kind: {kind}, labels: {labels:?}")] +#[cfg_attr(test, error("Kind: {kind}, labels: {labels:?}, backtrace: {bt}"))] +#[cfg_attr(not(test), error("Kind: {kind}, labels: {labels:?}"))] #[non_exhaustive] pub struct Error { /// The type of error that occurred. @@ -50,11 +60,13 @@ pub struct Error { pub(crate) wire_version: Option, #[source] pub(crate) source: Option>, + #[cfg(test)] + bt: Arc, } impl Error { /// Create a new `Error` wrapping an arbitrary value. Can be used to abort transactions in - /// callbacks for [`ClientSession::with_transaction`](crate::ClientSession::with_transaction). + /// callbacks for [`StartTransaction::and_run`](crate::action::StartTransaction::and_run). pub fn custom(e: impl Any + Send + Sync) -> Self { Self::new(ErrorKind::Custom(Arc::new(e)), None::>) } @@ -81,6 +93,8 @@ impl Error { labels, wire_version: None, source: None, + #[cfg(test)] + bt: Arc::new(std::backtrace::Backtrace::capture()), } } @@ -120,6 +134,13 @@ impl Error { .into() } + pub(crate) fn invalid_response(message: impl Into) -> Error { + ErrorKind::InvalidResponse { + message: message.into(), + } + .into() + } + /// Construct a generic network timeout error. pub(crate) fn network_timeout() -> Error { ErrorKind::Io(Arc::new(std::io::ErrorKind::TimedOut.into())).into() @@ -176,17 +197,38 @@ impl Error { self.contains_label(RETRYABLE_WRITE_ERROR) } + fn is_write_concern_error(&self) -> bool { + match *self.kind { + ErrorKind::Write(WriteFailure::WriteConcernError(_)) => true, + ErrorKind::InsertMany(ref insert_many_error) + if insert_many_error.write_concern_error.is_some() => + { + true + } + _ => false, + } + } + /// Whether a "RetryableWriteError" label should be added to this error. If max_wire_version /// indicates a 4.4+ server, a label should only be added if the error is a network error. /// Otherwise, a label should be added if the error is a network error or the error code /// matches one of the retryable write codes. - pub(crate) fn should_add_retryable_write_label(&self, max_wire_version: i32) -> bool { + pub(crate) fn should_add_retryable_write_label( + &self, + max_wire_version: i32, + server_type: Option, + ) -> bool { if max_wire_version > 8 { return self.is_network_error(); } if self.is_network_error() { return true; } + + if server_type == Some(ServerType::Mongos) && self.is_write_concern_error() { + return false; + } + match &self.sdam_code() { Some(code) => RETRYABLE_WRITE_CODES.contains(code), None => false, @@ -212,7 +254,7 @@ impl Error { matches!( self.kind.as_ref(), ErrorKind::Authentication { .. } - | ErrorKind::BulkWrite(_) + | ErrorKind::InsertMany(_) | ErrorKind::Command(_) | ErrorKind::Write(_) ) @@ -225,7 +267,13 @@ impl Error { /// Whether this error contains the specified label. pub fn contains_label>(&self, label: T) -> bool { - self.labels().contains(label.as_ref()) + let label = label.as_ref(); + self.labels().contains(label) + || self + .source + .as_ref() + .map(|source| source.contains_label(label)) + .unwrap_or(false) } /// Adds the given label to this error. @@ -234,7 +282,16 @@ impl Error { self.labels.insert(label); } - pub(crate) fn from_resolve_error(error: trust_dns_resolver::error::ResolveError) -> Self { + #[cfg(feature = "dns-resolver")] + pub(crate) fn from_resolve_error(error: hickory_resolver::error::ResolveError) -> Self { + ErrorKind::DnsResolve { + message: error.to_string(), + } + .into() + } + + #[cfg(feature = "dns-resolver")] + pub(crate) fn from_resolve_proto_error(error: hickory_proto::error::ProtoError) -> Self { ErrorKind::DnsResolve { message: error.to_string(), } @@ -252,7 +309,7 @@ impl Error { ) } - #[cfg(all(test, feature = "in-use-encryption-unstable"))] + #[cfg(all(test, feature = "in-use-encryption"))] pub(crate) fn is_csfle_error(&self) -> bool { matches!(self.kind.as_ref(), ErrorKind::Encryption(..)) } @@ -264,7 +321,7 @@ impl Error { ErrorKind::Command(command_error) => Some(command_error.code), // According to SDAM spec, write concern error codes MUST also be checked, and // writeError codes MUST NOT be checked. - ErrorKind::BulkWrite(BulkWriteFailure { + ErrorKind::InsertMany(InsertManyError { write_concern_error: Some(wc_error), .. }) => Some(wc_error.code), @@ -275,11 +332,11 @@ impl Error { } /// Gets the code from this error. - #[allow(unused)] + #[cfg(test)] pub(crate) fn code(&self) -> Option { match self.kind.as_ref() { ErrorKind::Command(command_error) => Some(command_error.code), - ErrorKind::BulkWrite(BulkWriteFailure { + ErrorKind::InsertMany(InsertManyError { write_concern_error: Some(wc_error), .. }) => Some(wc_error.code), @@ -290,15 +347,15 @@ impl Error { } /// Gets the message for this error, if applicable, for use in testing. - /// If this error is a BulkWriteError, the messages are concatenated. + /// If this error is an InsertManyError, the messages are concatenated. #[cfg(test)] pub(crate) fn message(&self) -> Option { match self.kind.as_ref() { ErrorKind::Command(command_error) => Some(command_error.message.clone()), // since this is used primarily for errorMessageContains assertions in the unified // runner, we just concatenate all the relevant server messages into one for - // bulk errors. - ErrorKind::BulkWrite(BulkWriteFailure { + // insert many errors. + ErrorKind::InsertMany(InsertManyError { write_concern_error, write_errors, inserted_ids: _, @@ -323,7 +380,7 @@ impl Error { ErrorKind::Transaction { message } => Some(message.clone()), ErrorKind::IncompatibleServer { message } => Some(message.clone()), ErrorKind::InvalidArgument { message } => Some(message.clone()), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] ErrorKind::Encryption(err) => err.message.clone(), _ => None, } @@ -338,7 +395,7 @@ impl Error { WriteFailure::WriteConcernError(ref wce) => Some(wce.code_name.as_str()), WriteFailure::WriteError(ref we) => we.code_name.as_deref(), }, - ErrorKind::BulkWrite(ref bwe) => bwe + ErrorKind::InsertMany(ref bwe) => bwe .write_concern_error .as_ref() .map(|wce| wce.code_name.as_str()), @@ -353,6 +410,11 @@ impl Error { .unwrap_or(false) } + /// If this error corresponds to a "reauthentication required" error. + pub(crate) fn is_reauthentication_required(&self) -> bool { + self.sdam_code() == Some(REAUTHENTICATION_REQUIRED_CODE) + } + /// If this error corresponds to a "node is recovering" error as per the SDAM spec. pub(crate) fn is_recovering(&self) -> bool { self.sdam_code() @@ -425,19 +487,31 @@ impl Error { /// sensitive commands. Currently, the only field besides those that we expose is the /// error message. pub(crate) fn redact(&mut self) { + if let Some(source) = self.source.as_deref_mut() { + source.redact(); + } + // This is intentionally written without a catch-all branch so that if new error // kinds are added we remember to reason about whether they need to be redacted. match *self.kind { - ErrorKind::BulkWrite(ref mut bwe) => { - if let Some(ref mut wes) = bwe.write_errors { + ErrorKind::InsertMany(ref mut insert_many_error) => { + if let Some(ref mut wes) = insert_many_error.write_errors { for we in wes { we.redact(); } } - if let Some(ref mut wce) = bwe.write_concern_error { + if let Some(ref mut wce) = insert_many_error.write_concern_error { wce.redact(); } } + ErrorKind::BulkWrite(ref mut bulk_write_error) => { + for write_concern_error in bulk_write_error.write_concern_errors.iter_mut() { + write_concern_error.redact(); + } + for (_, write_error) in bulk_write_error.write_errors.iter_mut() { + write_error.redact(); + } + } ErrorKind::Command(ref mut command_error) => { command_error.redact(); } @@ -465,9 +539,12 @@ impl Error { | ErrorKind::MissingResumeToken | ErrorKind::Authentication { .. } | ErrorKind::Custom(_) + | ErrorKind::Shutdown | ErrorKind::GridFs(_) => {} - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] ErrorKind::Encryption(_) => {} + #[cfg(feature = "bson-3")] + ErrorKind::Bson(_) => {} } } } @@ -481,26 +558,36 @@ where } } -impl From for ErrorKind { - fn from(err: bson::de::Error) -> Self { +#[cfg(not(feature = "bson-3"))] +impl From for ErrorKind { + fn from(err: crate::bson::de::Error) -> Self { Self::BsonDeserialization(err) } } -impl From for ErrorKind { - fn from(err: bson::ser::Error) -> Self { +#[cfg(not(feature = "bson-3"))] +impl From for ErrorKind { + fn from(err: crate::bson::ser::Error) -> Self { Self::BsonSerialization(err) } } -impl From for ErrorKind { - fn from(err: bson::raw::Error) -> Self { +#[cfg(not(feature = "bson-3"))] +impl From for ErrorKind { + fn from(err: crate::bson_compat::RawError) -> Self { Self::InvalidResponse { message: err.to_string(), } } } +#[cfg(feature = "bson-3")] +impl From for ErrorKind { + fn from(err: crate::bson::error::Error) -> Self { + Self::Bson(err) + } +} + impl From for ErrorKind { fn from(err: std::io::Error) -> Self { Self::Io(Arc::new(err)) @@ -513,13 +600,19 @@ impl From for ErrorKind { } } -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] impl From for ErrorKind { fn from(err: mongocrypt::error::Error) -> Self { Self::Encryption(err) } } +impl From for ErrorKind { + fn from(_err: std::convert::Infallible) -> Self { + unreachable!() + } +} + /// The types of errors that can occur. #[allow(missing_docs)] #[derive(Clone, Debug, Error)] @@ -536,17 +629,26 @@ pub enum ErrorKind { #[non_exhaustive] Authentication { message: String }, - /// Wrapper around `bson::de::Error`. + /// Wrapper around `bson::de::Error`. Unused if the `bson-3` feature is enabled. #[error("{0}")] - BsonDeserialization(crate::bson::de::Error), + BsonDeserialization(crate::bson_compat::DeError), - /// Wrapper around `bson::ser::Error`. + /// Wrapper around `bson::ser::Error`. Unused if the `bson-3` feature is enabled. #[error("{0}")] - BsonSerialization(crate::bson::ser::Error), + BsonSerialization(crate::bson_compat::SerError), - /// An error occurred when trying to execute a write operation consisting of multiple writes. - #[error("An error occurred when trying to execute a write operation: {0:?}")] - BulkWrite(BulkWriteFailure), + /// Wrapper around `bson::error::Error`. + #[cfg(feature = "bson-3")] + #[error("{0}")] + Bson(crate::bson::error::Error), + + /// An error occurred when trying to execute an [`insert_many`](crate::Collection::insert_many) + /// operation. + #[error("An error occurred when trying to execute an insert_many operation: {0:?}")] + InsertMany(InsertManyError), + + #[error("An error occurred when executing Client::bulk_write: {0:?}")] + BulkWrite(BulkWriteError), /// The server returned an error to an attempted operation. #[error("Command failed: {0}")] @@ -561,7 +663,6 @@ pub enum ErrorKind { /// A GridFS error occurred. #[error("{0:?}")] - #[non_exhaustive] GridFs(GridFsErrorKind), #[error("Internal error: {message}")] @@ -617,13 +718,17 @@ pub enum ErrorKind { MissingResumeToken, /// An error occurred during encryption or decryption. - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] #[error("An error occurred during client-side encryption: {0}")] Encryption(mongocrypt::error::Error), /// A custom value produced by user code. #[error("Custom user error")] Custom(Arc), + + /// A method was called on a client that was shut down. + #[error("Client has been shut down")] + Shutdown, } impl ErrorKind { @@ -632,7 +737,7 @@ impl ErrorKind { // TODO CLOUDP-105256 Remove this when Atlas Proxy error label behavior is fixed. fn get_write_concern_error(&self) -> Option<&WriteConcernError> { match self { - ErrorKind::BulkWrite(BulkWriteFailure { + ErrorKind::InsertMany(InsertManyError { write_concern_error, .. }) => write_concern_error.as_ref(), @@ -694,7 +799,7 @@ pub struct WriteConcernError { pub code_name: String, /// A description of the error that occurred. - #[serde(rename = "errmsg", default = "String::new")] + #[serde(alias = "errmsg", default = "String::new")] pub message: String, /// A document identifying the write concern setting related to the error. @@ -751,11 +856,11 @@ impl WriteError { } } -/// An error that occurred during a write operation consisting of multiple writes that wasn't due to -/// being unable to satisfy a write concern. +/// An individual write error that occurred during an +/// [`insert_many`](crate::Collection::insert_many) operation. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[non_exhaustive] -pub struct BulkWriteError { +pub struct IndexedWriteError { /// Index into the list of operations that this error corresponds to. #[serde(default)] pub index: usize, @@ -780,8 +885,8 @@ pub struct BulkWriteError { pub details: Option, } -impl BulkWriteError { - // If any new fields are added to BulkWriteError, this implementation must be updated to redact +impl IndexedWriteError { + // If any new fields are added to InsertError, this implementation must be updated to redact // them per the CLAM spec. fn redact(&mut self) { self.message = "REDACTED".to_string(); @@ -789,13 +894,14 @@ impl BulkWriteError { } } -/// The set of errors that occurred during a write operation. +/// The set of errors that occurred during a call to +/// [`insert_many`](crate::Collection::insert_many). #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] -pub struct BulkWriteFailure { +pub struct InsertManyError { /// The error(s) that occurred on account of a non write concern failure. - pub write_errors: Option>, + pub write_errors: Option>, /// The error that occurred on account of write concern failure. pub write_concern_error: Option, @@ -804,9 +910,9 @@ pub struct BulkWriteFailure { pub(crate) inserted_ids: HashMap, } -impl BulkWriteFailure { +impl InsertManyError { pub(crate) fn new() -> Self { - BulkWriteFailure { + InsertManyError { write_errors: None, write_concern_error: None, inserted_ids: Default::default(), @@ -827,13 +933,13 @@ pub enum WriteFailure { } impl WriteFailure { - fn from_bulk_failure(bulk: BulkWriteFailure) -> Result { - if let Some(bulk_write_error) = bulk.write_errors.and_then(|es| es.into_iter().next()) { + fn from_insert_many_error(bulk: InsertManyError) -> Result { + if let Some(insert_error) = bulk.write_errors.and_then(|es| es.into_iter().next()) { let write_error = WriteError { - code: bulk_write_error.code, - code_name: bulk_write_error.code_name, - message: bulk_write_error.message, - details: bulk_write_error.details, + code: insert_error.code, + code_name: insert_error.code_name, + message: insert_error.message, + details: insert_error.details, }; Ok(WriteFailure::WriteError(write_error)) } else if let Some(wc_error) = bulk.write_concern_error { @@ -846,6 +952,7 @@ impl WriteFailure { } } + #[cfg(test)] pub(crate) fn code(&self) -> i32 { match self { Self::WriteConcernError(e) => e.code, @@ -918,14 +1025,15 @@ pub enum GridFsFileIdentifier { Id(Bson), } -/// Translates ErrorKind::BulkWriteError cases to ErrorKind::WriteErrors, leaving all other errors -/// untouched. -pub(crate) fn convert_bulk_errors(error: Error) -> Error { +/// Translates ErrorKind::InsertMany to ErrorKind::Write, leaving all other errors untouched. +pub(crate) fn convert_insert_many_error(error: Error) -> Error { match *error.kind { - ErrorKind::BulkWrite(bulk_failure) => match WriteFailure::from_bulk_failure(bulk_failure) { - Ok(failure) => Error::new(ErrorKind::Write(failure), Some(error.labels)), - Err(e) => e, - }, + ErrorKind::InsertMany(insert_many_error) => { + match WriteFailure::from_insert_many_error(insert_many_error) { + Ok(failure) => Error::new(ErrorKind::Write(failure), Some(error.labels)), + Err(e) => e, + } + } _ => error, } } diff --git a/src/error/bulk_write.rs b/src/error/bulk_write.rs new file mode 100644 index 000000000..aa7386b56 --- /dev/null +++ b/src/error/bulk_write.rs @@ -0,0 +1,70 @@ +use std::collections::HashMap; + +use crate::{ + error::{WriteConcernError, WriteError}, + results::{BulkWriteResult, SummaryBulkWriteResult, VerboseBulkWriteResult}, +}; + +/// An error that occurred while executing [`bulk_write`](crate::Client::bulk_write). +/// +/// If an additional error occurred that was not the result of an individual write failing or a +/// write concern error, it can be retrieved by calling [`source`](core::error::Error::source) on +/// the [`Error`](struct@crate::error::Error) in which this value is stored. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct BulkWriteError { + /// The write concern errors that occurred. + pub write_concern_errors: Vec, + + /// The individual write errors that occurred. + pub write_errors: HashMap, + + /// The results of any successful writes. This value will only be populated if one or more + /// writes succeeded. + pub partial_result: Option, +} + +/// The results of a partially-successful [`bulk_write`](crate::Client::bulk_write) operation. +#[derive(Clone, Debug)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(untagged))] +pub enum PartialBulkWriteResult { + /// Summary bulk write results. This variant will be populated if + /// [`verbose_results`](crate::action::BulkWrite::verbose_results) was not configured in the + /// call to [`bulk_write`](crate::Client::bulk_write). + Summary(SummaryBulkWriteResult), + + /// Verbose bulk write results. This variant will be populated if + /// [`verbose_results`](crate::action::BulkWrite::verbose_results) was configured in the call + /// to [`bulk_write`](crate::Client::bulk_write). + Verbose(VerboseBulkWriteResult), +} + +impl PartialBulkWriteResult { + pub(crate) fn merge(&mut self, other: Self) { + match (self, other) { + (Self::Summary(this), Self::Summary(other)) => this.merge(other), + (Self::Verbose(this), Self::Verbose(other)) => this.merge(other), + // The operation execution path makes this an unreachable state + _ => unreachable!(), + } + } +} + +impl BulkWriteError { + pub(crate) fn merge(&mut self, other: Self) { + self.write_concern_errors.extend(other.write_concern_errors); + self.write_errors.extend(other.write_errors); + if let Some(other_partial_result) = other.partial_result { + self.merge_partial_results(other_partial_result); + } + } + + pub(crate) fn merge_partial_results(&mut self, other_partial_result: PartialBulkWriteResult) { + if let Some(ref mut partial_result) = self.partial_result { + partial_result.merge(other_partial_result); + } else { + self.partial_result = Some(other_partial_result); + } + } +} diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 000000000..7c634e72f --- /dev/null +++ b/src/event.rs @@ -0,0 +1,144 @@ +//! Contains the events and functionality for monitoring internal `Client` behavior. + +pub mod cmap; +pub mod command; +pub mod sdam; + +use std::sync::Arc; + +use futures_core::future::BoxFuture; + +use crate::event::command::CommandEvent; + +use self::{cmap::CmapEvent, sdam::SdamEvent}; + +/// A destination for events. Allows implicit conversion via [`From`] for concrete types for +/// convenience with [`crate::options::ClientOptions`] construction: +/// +/// ```rust +/// # use mongodb::options::ClientOptions; +/// # fn example() { +/// let (tx, mut rx) = tokio::sync::mpsc::channel(100); +/// tokio::spawn(async move { +/// while let Some(ev) = rx.recv().await { +/// println!("{:?}", ev); +/// } +/// }); +/// let options = ClientOptions::builder() +/// .command_event_handler(tx) +/// .build(); +/// # } +/// ``` +/// +/// or explicit construction for `Fn` traits: +/// +/// ```rust +/// # use mongodb::options::ClientOptions; +/// # use mongodb::event::EventHandler; +/// # fn example() { +/// let options = ClientOptions::builder() +/// .command_event_handler(EventHandler::callback(|ev| println!("{:?}", ev))) +/// .build(); +/// # } +/// ``` +#[derive(Clone)] +#[non_exhaustive] +pub enum EventHandler { + /// A callback. + Callback(Arc), + /// An async callback. + AsyncCallback(Arc BoxFuture<'static, ()> + Sync + Send>), + /// A `tokio` channel sender. + TokioMpsc(tokio::sync::mpsc::Sender), +} + +impl std::fmt::Debug for EventHandler { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("EventHandler").finish() + } +} + +impl From> for EventHandler { + fn from(value: tokio::sync::mpsc::Sender) -> Self { + Self::TokioMpsc(value) + } +} + +#[allow(deprecated)] +impl From> + for EventHandler +{ + fn from(value: Arc) -> Self { + Self::callback(move |ev| match ev { + CommandEvent::Started(e) => value.handle_command_started_event(e), + CommandEvent::Succeeded(e) => value.handle_command_succeeded_event(e), + CommandEvent::Failed(e) => value.handle_command_failed_event(e), + }) + } +} + +#[allow(deprecated)] +impl From> for EventHandler { + fn from(value: Arc) -> Self { + use CmapEvent::*; + Self::callback(move |ev| match ev { + PoolCreated(ev) => value.handle_pool_created_event(ev), + PoolReady(ev) => value.handle_pool_ready_event(ev), + PoolCleared(ev) => value.handle_pool_cleared_event(ev), + PoolClosed(ev) => value.handle_pool_closed_event(ev), + ConnectionCreated(ev) => value.handle_connection_created_event(ev), + ConnectionReady(ev) => value.handle_connection_ready_event(ev), + ConnectionClosed(ev) => value.handle_connection_closed_event(ev), + ConnectionCheckoutStarted(ev) => value.handle_connection_checkout_started_event(ev), + ConnectionCheckoutFailed(ev) => value.handle_connection_checkout_failed_event(ev), + ConnectionCheckedOut(ev) => value.handle_connection_checked_out_event(ev), + ConnectionCheckedIn(ev) => value.handle_connection_checked_in_event(ev), + }) + } +} + +#[allow(deprecated)] +impl From> for EventHandler { + fn from(value: Arc) -> Self { + use SdamEvent::*; + Self::callback(move |ev| match ev { + ServerDescriptionChanged(ev) => value.handle_server_description_changed_event(*ev), + ServerOpening(ev) => value.handle_server_opening_event(ev), + ServerClosed(ev) => value.handle_server_closed_event(ev), + TopologyDescriptionChanged(ev) => value.handle_topology_description_changed_event(*ev), + TopologyOpening(ev) => value.handle_topology_opening_event(ev), + TopologyClosed(ev) => value.handle_topology_closed_event(ev), + ServerHeartbeatStarted(ev) => value.handle_server_heartbeat_started_event(ev), + ServerHeartbeatSucceeded(ev) => value.handle_server_heartbeat_succeeded_event(ev), + ServerHeartbeatFailed(ev) => value.handle_server_heartbeat_failed_event(ev), + }) + } +} + +impl EventHandler { + /// Construct a new event handler with a callback. + pub fn callback(f: impl Fn(T) + Send + Sync + 'static) -> Self { + Self::Callback(Arc::new(f)) + } + + /// Construct a new event handler with an async callback. + pub fn async_callback(f: impl Fn(T) -> BoxFuture<'static, ()> + Send + Sync + 'static) -> Self { + Self::AsyncCallback(Arc::new(f)) + } + + pub(crate) fn handle(&self, event: T) { + match self { + // TODO RUST-1731 Use tokio's spawn_blocking + Self::Callback(cb) => (cb)(event), + Self::AsyncCallback(cb) => { + crate::runtime::spawn((cb)(event)); + } + Self::TokioMpsc(sender) => { + let sender = sender.clone(); + crate::runtime::spawn(async move { + let _ = sender.send(event).await; + }); + } + } + } +} diff --git a/src/event/cmap.rs b/src/event/cmap.rs index 98d7ea219..f77bbdcd8 100644 --- a/src/event/cmap.rs +++ b/src/event/cmap.rs @@ -1,13 +1,14 @@ //! Contains the events and functionality for monitoring behavior of the connection pooling of a //! `Client`. -use std::{sync::Arc, time::Duration}; +use std::time::Duration; +use derive_more::From; +#[cfg(feature = "tracing-unstable")] +use derive_where::derive_where; use serde::{Deserialize, Serialize}; -use crate::{bson::oid::ObjectId, bson_util, options::ServerAddress}; -use derivative::Derivative; -use derive_more::From; +use crate::{bson::oid::ObjectId, options::ServerAddress, serde_util}; #[cfg(feature = "tracing-unstable")] use crate::trace::{ @@ -17,6 +18,8 @@ use crate::trace::{ CONNECTION_TRACING_EVENT_TARGET, }; +use super::EventHandler; + /// We implement `Deserialize` for all of the event types so that we can more easily parse the CMAP /// spec tests. However, we have no need to parse the address field from the JSON files (if it's /// even present). To facilitate populating the address field with an empty value when @@ -54,7 +57,7 @@ pub struct ConnectionPoolOptions { /// The default is that connections will not be closed due to being idle. #[serde(rename = "maxIdleTimeMS")] #[serde(default)] - #[serde(deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis")] + #[serde(deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis")] pub max_idle_time: Option, /// The maximum number of connections that the pool can have at a given time. This includes @@ -92,6 +95,10 @@ pub struct PoolClearedEvent { /// If the connection is to a load balancer, the id of the selected backend. pub service_id: Option, + + /// Whether in-use connections were interrupted when the pool cleared. + #[serde(default)] + pub interrupt_in_use_connections: bool, } /// Event emitted when a connection pool is cleared. @@ -135,11 +142,16 @@ pub struct ConnectionReadyEvent { /// to identify other events related to this connection. #[serde(default = "default_connection_id")] pub connection_id: u32, + + /// The time it took to establish the connection. + #[serde(skip_deserializing)] + pub duration: Duration, } /// Event emitted when a connection is closed. -#[derive(Clone, Debug, Deserialize, Derivative, Serialize)] -#[derivative(PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "tracing-unstable", derive_where(PartialEq))] +#[cfg_attr(not(feature = "tracing-unstable"), derive(PartialEq))] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct ConnectionClosedEvent { @@ -154,6 +166,7 @@ pub struct ConnectionClosedEvent { pub connection_id: u32, /// The reason that the connection was closed. + #[cfg_attr(test, serde(default = "unset_connection_closed_reason"))] pub reason: ConnectionClosedReason, /// If the `reason` connection checkout failed was `Error`,the associated @@ -161,7 +174,7 @@ pub struct ConnectionClosedEvent { /// in future work we may add this to public API on the event itself. TODO: DRIVERS-2495 #[cfg(feature = "tracing-unstable")] #[serde(skip)] - #[derivative(PartialEq = "ignore")] + #[derive_where(skip)] pub(crate) error: Option, } @@ -184,6 +197,15 @@ pub enum ConnectionClosedReason { /// The pool that the connection belongs to has been closed. PoolClosed, + + #[cfg(test)] + /// The value was not set in the test file. + Unset, +} + +#[cfg(test)] +fn unset_connection_closed_reason() -> ConnectionClosedReason { + ConnectionClosedReason::Unset } /// Event emitted when a thread begins checking out a connection to use for an operation. @@ -197,8 +219,9 @@ pub struct ConnectionCheckoutStartedEvent { } /// Event emitted when a thread is unable to check out a connection. -#[derive(Clone, Debug, Deserialize, Derivative, Serialize)] -#[derivative(PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "tracing-unstable", derive_where(PartialEq))] +#[cfg_attr(not(feature = "tracing-unstable"), derive(PartialEq))] #[non_exhaustive] pub struct ConnectionCheckoutFailedEvent { /// The address of the server that the connection would have connected to. @@ -207,6 +230,7 @@ pub struct ConnectionCheckoutFailedEvent { pub address: ServerAddress, /// The reason a connection was unable to be checked out. + #[cfg_attr(test, serde(default = "unset_connection_checkout_failed_reason"))] pub reason: ConnectionCheckoutFailedReason, /// If the `reason` connection checkout failed was `ConnectionError`,the associated @@ -214,8 +238,12 @@ pub struct ConnectionCheckoutFailedEvent { /// in future work we may add this to public API on the event itself. TODO: DRIVERS-2495 #[cfg(feature = "tracing-unstable")] #[serde(skip)] - #[derivative(PartialEq = "ignore")] + #[derive_where(skip)] pub(crate) error: Option, + + /// See [ConnectionCheckedOutEvent::duration]. + #[serde(skip_deserializing)] + pub duration: Duration, } /// The reasons a connection may not be able to be checked out. @@ -229,6 +257,15 @@ pub enum ConnectionCheckoutFailedReason { /// An error occurred while trying to establish a connection (e.g. during the handshake or /// authentication). ConnectionError, + + #[cfg(test)] + /// The value was not set in the test file. + Unset, +} + +#[cfg(test)] +fn unset_connection_checkout_failed_reason() -> ConnectionCheckoutFailedReason { + ConnectionCheckoutFailedReason::Unset } /// Event emitted when a connection is successfully checked out. @@ -245,6 +282,10 @@ pub struct ConnectionCheckedOutEvent { /// to identify other events related to this connection. #[serde(default = "default_connection_id")] pub connection_id: u32, + + /// The time it took to check out the connection. + #[serde(skip_deserializing)] + pub duration: Duration, } /// Event emitted when a connection is checked back into a connection pool. @@ -269,10 +310,13 @@ fn default_connection_id() -> u32 { 42 } +/// Usage of this trait is deprecated. Applications should use the [`EventHandler`] API. +/// /// Applications can implement this trait to specify custom logic to run on each CMAP event sent /// by the driver. /// /// ```rust +/// # #![allow(deprecated)] /// # use std::sync::Arc; /// # /// # use mongodb::{ @@ -283,9 +327,9 @@ fn default_connection_id() -> u32 { /// # }, /// # options::ClientOptions, /// # }; -/// # #[cfg(any(feature = "sync", feature = "tokio-sync"))] +/// # #[cfg(feature = "sync")] /// # use mongodb::sync::Client; -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] +/// # #[cfg(not(feature = "sync"))] /// # use mongodb::Client; /// # /// struct FailedCheckoutLogger; @@ -297,7 +341,7 @@ fn default_connection_id() -> u32 { /// } /// /// # fn do_stuff() -> Result<()> { -/// let handler: Arc = Arc::new(FailedCheckoutLogger); +/// let handler = Arc::new(FailedCheckoutLogger); /// let options = ClientOptions::builder() /// .cmap_event_handler(handler) /// .build(); @@ -307,6 +351,7 @@ fn default_connection_id() -> u32 { /// # Ok(()) /// # } /// ``` +#[deprecated = "use the EventHandler API"] pub trait CmapEventHandler: Send + Sync { /// A [`Client`](../../struct.Client.html) will call this method on each registered handler /// whenever a connection pool is created. @@ -357,7 +402,9 @@ pub trait CmapEventHandler: Send + Sync { } #[derive(Clone, Debug, PartialEq, From)] -pub(crate) enum CmapEvent { +#[non_exhaustive] +#[allow(missing_docs)] +pub enum CmapEvent { PoolCreated(PoolCreatedEvent), PoolReady(PoolReadyEvent), PoolCleared(PoolClearedEvent), @@ -373,7 +420,7 @@ pub(crate) enum CmapEvent { #[derive(Clone)] pub(crate) struct CmapEventEmitter { - user_handler: Option>, + user_handler: Option>, #[cfg(feature = "tracing-unstable")] tracing_emitter: ConnectionTracingEventEmitter, @@ -383,7 +430,7 @@ impl CmapEventEmitter { // the topology ID is only used when the tracing feature is on. #[allow(unused_variables)] pub(crate) fn new( - user_handler: Option>, + user_handler: Option>, topology_id: ObjectId, ) -> CmapEventEmitter { Self { @@ -396,7 +443,7 @@ impl CmapEventEmitter { #[cfg(not(feature = "tracing-unstable"))] pub(crate) fn emit_event(&self, generate_event: impl FnOnce() -> CmapEvent) { if let Some(ref handler) = self.user_handler { - handle_cmap_event(handler.as_ref(), generate_event()); + handler.handle(generate_event()); } } @@ -417,39 +464,17 @@ impl CmapEventEmitter { (None, None) => {} (None, Some(tracing_emitter)) => { let event = generate_event(); - handle_cmap_event(tracing_emitter, event); + tracing_emitter.handle(event); } (Some(user_handler), None) => { let event = generate_event(); - handle_cmap_event(user_handler.as_ref(), event); + user_handler.handle(event); } (Some(user_handler), Some(tracing_emitter)) => { let event = generate_event(); - handle_cmap_event(user_handler.as_ref(), event.clone()); - handle_cmap_event(tracing_emitter, event); + user_handler.handle(event.clone()); + tracing_emitter.handle(event); } }; } } - -fn handle_cmap_event(handler: &dyn CmapEventHandler, event: CmapEvent) { - match event { - CmapEvent::PoolCreated(event) => handler.handle_pool_created_event(event), - CmapEvent::PoolReady(event) => handler.handle_pool_ready_event(event), - CmapEvent::PoolCleared(event) => handler.handle_pool_cleared_event(event), - CmapEvent::PoolClosed(event) => handler.handle_pool_closed_event(event), - CmapEvent::ConnectionCreated(event) => handler.handle_connection_created_event(event), - CmapEvent::ConnectionReady(event) => handler.handle_connection_ready_event(event), - CmapEvent::ConnectionClosed(event) => handler.handle_connection_closed_event(event), - CmapEvent::ConnectionCheckoutStarted(event) => { - handler.handle_connection_checkout_started_event(event) - } - CmapEvent::ConnectionCheckoutFailed(event) => { - handler.handle_connection_checkout_failed_event(event) - } - CmapEvent::ConnectionCheckedOut(event) => { - handler.handle_connection_checked_out_event(event) - } - CmapEvent::ConnectionCheckedIn(event) => handler.handle_connection_checked_in_event(event), - } -} diff --git a/src/event/command.rs b/src/event/command.rs index 295cecf1e..39eb4f7ef 100644 --- a/src/event/command.rs +++ b/src/event/command.rs @@ -7,8 +7,8 @@ use serde::Serialize; use crate::{ bson::{oid::ObjectId, Document}, - bson_util::serialize_error_as_string, error::Error, + serde_util, }; pub use crate::cmap::ConnectionInfo; @@ -78,7 +78,7 @@ pub struct CommandFailedEvent { pub command_name: String, /// The error that the driver returned due to the event failing. - #[serde(serialize_with = "serialize_error_as_string")] + #[serde(serialize_with = "serde_util::serialize_error_as_string")] pub failure: Error, /// The driver-generated identifier for the request. Applications can use this to identify the @@ -93,6 +93,9 @@ pub struct CommandFailedEvent { pub service_id: Option, } +/// Usage of this trait is deprecated. Applications should use the +/// [`EventHandler`](crate::event::EventHandler) API. +/// /// Applications can implement this trait to specify custom logic to run on each command event sent /// by the driver. /// @@ -107,9 +110,9 @@ pub struct CommandFailedEvent { /// # }, /// # options::ClientOptions, /// # }; -/// # #[cfg(any(feature = "sync", feature = "tokio-sync"))] +/// # #[cfg(feature = "sync")] /// # use mongodb::sync::Client; -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] +/// # #[cfg(not(feature = "sync"))] /// # use mongodb::Client; /// # /// struct FailedCommandLogger; @@ -121,7 +124,7 @@ pub struct CommandFailedEvent { /// } /// /// # fn do_stuff() -> Result<()> { -/// let handler: Arc = Arc::new(FailedCommandLogger); +/// let handler = Arc::new(FailedCommandLogger); /// let options = ClientOptions::builder() /// .command_event_handler(handler) /// .build(); @@ -131,6 +134,7 @@ pub struct CommandFailedEvent { /// # Ok(()) /// # } /// ``` +#[deprecated = "use the EventHandler API"] pub trait CommandEventHandler: Send + Sync { /// A [`Client`](../../struct.Client.html) will call this method on each registered handler /// whenever a database command is initiated. @@ -146,18 +150,11 @@ pub trait CommandEventHandler: Send + Sync { } #[derive(Clone, Debug, Serialize)] +#[allow(missing_docs)] #[serde(untagged)] -pub(crate) enum CommandEvent { +#[non_exhaustive] +pub enum CommandEvent { Started(CommandStartedEvent), Succeeded(CommandSucceededEvent), Failed(CommandFailedEvent), } - -/// Passes the specified event to the corresponding method on the provided handler. -pub(crate) fn handle_command_event(handler: &dyn CommandEventHandler, event: CommandEvent) { - match event { - CommandEvent::Started(event) => handler.handle_command_started_event(event), - CommandEvent::Succeeded(event) => handler.handle_command_succeeded_event(event), - CommandEvent::Failed(event) => handler.handle_command_failed_event(event), - } -} diff --git a/src/event/mod.rs b/src/event/mod.rs deleted file mode 100644 index 2f4061b19..000000000 --- a/src/event/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Contains the events and functionality for monitoring internal `Client` behavior. - -pub mod cmap; -pub mod command; -pub mod sdam; diff --git a/src/event/sdam.rs b/src/event/sdam.rs new file mode 100644 index 000000000..7d7857b44 --- /dev/null +++ b/src/event/sdam.rs @@ -0,0 +1,282 @@ +//! Contains the events and functionality for monitoring Server Discovery and Monitoring. + +mod topology_description; + +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +use crate::{ + bson::{oid::ObjectId, Document}, + error::Error, + options::ServerAddress, + serde_util, +}; + +pub use crate::sdam::public::TopologyType; +pub use topology_description::TopologyDescription; + +/// A description of the most up-to-date information known about a server. Further details can be +/// found in the [Server Discovery and Monitoring specification](https://specifications.readthedocs.io/en/latest/server-discovery-and-monitoring/server-discovery-and-monitoring/). +pub type ServerDescription = crate::sdam::public::ServerInfo<'static>; + +/// Published when a server description changes. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerDescriptionChangedEvent { + /// The address of the server. + pub address: ServerAddress, + + /// The unique ID of the topology. + pub topology_id: ObjectId, + + /// The server's previous description. + pub previous_description: ServerDescription, + + /// The server's new description. + pub new_description: ServerDescription, +} + +impl ServerDescriptionChangedEvent { + #[cfg(test)] + pub(crate) fn is_marked_unknown_event(&self) -> bool { + self.previous_description + .description + .server_type + .is_available() + && self.new_description.description.server_type == crate::ServerType::Unknown + } +} + +/// Published when a server is initialized. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerOpeningEvent { + /// The address of the server. + pub address: ServerAddress, + + /// The unique ID of the topology. + #[serde(skip, default = "ObjectId::new")] + pub topology_id: ObjectId, +} + +/// Published when a server is closed. +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerClosedEvent { + /// The address of the server. + pub address: ServerAddress, + + /// The unique ID of the topology. + #[serde(skip, default = "ObjectId::new")] + pub topology_id: ObjectId, +} + +/// Published when a topology description changes. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct TopologyDescriptionChangedEvent { + /// The ID of the topology. + pub topology_id: ObjectId, + + /// The topology's previous description. + pub previous_description: TopologyDescription, + + /// The topology's new description. + pub new_description: TopologyDescription, +} + +/// Published when a topology is initialized. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct TopologyOpeningEvent { + /// The unique ID of the topology. + #[serde(skip, default = "ObjectId::new")] + pub topology_id: ObjectId, +} + +/// Published when a topology is closed. Note that this event will not be published until the client +/// associated with the topology is dropped. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct TopologyClosedEvent { + /// The unique ID of the topology. + #[serde(skip, default = "ObjectId::new")] + pub topology_id: ObjectId, +} + +/// Published when a server monitor's `hello` or legacy hello command is started. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerHeartbeatStartedEvent { + /// The address of the server. + pub server_address: ServerAddress, + + /// Determines if this heartbeat event is from an awaitable `hello`. + pub awaited: bool, + + /// The driver-generated ID for the connection used for the heartbeat. + pub driver_connection_id: u32, + + /// The server-generated ID for the connection used for the heartbeat. This value is only + /// present on server versions 4.2+. If this event corresponds to the first heartbeat on a + /// new monitoring connection, this value will not be present. + pub server_connection_id: Option, +} + +/// Published when a server monitor's `hello` or legacy hello command succeeds. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerHeartbeatSucceededEvent { + /// The execution time of the event. + pub duration: Duration, + + /// The reply to the `hello` or legacy hello command. + pub reply: Document, + + /// The address of the server. + pub server_address: ServerAddress, + + /// Determines if this heartbeat event is from an awaitable `hello`. + pub awaited: bool, + + /// The driver-generated ID for the connection used for the heartbeat. + pub driver_connection_id: u32, + + /// The server-generated ID for the connection used for the heartbeat. This value is only + /// present for server versions 4.2+. + pub server_connection_id: Option, +} + +/// Published when a server monitor's `hello` or legacy hello command fails. +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct ServerHeartbeatFailedEvent { + /// The execution time of the event. + pub duration: Duration, + + /// The failure that occurred. + #[serde(serialize_with = "serde_util::serialize_error_as_string")] + pub failure: Error, + + /// The address of the server. + pub server_address: ServerAddress, + + /// Determines if this heartbeat event is from an awaitable `hello`. + pub awaited: bool, + + /// The driver-generated ID for the connection used for the heartbeat. + pub driver_connection_id: u32, + + /// The server-generated ID for the connection used for the heartbeat. This value is only + /// present on server versions 4.2+. If this event corresponds to the first heartbeat on a + /// new monitoring connection, this value will not be present. + pub server_connection_id: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[allow(missing_docs)] +#[non_exhaustive] +#[serde(untagged)] +pub enum SdamEvent { + ServerDescriptionChanged(Box), + ServerOpening(ServerOpeningEvent), + ServerClosed(ServerClosedEvent), + TopologyDescriptionChanged(Box), + TopologyOpening(TopologyOpeningEvent), + TopologyClosed(TopologyClosedEvent), + ServerHeartbeatStarted(ServerHeartbeatStartedEvent), + ServerHeartbeatSucceeded(ServerHeartbeatSucceededEvent), + ServerHeartbeatFailed(ServerHeartbeatFailedEvent), +} + +/// Usage of this trait is deprecated. Applications should use the +/// [`EventHandler`](crate::event::EventHandler) API. +/// +/// Applications can implement this trait to specify custom logic to run on each SDAM event sent +/// by the driver. +/// +/// ```rust +/// # #![allow(deprecated)] +/// # use std::sync::Arc; +/// # +/// # use mongodb::{ +/// # error::Result, +/// # event::sdam::{ +/// # SdamEventHandler, +/// # ServerHeartbeatFailedEvent, +/// # }, +/// # options::ClientOptions, +/// # }; +/// # #[cfg(feature = "sync")] +/// # use mongodb::sync::Client; +/// # #[cfg(not(feature = "sync"))] +/// # use mongodb::Client; +/// # +/// struct FailedHeartbeatLogger; +/// +/// impl SdamEventHandler for FailedHeartbeatLogger { +/// fn handle_server_heartbeat_failed_event(&self, event: ServerHeartbeatFailedEvent) { +/// eprintln!("Failed server heartbeat: {:?}", event); +/// } +/// } +/// +/// # fn do_stuff() -> Result<()> { +/// let handler = Arc::new(FailedHeartbeatLogger); +/// let options = ClientOptions::builder() +/// .sdam_event_handler(handler) +/// .build(); +/// let client = Client::with_options(options)?; +/// +/// // Do things with the client, and failed server heartbeats will be logged to stderr. +/// # Ok(()) +/// # } +/// ``` +#[deprecated = "use the EventHandler API"] +pub trait SdamEventHandler: Send + Sync { + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server description changes. + fn handle_server_description_changed_event(&self, _event: ServerDescriptionChangedEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server is initialized. + fn handle_server_opening_event(&self, _event: ServerOpeningEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server is closed. + fn handle_server_closed_event(&self, _event: ServerClosedEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// its topology description changes. + fn handle_topology_description_changed_event(&self, _event: TopologyDescriptionChangedEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// its topology is initialized. + fn handle_topology_opening_event(&self, _event: TopologyOpeningEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// its topology closes. Note that this method will not be called until the + /// [`Client`](../../struct.Client.html) is dropped. + fn handle_topology_closed_event(&self, _event: TopologyClosedEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server heartbeat begins. + fn handle_server_heartbeat_started_event(&self, _event: ServerHeartbeatStartedEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server heartbeat succeeds. + fn handle_server_heartbeat_succeeded_event(&self, _event: ServerHeartbeatSucceededEvent) {} + + /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when + /// a server heartbeat fails. + fn handle_server_heartbeat_failed_event(&self, _event: ServerHeartbeatFailedEvent) {} +} diff --git a/src/event/sdam/mod.rs b/src/event/sdam/mod.rs deleted file mode 100644 index 89bcd9103..000000000 --- a/src/event/sdam/mod.rs +++ /dev/null @@ -1,271 +0,0 @@ -//! Contains the events and functionality for monitoring Server Discovery and Monitoring. - -mod topology_description; - -use std::time::Duration; - -use serde::{Deserialize, Serialize}; - -use crate::{ - bson::{oid::ObjectId, Document}, - bson_util::serialize_error_as_string, - error::Error, - options::ServerAddress, -}; - -pub use crate::sdam::public::TopologyType; -pub use topology_description::TopologyDescription; - -/// A description of the most up-to-date information known about a server. Further details can be -/// found in the [Server Discovery and Monitoring specification](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst). -pub type ServerDescription = crate::sdam::public::ServerInfo<'static>; - -/// Published when a server description changes. -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerDescriptionChangedEvent { - /// The address of the server. - pub address: ServerAddress, - - /// The unique ID of the topology. - pub topology_id: ObjectId, - - /// The server's previous description. - pub previous_description: ServerDescription, - - /// The server's new description. - pub new_description: ServerDescription, -} - -impl ServerDescriptionChangedEvent { - #[cfg(test)] - pub(crate) fn is_marked_unknown_event(&self) -> bool { - self.previous_description - .description - .server_type - .is_available() - && self.new_description.description.server_type == crate::ServerType::Unknown - } -} - -/// Published when a server is initialized. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerOpeningEvent { - /// The address of the server. - pub address: ServerAddress, - - /// The unique ID of the topology. - #[serde(skip, default = "ObjectId::new")] - pub topology_id: ObjectId, -} - -/// Published when a server is closed. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerClosedEvent { - /// The address of the server. - pub address: ServerAddress, - - /// The unique ID of the topology. - #[serde(skip, default = "ObjectId::new")] - pub topology_id: ObjectId, -} - -/// Published when a topology description changes. -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct TopologyDescriptionChangedEvent { - /// The ID of the topology. - pub topology_id: ObjectId, - - /// The topology's previous description. - pub previous_description: TopologyDescription, - - /// The topology's new description. - pub new_description: TopologyDescription, -} - -/// Published when a topology is initialized. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct TopologyOpeningEvent { - /// The unique ID of the topology. - #[serde(skip, default = "ObjectId::new")] - pub topology_id: ObjectId, -} - -/// Published when a topology is closed. Note that this event will not be published until the client -/// associated with the topology is dropped. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct TopologyClosedEvent { - /// The unique ID of the topology. - #[serde(skip, default = "ObjectId::new")] - pub topology_id: ObjectId, -} - -/// Published when a server monitor's `hello` or legacy hello command is started. -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerHeartbeatStartedEvent { - /// The address of the server. - pub server_address: ServerAddress, - - /// Determines if this heartbeat event is from an awaitable `hello`. - pub awaited: bool, -} - -/// Published when a server monitor's `hello` or legacy hello command succeeds. -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerHeartbeatSucceededEvent { - /// The execution time of the event. - pub duration: Duration, - - /// The reply to the `hello` or legacy hello command. - pub reply: Document, - - /// The address of the server. - pub server_address: ServerAddress, - - /// Determines if this heartbeat event is from an awaitable `hello`. - pub awaited: bool, -} - -/// Published when a server monitor's `hello` or legacy hello command fails. -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct ServerHeartbeatFailedEvent { - /// The execution time of the event. - pub duration: Duration, - - /// The failure that occurred. - #[serde(serialize_with = "serialize_error_as_string")] - pub failure: Error, - - /// The address of the server. - pub server_address: ServerAddress, - - /// Determines if this heartbeat event is from an awaitable `hello`. - pub awaited: bool, -} - -#[derive(Clone, Debug)] -pub(crate) enum SdamEvent { - ServerDescriptionChanged(Box), - ServerOpening(ServerOpeningEvent), - ServerClosed(ServerClosedEvent), - TopologyDescriptionChanged(Box), - TopologyOpening(TopologyOpeningEvent), - TopologyClosed(TopologyClosedEvent), - ServerHeartbeatStarted(ServerHeartbeatStartedEvent), - ServerHeartbeatSucceeded(ServerHeartbeatSucceededEvent), - ServerHeartbeatFailed(ServerHeartbeatFailedEvent), -} - -/// Applications can implement this trait to specify custom logic to run on each SDAM event sent -/// by the driver. -/// -/// ```rust -/// # use std::sync::Arc; -/// # -/// # use mongodb::{ -/// # error::Result, -/// # event::sdam::{ -/// # SdamEventHandler, -/// # ServerHeartbeatFailedEvent, -/// # }, -/// # options::ClientOptions, -/// # }; -/// # #[cfg(any(feature = "sync", feature = "tokio-sync"))] -/// # use mongodb::sync::Client; -/// # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -/// # use mongodb::Client; -/// # -/// struct FailedHeartbeatLogger; -/// -/// impl SdamEventHandler for FailedHeartbeatLogger { -/// fn handle_server_heartbeat_failed_event(&self, event: ServerHeartbeatFailedEvent) { -/// eprintln!("Failed server heartbeat: {:?}", event); -/// } -/// } -/// -/// # fn do_stuff() -> Result<()> { -/// let handler: Arc = Arc::new(FailedHeartbeatLogger); -/// let options = ClientOptions::builder() -/// .sdam_event_handler(handler) -/// .build(); -/// let client = Client::with_options(options)?; -/// -/// // Do things with the client, and failed server heartbeats will be logged to stderr. -/// # Ok(()) -/// # } -/// ``` -pub trait SdamEventHandler: Send + Sync { - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server description changes. - fn handle_server_description_changed_event(&self, _event: ServerDescriptionChangedEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server is initialized. - fn handle_server_opening_event(&self, _event: ServerOpeningEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server is closed. - fn handle_server_closed_event(&self, _event: ServerClosedEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// its topology description changes. - fn handle_topology_description_changed_event(&self, _event: TopologyDescriptionChangedEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// its topology is initialized. - fn handle_topology_opening_event(&self, _event: TopologyOpeningEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// its topology closes. Note that this method will not be called until the - /// [`Client`](../../struct.Client.html) is dropped. - fn handle_topology_closed_event(&self, _event: TopologyClosedEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server heartbeat begins. - fn handle_server_heartbeat_started_event(&self, _event: ServerHeartbeatStartedEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server heartbeat succeeds. - fn handle_server_heartbeat_succeeded_event(&self, _event: ServerHeartbeatSucceededEvent) {} - - /// A [`Client`](../../struct.Client.html) will call this method on each registered handler when - /// a server heartbeat fails. - fn handle_server_heartbeat_failed_event(&self, _event: ServerHeartbeatFailedEvent) {} -} - -pub(crate) fn handle_sdam_event(handler: &dyn SdamEventHandler, event: SdamEvent) { - match event { - SdamEvent::ServerClosed(event) => handler.handle_server_closed_event(event), - SdamEvent::ServerDescriptionChanged(e) => { - handler.handle_server_description_changed_event(*e) - } - SdamEvent::ServerOpening(e) => handler.handle_server_opening_event(e), - SdamEvent::TopologyDescriptionChanged(e) => { - handler.handle_topology_description_changed_event(*e) - } - SdamEvent::TopologyOpening(e) => handler.handle_topology_opening_event(e), - SdamEvent::TopologyClosed(e) => handler.handle_topology_closed_event(e), - SdamEvent::ServerHeartbeatStarted(e) => handler.handle_server_heartbeat_started_event(e), - SdamEvent::ServerHeartbeatSucceeded(e) => { - handler.handle_server_heartbeat_succeeded_event(e) - } - SdamEvent::ServerHeartbeatFailed(e) => handler.handle_server_heartbeat_failed_event(e), - } -} diff --git a/src/event/sdam/topology_description.rs b/src/event/sdam/topology_description.rs index af44f462d..cc21cf068 100644 --- a/src/event/sdam/topology_description.rs +++ b/src/event/sdam/topology_description.rs @@ -10,7 +10,7 @@ use crate::{ }; /// A description of the most up-to-date information known about a topology. Further details can -/// be found in the [Server Discovery and Monitoring specification](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst). +/// be found in the [Server Discovery and Monitoring specification](https://specifications.readthedocs.io/en/latest/server-discovery-and-monitoring/server-discovery-and-monitoring/). #[derive(Clone, derive_more::Display)] #[display(fmt = "{}", description)] pub struct TopologyDescription { diff --git a/src/gridfs.rs b/src/gridfs.rs index b3124fdd6..a66b18251 100644 --- a/src/gridfs.rs +++ b/src/gridfs.rs @@ -11,9 +11,10 @@ use serde_with::skip_serializing_none; use crate::{ bson::{doc, oid::ObjectId, Bson, DateTime, Document, RawBinaryRef}, - cursor::Cursor, - error::{Error, ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result}, - options::{CollectionOptions, FindOptions, ReadConcern, SelectionCriteria, WriteConcern}, + checked::Checked, + error::Error, + options::{CollectionOptions, ReadConcern, SelectionCriteria, WriteConcern}, + serde_util, Collection, Database, }; @@ -31,6 +32,7 @@ pub(crate) struct Chunk<'a> { #[serde(rename = "_id")] id: ObjectId, files_id: Bson, + #[serde(serialize_with = "serde_util::serialize_u32_as_i32")] n: u32, #[serde(borrow)] data: RawBinaryRef<'a>, @@ -51,7 +53,10 @@ pub struct FilesCollectionDocument { pub length: u64, /// The size of the file's chunks in bytes. - #[serde(rename = "chunkSize")] + #[serde( + rename = "chunkSize", + serialize_with = "serde_util::serialize_u32_as_i32" + )] pub chunk_size_bytes: u32, /// The time at which the file was uploaded. @@ -72,19 +77,14 @@ impl FilesCollectionDocument { fn n_from_vals(length: u64, chunk_size_bytes: u32) -> u32 { let chunk_size_bytes = chunk_size_bytes as u64; - let n = length / chunk_size_bytes + u64::from(length % chunk_size_bytes != 0); - n as u32 - } - - /// Returns the expected length of a chunk given its index. - fn expected_chunk_length(&self, n: u32) -> u32 { - Self::expected_chunk_length_from_vals(self.length, self.chunk_size_bytes, n) + let n = Checked::new(length) / chunk_size_bytes + u64::from(length % chunk_size_bytes != 0); + n.try_into().unwrap() } fn expected_chunk_length_from_vals(length: u64, chunk_size_bytes: u32, n: u32) -> u32 { let remainder = length % (chunk_size_bytes as u64); if n == Self::n_from_vals(length, chunk_size_bytes) - 1 && remainder != 0 { - remainder as u32 + Checked::new(remainder).try_into().unwrap() } else { chunk_size_bytes } @@ -156,7 +156,6 @@ impl GridFsBucket { } } - #[cfg(test)] pub(crate) fn client(&self) -> &crate::Client { self.inner.files.client() } @@ -177,7 +176,7 @@ impl GridFsBucket { } /// Gets the chunk size in bytes for the bucket. - fn chunk_size_bytes(&self) -> u32 { + pub(crate) fn chunk_size_bytes(&self) -> u32 { self.inner .options .chunk_size_bytes @@ -193,63 +192,6 @@ impl GridFsBucket { pub(crate) fn chunks(&self) -> &Collection> { &self.inner.chunks } - - /// Deletes the [`FilesCollectionDocument`] with the given `id` and its associated chunks from - /// this bucket. This method returns an error if the `id` does not match any files in the - /// bucket. - pub async fn delete(&self, id: Bson) -> Result<()> { - let delete_result = self - .files() - .delete_one(doc! { "_id": id.clone() }, None) - .await?; - // Delete chunks regardless of whether a file was found. This will remove any possibly - // orphaned chunks. - self.chunks() - .delete_many(doc! { "files_id": id.clone() }, None) - .await?; - - if delete_result.deleted_count == 0 { - return Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { - identifier: GridFsFileIdentifier::Id(id), - }) - .into()); - } - - Ok(()) - } - - /// Finds and returns the [`FilesCollectionDocument`]s within this bucket that match the given - /// filter. - pub async fn find( - &self, - filter: Document, - options: impl Into>, - ) -> Result> { - let find_options = options.into().map(FindOptions::from); - self.files().find(filter, find_options).await - } - - /// Renames the file with the given 'id' to the provided `new_filename`. This method returns an - /// error if the `id` does not match any files in the bucket. - pub async fn rename(&self, id: Bson, new_filename: impl AsRef) -> Result<()> { - self.files() - .update_one( - doc! { "_id": id }, - doc! { "$set": { "filename": new_filename.as_ref() } }, - None, - ) - .await?; - - Ok(()) - } - - /// Removes all of the files and their associated chunks from this bucket. - pub async fn drop(&self) -> Result<()> { - self.files().drop(None).await?; - self.chunks().drop(None).await?; - - Ok(()) - } } impl Error { diff --git a/src/gridfs/download.rs b/src/gridfs/download.rs index c996394c0..7569538b2 100644 --- a/src/gridfs/download.rs +++ b/src/gridfs/download.rs @@ -1,5 +1,4 @@ use std::{ - marker::Unpin, ops::Range, pin::Pin, task::{Context, Poll}, @@ -7,200 +6,17 @@ use std::{ use futures_util::{ future::{BoxFuture, FutureExt}, - io::{AsyncRead, AsyncWrite, AsyncWriteExt}, + io::AsyncRead, }; -use super::{options::GridFsDownloadByNameOptions, Chunk, FilesCollectionDocument, GridFsBucket}; +use super::{Chunk, FilesCollectionDocument}; use crate::{ - bson::{doc, Bson}, - error::{ErrorKind, GridFsErrorKind, GridFsFileIdentifier, Result}, - options::{FindOneOptions, FindOptions}, + bson::doc, + error::{ErrorKind, GridFsErrorKind, Result}, Collection, Cursor, }; -// Utility functions for finding files within the bucket. -impl GridFsBucket { - async fn find_file_by_id(&self, id: &Bson) -> Result { - match self.files().find_one(doc! { "_id": id }, None).await? { - Some(file) => Ok(file), - None => Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { - identifier: GridFsFileIdentifier::Id(id.clone()), - }) - .into()), - } - } - - async fn find_file_by_name( - &self, - filename: &str, - options: Option, - ) -> Result { - let revision = options.and_then(|opts| opts.revision).unwrap_or(-1); - let (sort, skip) = if revision >= 0 { - (1, revision) - } else { - (-1, -revision - 1) - }; - let options = FindOneOptions::builder() - .sort(doc! { "uploadDate": sort }) - .skip(skip as u64) - .build(); - - match self - .files() - .find_one(doc! { "filename": filename }, options) - .await? - { - Some(fcd) => Ok(fcd), - None => { - if self - .files() - .find_one(doc! { "filename": filename }, None) - .await? - .is_some() - { - Err(ErrorKind::GridFs(GridFsErrorKind::RevisionNotFound { revision }).into()) - } else { - Err(ErrorKind::GridFs(GridFsErrorKind::FileNotFound { - identifier: GridFsFileIdentifier::Filename(filename.into()), - }) - .into()) - } - } - } - } -} - -// User functions for downloading to writers. -impl GridFsBucket { - /// Downloads the contents of the stored file specified by `id` and writes the contents to the - /// `destination`, which may be any type that implements the [`futures_io::AsyncWrite`] trait. - /// - /// To download to a type that implements [`tokio::io::AsyncWrite`], use the - /// [`tokio_util::compat`] module to convert between types. - /// - /// ```rust - /// # use mongodb::{bson::Bson, error::Result, gridfs::GridFsBucket}; - /// # async fn compat_example( - /// # bucket: GridFsBucket, - /// # tokio_writer: impl tokio::io::AsyncWrite + Unpin, - /// # id: Bson, - /// # ) -> Result<()> { - /// use tokio_util::compat::TokioAsyncWriteCompatExt; - /// - /// let futures_writer = tokio_writer.compat_write(); - /// bucket.download_to_futures_0_3_writer(id, futures_writer).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// Note that once an `AsyncWrite` trait is stabilized in the standard library, this method will - /// be deprecated in favor of one that accepts a `std::io::AsyncWrite` source. - pub async fn download_to_futures_0_3_writer(&self, id: Bson, destination: T) -> Result<()> - where - T: AsyncWrite + Unpin, - { - let file = self.find_file_by_id(&id).await?; - self.download_to_writer_common(file, destination).await - } - - /// Downloads the contents of the stored file specified by `filename` and writes the contents to - /// the `destination`, which may be any type that implements the [`futures_io::AsyncWrite`] - /// trait. - /// - /// If there are multiple files in the bucket with the given filename, the `revision` in the - /// options provided is used to determine which one to download. See the documentation for - /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is - /// provided, the file with `filename` most recently uploaded will be downloaded. - /// - /// To download to a type that implements [`tokio::io::AsyncWrite`], use the - /// [`tokio_util::compat`] module to convert between types. - /// - /// ```rust - /// # use mongodb::{bson::Bson, error::Result, gridfs::GridFsBucket}; - /// # async fn compat_example( - /// # bucket: GridFsBucket, - /// # tokio_writer: impl tokio::io::AsyncWrite + Unpin, - /// # id: Bson, - /// # ) -> Result<()> { - /// use tokio_util::compat::TokioAsyncWriteCompatExt; - /// - /// let futures_writer = tokio_writer.compat_write(); - /// bucket.download_to_futures_0_3_writer_by_name("example_file", futures_writer, None).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// Note that once an `AsyncWrite` trait is stabilized in the standard library, this method will - /// be deprecated in favor of one that accepts a `std::io::AsyncWrite` source. - pub async fn download_to_futures_0_3_writer_by_name( - &self, - filename: impl AsRef, - destination: T, - options: impl Into>, - ) -> Result<()> - where - T: AsyncWrite + Unpin, - { - let file = self - .find_file_by_name(filename.as_ref(), options.into()) - .await?; - self.download_to_writer_common(file, destination).await - } - - async fn download_to_writer_common( - &self, - file: FilesCollectionDocument, - mut destination: T, - ) -> Result<()> - where - T: AsyncWrite + Unpin, - { - if file.length == 0 { - return Ok(()); - } - - let options = FindOptions::builder().sort(doc! { "n": 1 }).build(); - let mut cursor = self - .chunks() - .find(doc! { "files_id": &file.id }, options) - .await?; - - let mut n = 0; - while cursor.advance().await? { - let chunk = cursor.deserialize_current()?; - if chunk.n != n { - return Err(ErrorKind::GridFs(GridFsErrorKind::MissingChunk { n }).into()); - } - - let chunk_length = chunk.data.bytes.len(); - let expected_length = file.expected_chunk_length(n); - if chunk_length != expected_length as usize { - return Err(ErrorKind::GridFs(GridFsErrorKind::WrongSizeChunk { - actual_size: chunk_length, - expected_size: expected_length, - n, - }) - .into()); - } - - destination.write_all(chunk.data.bytes).await?; - n += 1; - } - - if n != file.n() { - return Err(ErrorKind::GridFs(GridFsErrorKind::WrongNumberOfChunks { - actual_number: n, - expected_number: file.n(), - }) - .into()); - } - - Ok(()) - } -} - /// A stream from which a file stored in a GridFS bucket can be downloaded. /// /// # Downloading from the Stream @@ -220,6 +36,9 @@ impl GridFsBucket { /// # } /// ``` /// +/// If the destination is a local file (or other `AsyncWrite` byte sink), the contents of the stream +/// can be efficiently written to it with [`futures_util::io::copy`]. +/// /// # Using [`tokio::io::AsyncRead`] /// Users who prefer to use tokio's `AsyncRead` trait can use the [`tokio_util::compat`] module. /// @@ -265,15 +84,17 @@ impl State { } impl GridFsDownloadStream { - async fn new( + pub(crate) async fn new( file: FilesCollectionDocument, chunks: &Collection>, ) -> Result { let initial_state = if file.length == 0 { State::Done } else { - let options = FindOptions::builder().sort(doc! { "n": 1 }).build(); - let cursor = chunks.find(doc! { "files_id": &file.id }, options).await?; + let cursor = chunks + .find(doc! { "files_id": &file.id }) + .sort(doc! { "n": 1 }) + .await?; State::Idle(Some(Idle { buffer: Vec::new(), cursor: Box::new(cursor), @@ -394,31 +215,3 @@ async fn get_bytes( Ok((buffer, cursor)) } - -// User functions for creating download streams. -impl GridFsBucket { - /// Opens and returns a [`GridFsDownloadStream`] from which the application can read - /// the contents of the stored file specified by `id`. - pub async fn open_download_stream(&self, id: Bson) -> Result { - let file = self.find_file_by_id(&id).await?; - GridFsDownloadStream::new(file, self.chunks()).await - } - - /// Opens and returns a [`GridFsDownloadStream`] from which the application can read - /// the contents of the stored file specified by `filename`. - /// - /// If there are multiple files in the bucket with the given filename, the `revision` in the - /// options provided is used to determine which one to download. See the documentation for - /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is - /// provided, the file with `filename` most recently uploaded will be downloaded. - pub async fn open_download_stream_by_name( - &self, - filename: impl AsRef, - options: impl Into>, - ) -> Result { - let file = self - .find_file_by_name(filename.as_ref(), options.into()) - .await?; - GridFsDownloadStream::new(file, self.chunks()).await - } -} diff --git a/src/gridfs/options.rs b/src/gridfs/options.rs index 594eb89f5..81e207b98 100644 --- a/src/gridfs/options.rs +++ b/src/gridfs/options.rs @@ -1,11 +1,12 @@ use std::time::Duration; +use macro_magic::export_tokens; use serde::Deserialize; use typed_builder::TypedBuilder; use crate::{ bson::Document, - options::{FindOptions, ReadConcern, SelectionCriteria, WriteConcern}, + options::{FindOneOptions, FindOptions, ReadConcern, SelectionCriteria, WriteConcern}, }; /// Contains the options for creating a [`GridFsBucket`](crate::gridfs::GridFsBucket). @@ -34,9 +35,10 @@ pub struct GridFsBucketOptions { #[serde(rename_all = "camelCase")] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct GridFsUploadOptions { /// The number of bytes per chunk of this file. Defaults to the `chunk_size_bytes` specified - /// in the [`GridFsBucketOptions`]. + /// in the [`GridFsBucketOptions`](crate::options::GridFsBucketOptions). pub chunk_size_bytes: Option, /// User data for the 'metadata' field of the files collection document. @@ -48,6 +50,7 @@ pub struct GridFsUploadOptions { #[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct GridFsDownloadByNameOptions { /// Which revision (documents with the same filename and different `upload_date`s) /// of the file to retrieve. Defaults to -1 (the most recent revision). @@ -68,6 +71,7 @@ pub struct GridFsDownloadByNameOptions { #[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] #[builder(field_defaults(default, setter(into)))] #[non_exhaustive] +#[export_tokens] pub struct GridFsFindOptions { /// Enables writing to temporary files on the server. When set to true, the /// server can write temporary data to disk while executing the find operation @@ -103,3 +107,32 @@ impl From for FindOptions { } } } + +/// Contains the options for finding a single +/// [`FilesCollectionDocument`](crate::gridfs::FilesCollectionDocument) in a +/// [`GridFsBucket`](crate::gridfs::GridFsBucket). +#[derive(Clone, Debug, Default, Deserialize, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +#[export_tokens] +pub struct GridFsFindOneOptions { + /// The maximum amount of time to allow the query to run. + pub max_time: Option, + + /// The number of documents to skip before returning. + pub skip: Option, + + /// The order by which to sort results. Defaults to not sorting. + pub sort: Option, +} + +impl From for FindOneOptions { + fn from(options: GridFsFindOneOptions) -> Self { + Self { + max_time: options.max_time, + skip: options.skip, + sort: options.sort, + ..Default::default() + } + } +} diff --git a/src/gridfs/upload.rs b/src/gridfs/upload.rs index 303114c73..8bd14f565 100644 --- a/src/gridfs/upload.rs +++ b/src/gridfs/upload.rs @@ -6,159 +6,32 @@ use std::{ use futures_util::{ future::{BoxFuture, FutureExt}, - io::{AsyncRead, AsyncReadExt, AsyncWrite}, + io::AsyncWrite, stream::TryStreamExt, }; -use super::{options::GridFsUploadOptions, Chunk, FilesCollectionDocument, GridFsBucket}; +use super::{Chunk, FilesCollectionDocument, GridFsBucket}; use crate::{ + action::Action, bson::{doc, oid::ObjectId, spec::BinarySubtype, Bson, DateTime, Document, RawBinaryRef}, bson_util::get_int, + checked::Checked, + client::AsyncDropToken, error::{Error, ErrorKind, GridFsErrorKind, Result}, index::IndexModel, - options::{CreateCollectionOptions, FindOneOptions, ReadPreference, SelectionCriteria}, - runtime, + options::{ReadPreference, SelectionCriteria}, Collection, }; -// User functions for uploading from readers. impl GridFsBucket { - /// Uploads a user file to the bucket. Bytes are read from `source`, which may be any type that - /// implements the [`futures_io::AsyncRead`] trait, and stored in chunks in the bucket's - /// chunks collection. After all the chunks have been uploaded, a corresponding - /// [`FilesCollectionDocument`] is stored in the bucket's files collection. - /// - /// This method generates an [`ObjectId`] for the `id` field of the - /// [`FilesCollectionDocument`] and returns it. - /// - /// To upload from a type that implements [`tokio::io::AsyncRead`], use the - /// [`tokio_util::compat`] module to convert between types. - /// - /// ```rust - /// # use mongodb::{error::Result, gridfs::GridFsBucket}; - /// # async fn compat_example( - /// # bucket: GridFsBucket, - /// # tokio_reader: impl tokio::io::AsyncRead + Unpin) - /// # -> Result<()> { - /// use tokio_util::compat::TokioAsyncReadCompatExt; - /// - /// let futures_reader = tokio_reader.compat(); - /// bucket.upload_from_futures_0_3_reader("example_file", futures_reader, None).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// Note that once an `AsyncRead` trait is stabilized in the standard library, this method will - /// be deprecated in favor of one that accepts a `std::io::AsyncRead` source. - pub async fn upload_from_futures_0_3_reader( - &self, - filename: impl AsRef, - source: T, - options: impl Into>, - ) -> Result - where - T: AsyncRead + Unpin, - { - let id = ObjectId::new(); - self.upload_from_futures_0_3_reader_with_id(id.into(), filename, source, options) - .await?; - Ok(id) - } - - /// Uploads a user file to the bucket. Bytes are read from `source`, which may be any type that - /// implements the [`futures_io::AsyncRead`] trait, and stored in chunks in the bucket's - /// chunks collection. After all the chunks have been uploaded, a corresponding - /// [`FilesCollectionDocument`] with the given `id` is stored in the bucket's files collection. - /// - /// To upload from a type that implements [`tokio::io::AsyncRead`], use the - /// [`tokio_util::compat`] module to convert between types. - /// - /// ```rust - /// # use mongodb::{bson::Bson, error::Result, gridfs::GridFsBucket}; - /// # async fn compat_example( - /// # bucket: GridFsBucket, - /// # tokio_reader: impl tokio::io::AsyncRead + Unpin, - /// # id: Bson, - /// # ) -> Result<()> { - /// use tokio_util::compat::TokioAsyncReadCompatExt; - /// - /// let futures_reader = tokio_reader.compat(); - /// bucket.upload_from_futures_0_3_reader_with_id(id, "example_file", futures_reader, None).await?; - /// # Ok(()) - /// # } - /// ``` - /// - /// Note that once an `AsyncRead` trait is stabilized in the standard library, this method will - /// be deprecated in favor of one that accepts a `std::io::AsyncRead` source. - pub async fn upload_from_futures_0_3_reader_with_id( - &self, - id: Bson, - filename: impl AsRef, - mut source: T, - options: impl Into>, - ) -> Result<()> - where - T: AsyncRead + Unpin, - { - let options = options.into(); - - self.create_indexes().await?; - - let chunk_size_bytes = options - .as_ref() - .and_then(|opts| opts.chunk_size_bytes) - .unwrap_or_else(|| self.chunk_size_bytes()); - let mut length = 0u64; - let mut n = 0; - - let mut buf = vec![0u8; chunk_size_bytes as usize]; - loop { - let bytes_read = match read_exact_or_to_end(&mut buf, &mut source).await { - Ok(0) => break, - Ok(n) => n, - Err(error) => { - return clean_up_chunks(id.clone(), self.chunks().clone(), Some(error)).await; - } - }; - - let chunk = Chunk { - id: ObjectId::new(), - files_id: id.clone(), - n, - data: RawBinaryRef { - subtype: BinarySubtype::Generic, - bytes: &buf[..bytes_read], - }, - }; - self.chunks().insert_one(chunk, None).await?; - - length += bytes_read as u64; - n += 1; - } - - let file = FilesCollectionDocument { - id, - length, - chunk_size_bytes, - upload_date: DateTime::now(), - filename: Some(filename.as_ref().to_string()), - metadata: options.and_then(|opts| opts.metadata), - }; - self.files().insert_one(file, None).await?; - - Ok(()) - } - async fn create_indexes(&self) -> Result<()> { if !self.inner.created_indexes.load(Ordering::SeqCst) { - let find_options = FindOneOptions::builder() - .selection_criteria(SelectionCriteria::ReadPreference(ReadPreference::Primary)) - .projection(doc! { "_id": 1 }) - .build(); if self .files() .clone_with_type::() - .find_one(None, find_options) + .find_one(doc! {}) + .selection_criteria(SelectionCriteria::ReadPreference(ReadPreference::Primary)) + .projection(doc! { "_id": 1 }) .await? .is_none() { @@ -173,13 +46,20 @@ impl GridFsBucket { Ok(()) } - async fn create_index(&self, coll: &Collection, keys: Document) -> Result<()> { + async fn create_index( + &self, + coll: &Collection, + keys: Document, + ) -> Result<()> { // listIndexes returns an error if the collection has not yet been created. - let options = CreateCollectionOptions::builder() - .write_concern(self.write_concern().cloned()) - .build(); // Ignore NamespaceExists errors if the collection has already been created. - if let Err(error) = self.inner.db.create_collection(coll.name(), options).await { + if let Err(error) = self + .inner + .db + .create_collection(coll.name()) + .optional(self.write_concern().cloned(), |b, wc| b.write_concern(wc)) + .await + { if error.sdam_code() != Some(48) { return Err(error); } @@ -187,7 +67,7 @@ impl GridFsBucket { // From the spec: Drivers MUST check whether the indexes already exist before attempting to // create them. - let mut indexes = coll.list_indexes(None).await?; + let mut indexes = coll.list_indexes().await?; 'outer: while let Some(index_model) = indexes.try_next().await? { if index_model.keys.len() != keys.len() { continue; @@ -214,28 +94,12 @@ impl GridFsBucket { } let index_model = IndexModel::builder().keys(keys).build(); - coll.create_index(index_model, None).await?; + coll.create_index(index_model).await?; Ok(()) } } -async fn read_exact_or_to_end(buf: &mut [u8], source: &mut T) -> Result -where - T: AsyncRead + Unpin, -{ - let mut total_bytes_read = 0; - loop { - let bytes_read = match source.read(&mut buf[total_bytes_read..]).await? { - 0 => break, - n => n, - }; - total_bytes_read += bytes_read; - } - - Ok(total_bytes_read) -} - /// A stream to which bytes can be written to be uploaded to a GridFS bucket. /// /// # Uploading to the Stream @@ -255,13 +119,16 @@ where /// use futures_util::io::AsyncWriteExt; /// /// let bytes = vec![0u8; 100]; -/// let mut upload_stream = bucket.open_upload_stream("example_file", None); +/// let mut upload_stream = bucket.open_upload_stream("example_file").await?; /// upload_stream.write_all(&bytes[..]).await?; /// upload_stream.close().await?; /// # Ok(()) /// # } /// ``` /// +/// If the data is a local file (or other `AsyncRead` byte source), its contents can be efficiently +/// written to the stream with [`futures_util::io::copy`]. +/// /// # Aborting the Stream /// A stream can be aborted by calling the `abort` method. This will remove any chunks associated /// with the stream from the chunks collection. It is an error to write to, abort, or close the @@ -273,7 +140,7 @@ where /// use futures_util::io::AsyncWriteExt; /// /// let bytes = vec![0u8; 100]; -/// let mut upload_stream = bucket.open_upload_stream("example_file", None); +/// let mut upload_stream = bucket.open_upload_stream("example_file").await?; /// upload_stream.write_all(&bytes[..]).await?; /// upload_stream.abort().await?; /// # Ok(()) @@ -300,11 +167,13 @@ where /// /// ```rust /// # use mongodb::gridfs::{GridFsBucket, GridFsUploadStream}; -/// # fn compat_example(bucket: GridFsBucket) { +/// # use mongodb::error::Result; +/// # async fn compat_example(bucket: GridFsBucket) -> Result<()> { /// use tokio_util::compat::FuturesAsyncWriteCompatExt; /// -/// let futures_upload_stream = bucket.open_upload_stream("example_file", None); +/// let futures_upload_stream = bucket.open_upload_stream("example_file").await?; /// let tokio_upload_stream = futures_upload_stream.compat_write(); +/// # Ok(()) /// # } /// ``` pub struct GridFsUploadStream { @@ -317,6 +186,7 @@ pub struct GridFsUploadStream { // taken and inserted into a FilesCollectionDocument when the stream is closed. filename: Option, metadata: Option>, + drop_token: AsyncDropToken, } type WriteBytesFuture = BoxFuture<'static, Result<(u32, Vec)>>; @@ -350,6 +220,26 @@ impl State { } impl GridFsUploadStream { + pub(crate) fn new( + bucket: GridFsBucket, + id: Bson, + filename: String, + chunk_size_bytes: u32, + metadata: Option, + drop_token: AsyncDropToken, + ) -> Self { + Self { + bucket, + state: State::Idle(Some(Vec::new())), + current_n: 0, + id, + filename: Some(filename), + chunk_size_bytes, + metadata: Some(metadata), + drop_token, + } + } + /// Gets the stream's unique [`Bson`] identifier. This value will be the `id` field for the /// [`FilesCollectionDocument`] uploaded to the files collection when the stream is closed. pub fn id(&self) -> &Bson { @@ -371,13 +261,12 @@ impl GridFsUploadStream { } impl Drop for GridFsUploadStream { - // TODO RUST-1493: pre-create this task fn drop(&mut self) { if !matches!(self.state, State::Closed) { let chunks = self.bucket.chunks().clone(); let id = self.id.clone(); - runtime::execute(async move { - let _result = chunks.delete_many(doc! { "files_id": id }, None).await; + self.drop_token.spawn(async move { + let _result = chunks.delete_many(doc! { "files_id": id }).await; }) } } @@ -497,31 +386,32 @@ async fn write_bytes( chunk_size_bytes: u32, files_id: Bson, ) -> Result<(u32, Vec)> { + let chunk_size_bytes: usize = Checked::new(chunk_size_bytes).try_into()?; bucket.create_indexes().await?; - let mut n = 0; + let mut n = Checked::new(0); let mut chunks = vec![]; - while buffer.len() as u32 - (n * chunk_size_bytes) >= chunk_size_bytes { + while (Checked::new(buffer.len()) - (n * chunk_size_bytes)).get()? >= chunk_size_bytes { let start = n * chunk_size_bytes; let end = (n + 1) * chunk_size_bytes; let chunk = Chunk { id: ObjectId::new(), files_id: files_id.clone(), - n: starting_n + n, + n: starting_n + n.try_into::()?, data: RawBinaryRef { subtype: BinarySubtype::Generic, - bytes: &buffer[(start as usize)..(end as usize)], + bytes: &buffer[start.get()?..end.get()?], }, }; n += 1; chunks.push(chunk); } - match bucket.chunks().insert_many(chunks, None).await { + match bucket.chunks().insert_many(chunks).await { Ok(_) => { - buffer.drain(..(n * chunk_size_bytes) as usize); - Ok((n, buffer)) + buffer.drain(..(n * chunk_size_bytes).get()?); + Ok((n.try_into()?, buffer)) } Err(error) => match clean_up_chunks(files_id, bucket.chunks().clone(), Some(error)).await { // clean_up_chunks will always return an error if one is passed in, so this case is @@ -545,9 +435,9 @@ async fn close(bucket: GridFsBucket, buffer: Vec, file: FilesCollectionDocum bytes: &buffer[..], }, }; - bucket.chunks().insert_one(final_chunk, None).await?; + bucket.chunks().insert_one(final_chunk).await?; } - bucket.files().insert_one(&file, None).await?; + bucket.files().insert_one(&file).await?; Ok(()) } .await; @@ -563,7 +453,7 @@ async fn clean_up_chunks( chunks: Collection>, original_error: Option, ) -> Result<()> { - match chunks.delete_many(doc! { "files_id": id }, None).await { + match chunks.delete_many(doc! { "files_id": id }).await { Ok(_) => match original_error { Some(error) => Err(error), None => Ok(()), @@ -580,42 +470,3 @@ fn get_closed_error() -> futures_io::Error { let error: Error = ErrorKind::GridFs(GridFsErrorKind::UploadStreamClosed).into(); error.into_futures_io_error() } - -// User functions for creating upload streams. -impl GridFsBucket { - /// Creates and returns a [`GridFsUploadStream`] that the application can write the contents of - /// the file to. This method generates a unique [`ObjectId`] for the corresponding - /// [`FilesCollectionDocument`]'s `id` field that can be accessed via the stream's `id` - /// method. - pub fn open_upload_stream( - &self, - filename: impl AsRef, - options: impl Into>, - ) -> GridFsUploadStream { - self.open_upload_stream_with_id(ObjectId::new().into(), filename, options) - } - - /// Opens a [`GridFsUploadStream`] that the application can write the contents of the file to. - /// The provided `id` will be used for the corresponding [`FilesCollectionDocument`]'s `id` - /// field. - pub fn open_upload_stream_with_id( - &self, - id: Bson, - filename: impl AsRef, - options: impl Into>, - ) -> GridFsUploadStream { - let options = options.into(); - GridFsUploadStream { - bucket: self.clone(), - state: State::Idle(Some(Vec::new())), - current_n: 0, - id, - filename: Some(filename.as_ref().into()), - chunk_size_bytes: options - .as_ref() - .and_then(|opts| opts.chunk_size_bytes) - .unwrap_or_else(|| self.chunk_size_bytes()), - metadata: Some(options.and_then(|opts| opts.metadata)), - } - } -} diff --git a/src/hello.rs b/src/hello.rs index 37229ec5b..b070b8db1 100644 --- a/src/hello.rs +++ b/src/hello.rs @@ -1,7 +1,11 @@ use std::time::Duration; -use bson::RawDocumentBuf; +use crate::{ + bson::{rawdoc, RawDocumentBuf}, + bson_compat::cstr, +}; use serde::{Deserialize, Serialize}; +use tokio::sync::broadcast; use crate::{ bson::{doc, oid::ObjectId, DateTime, Document, Timestamp}, @@ -19,6 +23,7 @@ use crate::{ /// To limit usages of the legacy name in the codebase, this constant should be used /// wherever possible. pub(crate) const LEGACY_HELLO_COMMAND_NAME: &str = "isMaster"; +pub(crate) const LEGACY_HELLO_COMMAND_NAME_CSTR: &crate::bson_compat::CStr = cstr!("isMaster"); pub(crate) const LEGACY_HELLO_COMMAND_NAME_LOWERCASE: &str = "ismaster"; #[derive(Debug, Clone, Copy)] @@ -39,25 +44,31 @@ pub(crate) fn hello_command( hello_ok: Option, awaitable_options: Option, ) -> Command { - let (mut command, command_name) = if server_api.is_some() + let (mut body, command_name) = if server_api.is_some() || matches!(load_balanced, Some(true)) || matches!(hello_ok, Some(true)) { - (doc! { "hello": 1 }, "hello") + (rawdoc! { "hello": 1 }, "hello") } else { - let mut cmd = doc! { LEGACY_HELLO_COMMAND_NAME: 1 }; + let mut body = rawdoc! { LEGACY_HELLO_COMMAND_NAME_CSTR: 1 }; if hello_ok.is_none() { - cmd.insert("helloOk", true); + body.append(cstr!("helloOk"), true); } - (cmd, LEGACY_HELLO_COMMAND_NAME) + (body, LEGACY_HELLO_COMMAND_NAME) }; if let Some(opts) = awaitable_options { - command.insert("topologyVersion", opts.topology_version); - command.insert("maxAwaitTimeMS", opts.max_await_time.as_millis() as i64); + body.append(cstr!("topologyVersion"), opts.topology_version); + body.append( + cstr!("maxAwaitTimeMS"), + opts.max_await_time + .as_millis() + .try_into() + .unwrap_or(i64::MAX), + ); } - let mut command = Command::new(command_name.into(), "admin".into(), command); + let mut command = Command::new(command_name, "admin", body); if let Some(server_api) = server_api { command.set_server_api(server_api); } @@ -66,8 +77,18 @@ pub(crate) fn hello_command( } /// Execute a hello or legacy hello command. -pub(crate) async fn run_hello(conn: &mut Connection, command: Command) -> Result { - let response_result = conn.send_command(command, None).await; +pub(crate) async fn run_hello( + conn: &mut Connection, + command: Command, + mut cancellation_receiver: Option>, +) -> Result { + let response_result = match cancellation_receiver { + Some(ref mut cancellation_receiver) => { + conn.send_message_with_cancellation(command, cancellation_receiver) + .await + } + None => conn.send_message(command).await, + }; response_result.and_then(|raw_response| raw_response.into_hello_reply()) } @@ -130,8 +151,8 @@ pub(crate) struct HelloCommandResponse { /// Whether the server is an arbiter. pub arbiter_only: Option, - #[serde(rename = "isreplicaset")] + #[serde(rename = "isreplicaset")] /// Whether the server is a replica set. pub is_replica_set: Option, diff --git a/src/id_set.rs b/src/id_set.rs new file mode 100644 index 000000000..14d4cc90c --- /dev/null +++ b/src/id_set.rs @@ -0,0 +1,62 @@ +/// A container that provides removal tokens when an item is added. +#[derive(Debug, Clone)] +pub(crate) struct IdSet { + values: Vec>, + free: Vec, +} + +#[derive(Debug, Clone)] +struct Entry { + generation: u32, + value: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct Id { + index: usize, + generation: u32, +} + +impl IdSet { + pub(crate) fn new() -> Self { + Self { + values: vec![], + free: vec![], + } + } + + pub(crate) fn insert(&mut self, value: T) -> Id { + let value = Some(value); + if let Some(index) = self.free.pop() { + let generation = self.values[index].generation + 1; + self.values[index] = Entry { generation, value }; + Id { index, generation } + } else { + let generation = 0; + self.values.push(Entry { generation, value }); + Id { + index: self.values.len() - 1, + generation, + } + } + } + + pub(crate) fn remove(&mut self, id: &Id) { + if let Some(entry) = self.values.get_mut(id.index) { + if entry.generation == id.generation { + entry.value = None; + self.free.push(id.index); + } + } + } + + #[cfg(all(test, mongodb_internal_tracking_arc))] + pub(crate) fn values(&self) -> impl Iterator { + self.values.iter().filter_map(|e| e.value.as_ref()) + } + + pub(crate) fn extract(&mut self) -> Vec { + self.free.clear(); + self.values.drain(..).filter_map(|e| e.value).collect() + } +} diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 000000000..5cb032e1f --- /dev/null +++ b/src/index.rs @@ -0,0 +1,58 @@ +pub mod options; + +use crate::bson::Document; + +use self::options::*; +use serde::{Deserialize, Serialize}; + +use typed_builder::TypedBuilder; + +/// Specifies the fields and options for an index. For more information, see the [documentation](https://www.mongodb.com/docs/manual/indexes/). +#[derive(Clone, Debug, Default, Deserialize, TypedBuilder, Serialize)] +#[builder(field_defaults(default, setter(into)))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct IndexModel { + /// Specifies the index’s fields. For each field, specify a key-value pair in which the key is + /// the name of the field to index and the value is index type. + #[serde(rename = "key")] + pub keys: Document, + + /// The options for the index. + #[serde(flatten)] + pub options: Option, +} + +impl IndexModel { + /// If the client did not specify a name, generate and set it. Otherwise, do nothing. + pub(crate) fn update_name(&mut self) { + if self + .options + .as_ref() + .and_then(|o| o.name.as_ref()) + .is_none() + { + fn format_kv(kv: (&String, &crate::bson::Bson)) -> String { + if let crate::bson::Bson::String(s) = kv.1 { + format!("{}_{}", kv.0, s) + } else { + format!("{}_{}", kv.0, kv.1) + } + } + let key_names: Vec = self.keys.iter().map(format_kv).collect(); + self.options.get_or_insert(IndexOptions::default()).name = Some(key_names.join("_")); + } + } + + pub(crate) fn get_name(&self) -> Option { + self.options.as_ref().and_then(|o| o.name.as_ref()).cloned() + } + + #[cfg(test)] + pub(crate) fn is_unique(&self) -> bool { + self.options + .as_ref() + .and_then(|o| o.unique) + .unwrap_or(false) + } +} diff --git a/src/index/mod.rs b/src/index/mod.rs deleted file mode 100644 index 1078e2db5..000000000 --- a/src/index/mod.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub mod options; - -use crate::bson::Document; - -use self::options::*; -use serde::{Deserialize, Serialize}; - -use typed_builder::TypedBuilder; - -/// Specifies the fields and options for an index. For more information, see the [documentation](https://www.mongodb.com/docs/manual/indexes/). -#[derive(Clone, Debug, Default, Deserialize, TypedBuilder, Serialize)] -#[builder(field_defaults(default, setter(into)))] -#[serde(rename_all = "camelCase")] -#[non_exhaustive] -pub struct IndexModel { - /// Specifies the index’s fields. For each field, specify a key-value pair in which the key is - /// the name of the field to index and the value is index type. - #[serde(rename = "key")] - pub keys: Document, - - /// The options for the index. - #[serde(flatten)] - pub options: Option, -} - -impl IndexModel { - /// If the client did not specify a name, generate and set it. Otherwise, do nothing. - pub(crate) fn update_name(&mut self) { - if self - .options - .as_ref() - .and_then(|o| o.name.as_ref()) - .is_none() - { - let key_names: Vec = self - .keys - .iter() - .map(|(k, v)| format!("{}_{}", k, v)) - .collect(); - self.options.get_or_insert(IndexOptions::default()).name = Some(key_names.join("_")); - } - } - - pub(crate) fn get_name(&self) -> Option { - self.options.as_ref().and_then(|o| o.name.as_ref()).cloned() - } - - #[cfg(test)] - pub(crate) fn is_unique(&self) -> bool { - self.options - .as_ref() - .and_then(|o| o.unique) - .unwrap_or(false) - } -} diff --git a/src/index/options.rs b/src/index/options.rs index 934a7211e..b196525e7 100644 --- a/src/index/options.rs +++ b/src/index/options.rs @@ -1,10 +1,6 @@ use std::time::Duration; -use crate::{ - bson::{serde_helpers, Document}, - bson_util, - collation::Collation, -}; +use crate::{bson::Document, collation::Collation, serde_util}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use typed_builder::TypedBuilder; @@ -29,8 +25,7 @@ pub struct IndexOptions { #[serde( rename = "expireAfterSeconds", default, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_seconds", - serialize_with = "bson_util::serialize_duration_option_as_int_secs" + with = "serde_util::duration_option_as_int_seconds" )] pub expire_after: Option, @@ -82,7 +77,7 @@ pub struct IndexOptions { /// For `2dsphere` indexes, the number of precision of the stored geohash value of the /// location data. The bits value ranges from 1 to 32 inclusive. - #[serde(serialize_with = "bson_util::serialize_u32_option_as_i32")] + #[serde(serialize_with = "serde_util::serialize_u32_option_as_i32")] pub bits: Option, /// For `2dsphere` indexes, the upper inclusive boundary for the longitude and latitude @@ -95,7 +90,7 @@ pub struct IndexOptions { /// For `geoHaystack` indexes, specify the number of units within which to group the location /// values. - #[serde(serialize_with = "bson_util::serialize_u32_option_as_i32")] + #[serde(serialize_with = "serde_util::serialize_u32_option_as_i32")] pub bucket_size: Option, /// If specified, the index only references documents that match the filter @@ -148,28 +143,28 @@ pub enum IndexVersion { Custom(u32), } -#[allow(deprecated)] impl Serialize for IndexVersion { fn serialize(&self, serializer: S) -> Result where S: Serializer, { match self { + #[allow(deprecated)] IndexVersion::V0 => serializer.serialize_i32(0), IndexVersion::V1 => serializer.serialize_i32(1), IndexVersion::V2 => serializer.serialize_i32(2), - IndexVersion::Custom(i) => serde_helpers::serialize_u32_as_i32(i, serializer), + IndexVersion::Custom(i) => serde_util::serialize_u32_as_i32(i, serializer), } } } -#[allow(deprecated)] impl<'de> Deserialize<'de> for IndexVersion { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { match i32::deserialize(deserializer)? { + #[allow(deprecated)] 0 => Ok(IndexVersion::V0), 1 => Ok(IndexVersion::V1), 2 => Ok(IndexVersion::V2), @@ -204,7 +199,7 @@ impl Serialize for TextIndexVersion { TextIndexVersion::V1 => serializer.serialize_i32(1), TextIndexVersion::V2 => serializer.serialize_i32(2), TextIndexVersion::V3 => serializer.serialize_i32(3), - TextIndexVersion::Custom(i) => serde_helpers::serialize_u32_as_i32(i, serializer), + TextIndexVersion::Custom(i) => serde_util::serialize_u32_as_i32(i, serializer), } } } @@ -245,7 +240,7 @@ impl Serialize for Sphere2DIndexVersion { match self { Sphere2DIndexVersion::V2 => serializer.serialize_i32(2), Sphere2DIndexVersion::V3 => serializer.serialize_i32(3), - Sphere2DIndexVersion::Custom(i) => serde_helpers::serialize_u32_as_i32(i, serializer), + Sphere2DIndexVersion::Custom(i) => serde_util::serialize_u32_as_i32(i, serializer), } } } diff --git a/src/lib.rs b/src/lib.rs index cf0301a52..7f0ab39e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,322 +1,30 @@ -//! This crate contains the officially supported MongoDB Rust driver, a -//! client side library that can be used to interact with MongoDB deployments -//! in Rust applications. It uses the [`bson`] crate for BSON support. -//! The driver contains a fully async API that supports either [`tokio`](https://docs.rs/tokio) (default) -//! or [`async-std`](https://docs.rs/async_std), depending on the feature flags set. The driver also has -//! a sync API that may be enabled via the `"sync"` or `"tokio-sync"` feature flag. -//! -//! # Installation -//! -//! ## Requirements -//! - Rust 1.57+ -//! - MongoDB 3.6+ -//! -//! ## Importing -//! The driver is available on [crates.io](https://crates.io/crates/mongodb). To use the driver in -//! your application, simply add it to your project's `Cargo.toml`. -//! ```toml -//! [dependencies] -//! mongodb = "2.5.0" -//! ``` -//! -//! ### Configuring the async runtime -//! The driver supports both of the most popular async runtime crates, namely -//! [`tokio`](https://crates.io/crates/tokio) and [`async-std`](https://crates.io/crates/async-std). By -//! default, the driver will use [`tokio`](https://crates.io/crates/tokio), but you can explicitly choose -//! a runtime by specifying one of `"tokio-runtime"` or `"async-std-runtime"` feature flags in your -//! `Cargo.toml`. -//! -//! For example, to instruct the driver to work with [`async-std`](https://crates.io/crates/async-std), -//! add the following to your `Cargo.toml`: -//! ```toml -//! [dependencies.mongodb] -//! version = "2.5.0" -//! default-features = false -//! features = ["async-std-runtime"] -//! ``` -//! -//! ### Enabling the sync API -//! The driver also provides a blocking sync API. To enable this, add the `"sync"` or `"tokio-sync"` -//! feature to your `Cargo.toml`: -//! ```toml -//! [dependencies.mongodb] -//! version = "2.5.0" -//! features = ["tokio-sync"] -//! ``` -//! Using the `"sync"` feature also requires using `default-features = false`. -//! **Note:** The sync-specific types can be imported from `mongodb::sync` (e.g. -//! `mongodb::sync::Client`). -//! -//! ### All Feature flags -//! -//! | Feature | Description | Default | -//! |:-----------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------| -//! | `tokio-runtime` | Enable support for the `tokio` async runtime. | yes | -//! | `async-std-runtime` | Enable support for the `async-std` runtime. | no | -//! | `sync` | Expose the synchronous API (`mongodb::sync`), using an async-std backend. Cannot be used with the `tokio-runtime` feature flag. | no | -//! | `tokio-sync` | Expose the synchronous API (`mongodb::sync`), using a tokio backend. Cannot be used with the `async-std-runtime` feature flag. | no | -//! | `aws-auth` | Enable support for the MONGODB-AWS authentication mechanism. | no | -//! | `bson-uuid-0_8` | Enable support for v0.8 of the [`uuid`](docs.rs/uuid/0.8) crate in the public API of the re-exported `bson` crate. | no | -//! | `bson-uuid-1` | Enable support for v1.x of the [`uuid`](docs.rs/uuid/1.0) crate in the public API of the re-exported `bson` crate. | no | -//! | `bson-chrono-0_4` | Enable support for v0.4 of the [`chrono`](docs.rs/chrono/0.4) crate in the public API of the re-exported `bson` crate. | no | -//! | `bson-serde_with` | Enable support for the [`serde_with`](docs.rs/serde_with/latest) crate in the public API of the re-exported `bson` crate. | no | -//! | `zlib-compression` | Enable support for compressing messages with [`zlib`](https://zlib.net/). | no | -//! | `zstd-compression` | Enable support for compressing messages with [`zstd`](http://facebook.github.io/zstd/). | no | -//! | `snappy-compression` | Enable support for compressing messages with [`snappy`](http://google.github.io/snappy/). | no | -//! | `openssl-tls` | Switch TLS connection handling to use [`openssl`](https://docs.rs/openssl/0.10.38/). | no | -//! | `in-use-encryption-unstable` | Enable support for client-side field level encryption and queryable encryption. This API is unstable and may be subject to breaking changes in minor releases. | no | -//! | `tracing-unstable` | Enable support for emitting [`tracing`](https://docs.rs/tracing/latest/tracing/) events. This API is unstable and may be subject to breaking changes in minor releases. | no | -//! -//! # Example Usage -//! -//! ## Using the async API -//! ### Connecting to a MongoDB deployment -//! ```no_run -//! # async fn foo() -> mongodb::error::Result<()> { -//! use mongodb::{Client, options::ClientOptions}; -//! -//! // Parse a connection string into an options struct. -//! let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?; -//! -//! // Manually set an option. -//! client_options.app_name = Some("My App".to_string()); -//! -//! // Get a handle to the deployment. -//! let client = Client::with_options(client_options)?; -//! -//! // List the names of the databases in that deployment. -//! for db_name in client.list_database_names(None, None).await? { -//! println!("{}", db_name); -//! } -//! # Ok(()) } -//! ``` -//! ### Getting a handle to a database -//! ```no_run -//! # async fn foo() -> mongodb::error::Result<()> { -//! # let client = mongodb::Client::with_uri_str("").await?; -//! // Get a handle to a database. -//! let db = client.database("mydb"); -//! -//! // List the names of the collections in that database. -//! for collection_name in db.list_collection_names(None).await? { -//! println!("{}", collection_name); -//! } -//! # Ok(()) } -//! ``` -//! ### Inserting documents into a collection -//! ```no_run -//! # async fn foo() -> mongodb::error::Result<()> { -//! # let db = mongodb::Client::with_uri_str("").await?.database(""); -//! use mongodb::bson::{doc, Document}; -//! -//! // Get a handle to a collection in the database. -//! let collection = db.collection::("books"); -//! -//! let docs = vec![ -//! doc! { "title": "1984", "author": "George Orwell" }, -//! doc! { "title": "Animal Farm", "author": "George Orwell" }, -//! doc! { "title": "The Great Gatsby", "author": "F. Scott Fitzgerald" }, -//! ]; -//! -//! // Insert some documents into the "mydb.books" collection. -//! collection.insert_many(docs, None).await?; -//! # Ok(()) } -//! ``` -//! -//! A [`Collection`](struct.Collection.html) can be parameterized with any type that implements the -//! `Serialize` and `Deserialize` traits from the [`serde`](https://serde.rs/) crate, -//! not just `Document`: -//! -//! ```toml -//! # In Cargo.toml, add the following dependency. -//! serde = { version = "1.0", features = ["derive"] } -//! ``` -//! -//! ```no_run -//! # async fn foo() -> mongodb::error::Result<()> { -//! # let db = mongodb::Client::with_uri_str("").await?.database(""); -//! use serde::{Deserialize, Serialize}; -//! -//! #[derive(Debug, Serialize, Deserialize)] -//! struct Book { -//! title: String, -//! author: String, -//! } -//! -//! // Get a handle to a collection of `Book`. -//! let typed_collection = db.collection::("books"); -//! -//! let books = vec![ -//! Book { -//! title: "The Grapes of Wrath".to_string(), -//! author: "John Steinbeck".to_string(), -//! }, -//! Book { -//! title: "To Kill a Mockingbird".to_string(), -//! author: "Harper Lee".to_string(), -//! }, -//! ]; -//! -//! // Insert the books into "mydb.books" collection, no manual conversion to BSON necessary. -//! typed_collection.insert_many(books, None).await?; -//! # Ok(()) } -//! ``` -//! -//! ### Finding documents in a collection -//! Results from queries are generally returned via [`Cursor`](struct.Cursor.html), a struct which streams -//! the results back from the server as requested. The [`Cursor`](struct.Cursor.html) type implements the -//! [`Stream`](https://docs.rs/futures/latest/futures/stream/trait.Stream.html) trait from -//! the [`futures`](https://crates.io/crates/futures) crate, and in order to access its streaming -//! functionality you need to import at least one of the -//! [`StreamExt`](https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html) or -//! [`TryStreamExt`](https://docs.rs/futures/latest/futures/stream/trait.TryStreamExt.html) traits. -//! -//! ```toml -//! # In Cargo.toml, add the following dependency. -//! futures = "0.3" -//! ``` -//! ```no_run -//! # use serde::Deserialize; -//! # #[derive(Deserialize)] -//! # struct Book { title: String } -//! # async fn foo() -> mongodb::error::Result<()> { -//! # let typed_collection = mongodb::Client::with_uri_str("").await?.database("").collection::(""); -//! // This trait is required to use `try_next()` on the cursor -//! use futures::stream::TryStreamExt; -//! use mongodb::{bson::doc, options::FindOptions}; -//! -//! // Query the books in the collection with a filter and an option. -//! let filter = doc! { "author": "George Orwell" }; -//! let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build(); -//! let mut cursor = typed_collection.find(filter, find_options).await?; -//! -//! // Iterate over the results of the cursor. -//! while let Some(book) = cursor.try_next().await? { -//! println!("title: {}", book.title); -//! } -//! # Ok(()) } -//! ``` -//! -//! ### Using the sync API -//! The driver also provides a blocking sync API. See the [Installation](#enabling-the-sync-api) -//! section for instructions on how to enable it. -//! -//! The various sync-specific types are found in the `mongodb::sync` submodule rather than in the -//! crate's top level like in the async API. The sync API calls through to the async API internally -//! though, so it looks and behaves similarly to it. -//! ```no_run -//! # #[cfg(any(feature = "sync", feature = "tokio-sync"))] -//! # { -//! use mongodb::{ -//! bson::doc, -//! sync::Client, -//! }; -//! use serde::{Deserialize, Serialize}; -//! -//! #[derive(Debug, Serialize, Deserialize)] -//! struct Book { -//! title: String, -//! author: String, -//! } -//! -//! let client = Client::with_uri_str("mongodb://localhost:27017")?; -//! let database = client.database("mydb"); -//! let collection = database.collection::("books"); -//! -//! let docs = vec![ -//! Book { -//! title: "1984".to_string(), -//! author: "George Orwell".to_string(), -//! }, -//! Book { -//! title: "Animal Farm".to_string(), -//! author: "George Orwell".to_string(), -//! }, -//! Book { -//! title: "The Great Gatsby".to_string(), -//! author: "F. Scott Fitzgerald".to_string(), -//! }, -//! ]; -//! -//! // Insert some books into the "mydb.books" collection. -//! collection.insert_many(docs, None)?; -//! -//! let cursor = collection.find(doc! { "author": "George Orwell" }, None)?; -//! for result in cursor { -//! println!("title: {}", result?.title); -//! } -//! # } -//! ``` -//! -//! ## Warning about timeouts / cancellation -//! -//! In async Rust, it is common to implement cancellation and timeouts by dropping a future after a -//! certain period of time instead of polling it to completion. This is how -//! [`tokio::time::timeout`](https://docs.rs/tokio/1.10.1/tokio/time/fn.timeout.html) works, for -//! example. However, doing this with futures returned by the driver can leave the driver's internals in -//! an inconsistent state, which may lead to unpredictable or incorrect behavior (see RUST-937 for more -//! details). As such, it is **_highly_** recommended to poll all futures returned from the driver to -//! completion. In order to still use timeout mechanisms like `tokio::time::timeout` with the driver, -//! one option is to spawn tasks and time out on their -//! [`JoinHandle`](https://docs.rs/tokio/1.10.1/tokio/task/struct.JoinHandle.html) futures instead of on -//! the driver's futures directly. This will ensure the driver's futures will always be completely polled -//! while also allowing the application to continue in the event of a timeout. -//! -//! e.g. -//! ``` rust -//! # use std::time::Duration; -//! # use mongodb::{ -//! # Client, -//! # bson::doc, -//! # }; -//! # -//! # #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync"), feature = "tokio-runtime"))] -//! # async fn foo() -> std::result::Result<(), Box> { -//! # -//! # let client = Client::with_uri_str("mongodb://example.com").await?; -//! let collection = client.database("foo").collection("bar"); -//! let handle = tokio::task::spawn(async move { -//! collection.insert_one(doc! { "x": 1 }, None).await -//! }); -//! -//! tokio::time::timeout(Duration::from_secs(5), handle).await???; -//! # Ok(()) -//! # } -//! ``` -//! -//! ## Minimum supported Rust version (MSRV) -//! -//! The MSRV for this crate is currently 1.57.0. This will be rarely be increased, and if it ever is, -//! it will only happen in a minor or major version release. - +#![doc = include_str!("../README.md")] #![warn(missing_docs)] #![warn(rustdoc::missing_crate_level_docs)] -#![cfg_attr( - feature = "cargo-clippy", - allow( - clippy::unreadable_literal, - clippy::cognitive_complexity, - clippy::float_cmp, - clippy::match_like_matches_macro, - clippy::derive_partial_eq_without_eq - ) +#![warn(clippy::cast_possible_truncation)] +#![warn(clippy::cast_possible_wrap)] +#![allow( + clippy::unreadable_literal, + clippy::cognitive_complexity, + clippy::float_cmp, + clippy::match_like_matches_macro, + clippy::derive_partial_eq_without_eq )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(test, type_length_limit = "80000000")] -#![doc(html_root_url = "https://docs.rs/mongodb/2.5.0")] - -#[cfg(all(feature = "aws-auth", feature = "async-std-runtime"))] -compile_error!("The `aws-auth` feature flag is only supported on the tokio runtime."); +#![doc(html_root_url = "https://docs.rs/mongodb/3.2.3")] #[macro_use] pub mod options; -pub use ::bson; -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] pub use ::mongocrypt; +pub mod action; +pub(crate) mod bson_compat; mod bson_util; pub mod change_stream; +pub(crate) mod checked; mod client; mod cmap; mod coll; @@ -329,21 +37,34 @@ pub mod error; pub mod event; pub mod gridfs; mod hello; +pub(crate) mod id_set; mod index; mod operation; pub mod results; pub(crate) mod runtime; mod sdam; +mod search_index; mod selection_criteria; +mod serde_util; mod srv; -#[cfg(any(feature = "sync", feature = "tokio-sync"))] +#[cfg(feature = "sync")] pub mod sync; #[cfg(test)] mod test; #[cfg(feature = "tracing-unstable")] mod trace; +pub(crate) mod tracking_arc; + +#[cfg(not(any(feature = "bson-2", feature = "bson-3")))] +compile_error!("One of the bson-2 and bson-3 features must be enabled."); + +#[cfg(all(feature = "bson-2", not(feature = "bson-3")))] +pub use bson2 as bson; + +#[cfg(feature = "bson-3")] +pub use bson3 as bson; -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] pub use crate::client::csfle::client_encryption; pub use crate::{ client::{session::ClientSession, Client}, @@ -353,30 +74,19 @@ pub use crate::{ Cursor, }, db::Database, - gridfs::{GridFsBucket, GridFsDownloadStream, GridFsUploadStream}, }; -pub use {client::session::ClusterTime, coll::Namespace, index::IndexModel, sdam::public::*}; +pub use client::session::ClusterTime; +pub use coll::Namespace; +pub use index::IndexModel; +pub use sdam::public::*; +pub use search_index::{SearchIndexModel, SearchIndexType}; -#[cfg(all(feature = "tokio-runtime", feature = "sync",))] -compile_error!( - "`tokio-runtime` and `sync` can't both be enabled; either switch to using `tokio-sync` or set \ - `default-features = false` in your Cargo.toml" -); - -#[cfg(all( - feature = "tokio-runtime", - feature = "async-std-runtime", - not(feature = "sync") -))] -compile_error!( - "`tokio-runtime` and `async-std-runtime` can't both be enabled; either disable \ - `async-std-runtime` or set `default-features = false` in your Cargo.toml" -); +/// A boxed future. +pub type BoxFuture<'a, T> = std::pin::Pin + Send + 'a>>; -#[cfg(all(not(feature = "tokio-runtime"), not(feature = "async-std-runtime")))] +#[cfg(not(feature = "compat-3-3-0"))] compile_error!( - "one of `tokio-runtime`, `async-std-runtime`, `sync`, or `tokio-sync` must be enabled; \ - either enable `default-features`, or enable one of those features specifically in your \ - Cargo.toml" + "The feature 'compat-3-3-0' must be enabled to ensure forward compatibility with future \ + versions of this crate." ); diff --git a/src/operation.rs b/src/operation.rs new file mode 100644 index 000000000..34d5522a3 --- /dev/null +++ b/src/operation.rs @@ -0,0 +1,576 @@ +mod abort_transaction; +pub(crate) mod aggregate; +pub(crate) mod bulk_write; +mod commit_transaction; +pub(crate) mod count; +pub(crate) mod count_documents; +pub(crate) mod create; +mod create_indexes; +mod delete; +mod distinct; +pub(crate) mod drop_collection; +pub(crate) mod drop_database; +mod drop_indexes; +mod find; +pub(crate) mod find_and_modify; +mod get_more; +mod insert; +pub(crate) mod list_collections; +pub(crate) mod list_databases; +mod list_indexes; +#[cfg(feature = "in-use-encryption")] +mod raw_output; +pub(crate) mod run_command; +pub(crate) mod run_cursor_command; +mod search_index; +mod update; + +use std::{collections::VecDeque, fmt::Debug, ops::Deref}; + +use bson::{RawBsonRef, RawDocument, RawDocumentBuf, Timestamp}; +use futures_util::FutureExt; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +use crate::{ + bson::{self, Bson, Document}, + bson_compat::CStr, + bson_util::{self, extend_raw_document_buf}, + client::{ClusterTime, HELLO_COMMAND_NAMES, REDACTED_COMMANDS}, + cmap::{ + conn::{pooled::PooledConnection, PinnedConnectionHandle}, + Command, + RawCommandResponse, + StreamDescription, + }, + error::{ + CommandError, + Error, + ErrorKind, + IndexedWriteError, + InsertManyError, + Result, + WriteConcernError, + WriteFailure, + }, + options::{ClientOptions, WriteConcern}, + selection_criteria::SelectionCriteria, + BoxFuture, + ClientSession, + Namespace, +}; + +pub(crate) use abort_transaction::AbortTransaction; +pub(crate) use commit_transaction::CommitTransaction; +pub(crate) use create_indexes::CreateIndexes; +pub(crate) use delete::Delete; +pub(crate) use distinct::Distinct; +pub(crate) use drop_indexes::DropIndexes; +pub(crate) use find::Find; +pub(crate) use find_and_modify::FindAndModify; +pub(crate) use get_more::GetMore; +pub(crate) use insert::Insert; +pub(crate) use list_indexes::ListIndexes; +#[cfg(feature = "in-use-encryption")] +pub(crate) use raw_output::RawOutput; +pub(crate) use search_index::{CreateSearchIndexes, DropSearchIndex, UpdateSearchIndex}; +pub(crate) use update::{Update, UpdateOrReplace}; + +const SERVER_4_2_0_WIRE_VERSION: i32 = 8; +const SERVER_4_4_0_WIRE_VERSION: i32 = 9; +const SERVER_5_0_0_WIRE_VERSION: i32 = 13; +const SERVER_8_0_0_WIRE_VERSION: i32 = 25; +// The maximum number of bytes that may be included in a write payload when auto-encryption is +// enabled. +const MAX_ENCRYPTED_WRITE_SIZE: usize = 2_097_152; +// The amount of message overhead (OP_MSG bytes and command-agnostic fields) to account for when +// building a multi-write operation using document sequences. +const OP_MSG_OVERHEAD_BYTES: usize = 1_000; + +/// Context about the execution of the operation. +pub(crate) struct ExecutionContext<'a> { + pub(crate) connection: &'a mut PooledConnection, + pub(crate) session: Option<&'a mut ClientSession>, + pub(crate) effective_criteria: SelectionCriteria, +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub(crate) enum Retryability { + Write, + Read, + None, +} + +impl Retryability { + /// Returns this level of retryability in tandem with the client options. + pub(crate) fn with_options(&self, options: &ClientOptions) -> Self { + match self { + Self::Write if options.retry_writes != Some(false) => Self::Write, + Self::Read if options.retry_reads != Some(false) => Self::Read, + _ => Self::None, + } + } + + /// Whether this level of retryability can retry the given error. + pub(crate) fn can_retry_error(&self, error: &Error) -> bool { + match self { + Self::Write => error.is_write_retryable(), + Self::Read => error.is_read_retryable(), + Self::None => false, + } + } +} + +/// A trait modeling the behavior of a server side operation. +/// +/// No methods in this trait should have default behaviors to ensure that wrapper operations +/// replicate all behavior. Default behavior is provided by the `OperationDefault` trait. +pub(crate) trait Operation { + /// The output type of this operation. + type O; + + /// The name of the server side command associated with this operation. + const NAME: &'static CStr; + + /// Returns the command that should be sent to the server as part of this operation. + /// The operation may store some additional state that is required for handling the response. + fn build(&mut self, description: &StreamDescription) -> Result; + + /// Parse the response for the atClusterTime field. + /// Depending on the operation, this may be found in different locations. + fn extract_at_cluster_time(&self, _response: &RawDocument) -> Result>; + + /// Interprets the server response to the command. + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> BoxFuture<'a, Result>; + + /// Interpret an error encountered while sending the built command to the server, potentially + /// recovering. + fn handle_error(&self, error: Error) -> Result; + + /// Criteria to use for selecting the server that this operation will be executed on. + fn selection_criteria(&self) -> Option<&SelectionCriteria>; + + /// Whether or not this operation will request acknowledgment from the server. + fn is_acknowledged(&self) -> bool; + + /// The write concern to use for this operation, if any. + fn write_concern(&self) -> Option<&WriteConcern>; + + /// Returns whether or not this command supports the `readConcern` field. + fn supports_read_concern(&self, description: &StreamDescription) -> bool; + + /// Whether this operation supports sessions or not. + fn supports_sessions(&self) -> bool; + + /// The level of retryability the operation supports. + fn retryability(&self) -> Retryability; + + /// Updates this operation as needed for a retry. + fn update_for_retry(&mut self); + + /// Returns a function handle to potentially override selection criteria based on server + /// topology. + fn override_criteria(&self) -> OverrideCriteriaFn; + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle>; + + /// The name of the server side command associated with this operation. + fn name(&self) -> &CStr; +} + +pub(crate) type OverrideCriteriaFn = + fn(&SelectionCriteria, &crate::sdam::TopologyDescription) -> Option; + +// A mirror of the `Operation` trait, with default behavior where appropriate. Should only be +// implemented by operation types that do not delegate to other operations. +pub(crate) trait OperationWithDefaults: Send + Sync { + /// The output type of this operation. + type O; + + /// The name of the server side command associated with this operation. + const NAME: &'static CStr; + + /// Returns the command that should be sent to the server as part of this operation. + /// The operation may store some additional state that is required for handling the response. + fn build(&mut self, description: &StreamDescription) -> Result; + + /// Parse the response for the atClusterTime field. + /// Depending on the operation, this may be found in different locations. + fn extract_at_cluster_time(&self, _response: &RawDocument) -> Result> { + Ok(None) + } + + /// Interprets the server response to the command. + fn handle_response<'a>( + &'a self, + _response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + Err(ErrorKind::Internal { + message: format!("operation handling not implemented for {}", Self::NAME), + } + .into()) + } + + /// Interprets the server response to the command. This method should only be implemented when + /// async code is required to handle the response. + fn handle_response_async<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> BoxFuture<'a, Result> { + async move { self.handle_response(response, context) }.boxed() + } + + /// Interpret an error encountered while sending the built command to the server, potentially + /// recovering. + fn handle_error(&self, error: Error) -> Result { + Err(error) + } + + /// Criteria to use for selecting the server that this operation will be executed on. + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + None + } + + /// Whether or not this operation will request acknowledgment from the server. + fn is_acknowledged(&self) -> bool { + self.write_concern() + .map(WriteConcern::is_acknowledged) + .unwrap_or(true) + } + + /// The write concern to use for this operation, if any. + fn write_concern(&self) -> Option<&WriteConcern> { + None + } + + /// Returns whether or not this command supports the `readConcern` field. + fn supports_read_concern(&self, _description: &StreamDescription) -> bool { + false + } + + /// Whether this operation supports sessions or not. + fn supports_sessions(&self) -> bool { + true + } + + /// The level of retryability the operation supports. + fn retryability(&self) -> Retryability { + Retryability::None + } + + /// Updates this operation as needed for a retry. + fn update_for_retry(&mut self) {} + + /// Returns a function handle to potentially override selection criteria based on server + /// topology. + fn override_criteria(&self) -> OverrideCriteriaFn { + |_, _| None + } + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + None + } + + /// The name of the server side command associated with this operation. + fn name(&self) -> &CStr { + Self::NAME + } +} + +impl Operation for T +where + T: Send + Sync, +{ + type O = T::O; + const NAME: &'static CStr = T::NAME; + fn build(&mut self, description: &StreamDescription) -> Result { + self.build(description) + } + fn extract_at_cluster_time(&self, response: &RawDocument) -> Result> { + self.extract_at_cluster_time(response) + } + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> BoxFuture<'a, Result> { + self.handle_response_async(response, context) + } + fn handle_error(&self, error: Error) -> Result { + self.handle_error(error) + } + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.selection_criteria() + } + fn is_acknowledged(&self) -> bool { + self.is_acknowledged() + } + fn write_concern(&self) -> Option<&WriteConcern> { + self.write_concern() + } + fn supports_read_concern(&self, description: &StreamDescription) -> bool { + self.supports_read_concern(description) + } + fn supports_sessions(&self) -> bool { + self.supports_sessions() + } + fn retryability(&self) -> Retryability { + self.retryability() + } + fn update_for_retry(&mut self) { + self.update_for_retry() + } + fn override_criteria(&self) -> OverrideCriteriaFn { + self.override_criteria() + } + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + self.pinned_connection() + } + fn name(&self) -> &CStr { + self.name() + } +} + +fn should_redact_body(body: &RawDocumentBuf) -> bool { + if let Some(Ok((command_name, _))) = body.into_iter().next() { + HELLO_COMMAND_NAMES.contains(command_name.to_lowercase().as_str()) + && body.get("speculativeAuthenticate").ok().flatten().is_some() + } else { + false + } +} + +impl Command { + pub(crate) fn should_redact(&self) -> bool { + let name = self.name.to_lowercase(); + REDACTED_COMMANDS.contains(name.as_str()) || should_redact_body(&self.body) + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + pub(crate) fn should_compress(&self) -> bool { + let name = self.name.to_lowercase(); + !REDACTED_COMMANDS.contains(name.as_str()) && !HELLO_COMMAND_NAMES.contains(name.as_str()) + } +} + +/// A response to a command with a body shaped deserialized to a `T`. +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CommandResponse { + pub(crate) ok: Bson, + + #[serde(rename = "$clusterTime")] + pub(crate) cluster_time: Option, + + #[serde(flatten)] + pub(crate) body: T, +} + +impl CommandResponse { + /// Whether the command succeeeded or not (i.e. if this response is ok: 1). + pub(crate) fn is_success(&self) -> bool { + bson_util::get_int(&self.ok) == Some(1) + } + + pub(crate) fn cluster_time(&self) -> Option<&ClusterTime> { + self.cluster_time.as_ref() + } +} + +/// A response body useful for deserializing command errors. +#[derive(Deserialize, Debug)] +pub(crate) struct CommandErrorBody { + #[serde(rename = "errorLabels")] + pub(crate) error_labels: Option>, + + #[serde(flatten)] + pub(crate) command_error: CommandError, +} + +impl From for Error { + fn from(command_error_response: CommandErrorBody) -> Error { + Error::new( + ErrorKind::Command(command_error_response.command_error), + command_error_response.error_labels, + ) + } +} + +/// Appends a serializable struct to the input document. The serializable struct MUST serialize to a +/// Document; otherwise, an error will be thrown. +pub(crate) fn append_options( + doc: &mut Document, + options: Option<&T>, +) -> Result<()> { + if let Some(options) = options { + let options_doc = crate::bson_compat::serialize_to_document(options)?; + doc.extend(options_doc); + } + Ok(()) +} + +pub(crate) fn append_options_to_raw_document( + doc: &mut RawDocumentBuf, + options: Option<&T>, +) -> Result<()> { + if let Some(options) = options { + let options_raw_doc = crate::bson_compat::serialize_to_raw_document_buf(options)?; + extend_raw_document_buf(doc, options_raw_doc)?; + } + Ok(()) +} + +#[derive(Deserialize, Debug)] +pub(crate) struct SingleWriteBody { + n: u64, +} + +/// Body of a write response that could possibly have a write concern error but not write errors. +#[derive(Debug, Deserialize, Default, Clone)] +pub(crate) struct WriteConcernOnlyBody { + #[serde(rename = "writeConcernError")] + write_concern_error: Option, + + #[serde(rename = "errorLabels")] + labels: Option>, +} + +impl WriteConcernOnlyBody { + fn validate(&self) -> Result<()> { + match self.write_concern_error { + Some(ref wc_error) => Err(Error::new( + ErrorKind::Write(WriteFailure::WriteConcernError(wc_error.clone())), + self.labels.clone(), + )), + None => Ok(()), + } + } +} + +#[derive(Debug)] +pub(crate) struct WriteResponseBody { + body: T, + write_errors: Option>, + write_concern_error: Option, + labels: Option>, +} + +impl<'de, T: Deserialize<'de>> Deserialize<'de> for WriteResponseBody { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use bson::serde_helpers::Utf8LossyDeserialization; + #[derive(Deserialize)] + struct Helper { + #[serde(flatten)] + body: T, + #[serde(rename = "writeErrors")] + write_errors: Option>>, + #[serde(rename = "writeConcernError")] + write_concern_error: Option>, + #[serde(rename = "errorLabels")] + labels: Option>, + } + let helper = Helper::deserialize(deserializer)?; + Ok(Self { + body: helper.body, + write_errors: helper.write_errors.map(|l| l.0), + write_concern_error: helper.write_concern_error.map(|l| l.0), + labels: helper.labels, + }) + } +} + +impl WriteResponseBody { + fn validate(&self) -> Result<()> { + if self.write_errors.is_none() && self.write_concern_error.is_none() { + return Ok(()); + }; + + let failure = InsertManyError { + write_errors: self.write_errors.clone(), + write_concern_error: self.write_concern_error.clone(), + inserted_ids: Default::default(), + }; + + Err(Error::new( + ErrorKind::InsertMany(failure), + self.labels.clone(), + )) + } +} + +impl Deref for WriteResponseBody { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.body + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct CursorBody { + cursor: CursorInfo, +} + +impl CursorBody { + fn extract_at_cluster_time(response: &RawDocument) -> Result> { + Ok(response + .get("cursor")? + .and_then(RawBsonRef::as_document) + .map(|d| d.get("atClusterTime")) + .transpose()? + .flatten() + .and_then(RawBsonRef::as_timestamp)) + } +} + +#[derive(Debug, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CursorInfo { + pub(crate) id: i64, + + pub(crate) ns: Namespace, + + pub(crate) first_batch: VecDeque, + + pub(crate) post_batch_resume_token: Option, +} + +/// Type used to deserialize just the first result from a cursor, if any. +#[derive(Debug, Clone)] +pub(crate) struct SingleCursorResult(Option); + +impl<'de, T> Deserialize<'de> for SingleCursorResult +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct FullCursorBody { + cursor: InteriorBody, + } + + #[derive(Deserialize)] + struct InteriorBody { + #[serde(rename = "firstBatch")] + first_batch: Vec, + } + + let mut full_body = FullCursorBody::deserialize(deserializer)?; + Ok(SingleCursorResult(full_body.cursor.first_batch.pop())) + } +} diff --git a/src/operation/abort_transaction.rs b/src/operation/abort_transaction.rs new file mode 100644 index 000000000..802633e4e --- /dev/null +++ b/src/operation/abort_transaction.rs @@ -0,0 +1,83 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + bson_util::append_ser, + client::session::TransactionPin, + cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, + error::Result, + operation::Retryability, + options::WriteConcern, + selection_criteria::SelectionCriteria, +}; + +use super::{ExecutionContext, OperationWithDefaults, WriteConcernOnlyBody}; + +pub(crate) struct AbortTransaction { + write_concern: Option, + pinned: Option, +} + +impl AbortTransaction { + pub(crate) fn new(write_concern: Option, pinned: Option) -> Self { + Self { + write_concern, + pinned, + } + } +} + +impl OperationWithDefaults for AbortTransaction { + type O = (); + + const NAME: &'static CStr = cstr!("abortTransaction"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: 1, + }; + if let Some(ref write_concern) = self.write_concern() { + if !write_concern.is_empty() { + append_ser(&mut body, cstr!("writeConcern"), write_concern)?; + } + } + + Ok(Command::new(Self::NAME, "admin", body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate() + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + match &self.pinned { + Some(TransactionPin::Mongos(s)) => Some(s), + _ => None, + } + } + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + match &self.pinned { + Some(TransactionPin::Connection(h)) => Some(h), + _ => None, + } + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.write_concern.as_ref() + } + + fn retryability(&self) -> Retryability { + Retryability::Write + } + + fn update_for_retry(&mut self) { + // The session must be "unpinned" before server selection for a retry. + self.pinned = None; + } +} diff --git a/src/operation/abort_transaction/mod.rs b/src/operation/abort_transaction/mod.rs deleted file mode 100644 index 7a08861dd..000000000 --- a/src/operation/abort_transaction/mod.rs +++ /dev/null @@ -1,87 +0,0 @@ -use bson::Document; - -use crate::{ - bson::doc, - client::session::TransactionPin, - cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::Retryability, - options::WriteConcern, - selection_criteria::SelectionCriteria, -}; - -use super::{OperationWithDefaults, WriteConcernOnlyBody}; - -pub(crate) struct AbortTransaction { - write_concern: Option, - pinned: Option, -} - -impl AbortTransaction { - pub(crate) fn new(write_concern: Option, pinned: Option) -> Self { - Self { - write_concern, - pinned, - } - } -} - -impl OperationWithDefaults for AbortTransaction { - type O = (); - type Command = Document; - - const NAME: &'static str = "abortTransaction"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: 1, - }; - if let Some(ref write_concern) = self.write_concern() { - if !write_concern.is_empty() { - body.insert("writeConcern", bson::to_bson(write_concern)?); - } - } - - Ok(Command::new( - Self::NAME.to_string(), - "admin".to_string(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate() - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - match &self.pinned { - Some(TransactionPin::Mongos(s)) => Some(s), - _ => None, - } - } - - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - match &self.pinned { - Some(TransactionPin::Connection(h)) => Some(h), - _ => None, - } - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.write_concern.as_ref() - } - - fn retryability(&self) -> Retryability { - Retryability::Write - } - - fn update_for_retry(&mut self) { - // The session must be "unpinned" before server selection for a retry. - self.pinned = None; - } -} diff --git a/src/operation/aggregate.rs b/src/operation/aggregate.rs new file mode 100644 index 000000000..c6d36dfc1 --- /dev/null +++ b/src/operation/aggregate.rs @@ -0,0 +1,205 @@ +pub(crate) mod change_stream; + +use crate::{ + bson::{doc, Bson, Document}, + bson_compat::{cstr, CStr}, + bson_util, + cmap::{Command, RawCommandResponse, StreamDescription}, + cursor::CursorSpecification, + error::Result, + operation::{append_options, Retryability}, + options::{AggregateOptions, ReadPreference, SelectionCriteria, WriteConcern}, + Namespace, +}; + +use super::{ + CursorBody, + ExecutionContext, + OperationWithDefaults, + WriteConcernOnlyBody, + SERVER_4_2_0_WIRE_VERSION, + SERVER_4_4_0_WIRE_VERSION, +}; + +#[derive(Debug)] +pub(crate) struct Aggregate { + target: AggregateTarget, + pipeline: Vec, + options: Option, +} + +impl Aggregate { + pub(crate) fn new( + target: impl Into, + pipeline: impl IntoIterator, + options: Option, + ) -> Self { + Self { + target: target.into(), + pipeline: pipeline.into_iter().collect(), + options, + } + } +} + +// IMPORTANT: If new method implementations are added here, make sure `ChangeStreamAggregate` has +// the equivalent delegations. +impl OperationWithDefaults for Aggregate { + type O = CursorSpecification; + + const NAME: &'static CStr = cstr!("aggregate"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = doc! { + crate::bson_compat::cstr_to_str(Self::NAME): self.target.to_bson(), + "pipeline": bson_util::to_bson_array(&self.pipeline), + "cursor": {} + }; + + append_options(&mut body, self.options.as_ref())?; + + if self.is_out_or_merge() { + if let Ok(cursor_doc) = body.get_document_mut("cursor") { + cursor_doc.remove("batchSize"); + } + } + + Ok(Command::new_read( + Self::NAME, + self.target.db_name(), + self.options.as_ref().and_then(|o| o.read_concern.clone()), + (&body).try_into()?, + )) + } + + fn extract_at_cluster_time( + &self, + response: &crate::bson::RawDocument, + ) -> Result> { + CursorBody::extract_at_cluster_time(response) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> Result { + let cursor_response: CursorBody = response.body()?; + + if self.is_out_or_merge() { + let wc_error_info = response.body::()?; + wc_error_info.validate()?; + }; + + let description = context.connection.stream_description()?; + + // The comment should only be propagated to getMore calls on 4.4+. + let comment = if description.max_wire_version.unwrap_or(0) < SERVER_4_4_0_WIRE_VERSION { + None + } else { + self.options.as_ref().and_then(|opts| opts.comment.clone()) + }; + + Ok(CursorSpecification::new( + cursor_response.cursor, + description.server_address.clone(), + self.options.as_ref().and_then(|opts| opts.batch_size), + self.options.as_ref().and_then(|opts| opts.max_await_time), + comment, + )) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.options + .as_ref() + .and_then(|opts| opts.selection_criteria.as_ref()) + } + + fn supports_read_concern(&self, description: &StreamDescription) -> bool { + // for aggregates that write, read concern is only supported in MongoDB 4.2+. + !self.is_out_or_merge() + || description.max_wire_version.unwrap_or(0) >= SERVER_4_2_0_WIRE_VERSION + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } + + fn retryability(&self) -> Retryability { + if self.is_out_or_merge() { + Retryability::None + } else { + Retryability::Read + } + } + + fn override_criteria(&self) -> super::OverrideCriteriaFn { + if !self.is_out_or_merge() { + return |_, _| None; + } + |criteria, topology| { + if criteria == &SelectionCriteria::ReadPreference(ReadPreference::Primary) + || topology.topology_type() == crate::TopologyType::LoadBalanced + { + return None; + } + for server in topology.servers.values() { + if let Ok(Some(v)) = server.max_wire_version() { + if v < super::SERVER_5_0_0_WIRE_VERSION { + return Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)); + } + } + } + None + } + } +} + +impl Aggregate { + /// Returns whether this is a $out or $merge aggregation operation. + fn is_out_or_merge(&self) -> bool { + self.pipeline + .last() + .map(|stage| { + let stage = bson_util::first_key(stage); + stage == Some("$out") || stage == Some("$merge") + }) + .unwrap_or(false) + } +} + +#[derive(Clone, Debug)] +pub(crate) enum AggregateTarget { + Database(String), + Collection(Namespace), +} + +impl AggregateTarget { + fn to_bson(&self) -> Bson { + match self { + AggregateTarget::Database(_) => Bson::Int32(1), + AggregateTarget::Collection(ref ns) => Bson::String(ns.coll.to_string()), + } + } + + fn db_name(&self) -> &str { + match self { + AggregateTarget::Database(ref s) => s.as_str(), + AggregateTarget::Collection(ref ns) => ns.db.as_str(), + } + } +} + +impl From for AggregateTarget { + fn from(ns: Namespace) -> Self { + AggregateTarget::Collection(ns) + } +} + +impl From for AggregateTarget { + fn from(db_name: String) -> Self { + AggregateTarget::Database(db_name) + } +} diff --git a/src/operation/aggregate/change_stream.rs b/src/operation/aggregate/change_stream.rs index 50a108cc3..8b8bab53c 100644 --- a/src/operation/aggregate/change_stream.rs +++ b/src/operation/aggregate/change_stream.rs @@ -4,7 +4,7 @@ use crate::{ cmap::{Command, RawCommandResponse, StreamDescription}, cursor::CursorSpecification, error::Result, - operation::{append_options, OperationWithDefaults, Retryability}, + operation::{append_options, ExecutionContext, OperationWithDefaults, Retryability}, options::{ChangeStreamOptions, SelectionCriteria, WriteConcern}, }; @@ -41,9 +41,8 @@ impl ChangeStreamAggregate { impl OperationWithDefaults for ChangeStreamAggregate { type O = (CursorSpecification, ChangeStreamData); - type Command = Document; - const NAME: &'static str = "aggregate"; + const NAME: &'static crate::bson_compat::CStr = Aggregate::NAME; fn build(&mut self, description: &StreamDescription) -> Result { if let Some(data) = &mut self.resume_data { @@ -62,7 +61,7 @@ impl OperationWithDefaults for ChangeStreamAggregate { .start_at_operation_time .as_ref() .or(data.initial_operation_time.as_ref()); - if saved_time.is_some() && description.max_wire_version.map_or(false, |v| v >= 7) { + if saved_time.is_some() && description.max_wire_version.is_some_and(|v| v >= 7) { new_opts.start_at_operation_time = saved_time.cloned(); } } @@ -77,21 +76,27 @@ impl OperationWithDefaults for ChangeStreamAggregate { fn extract_at_cluster_time( &self, - response: &bson::RawDocument, - ) -> Result> { + response: &crate::bson::RawDocument, + ) -> Result> { self.inner.extract_at_cluster_time(response) } - fn handle_response( - &self, + fn handle_response<'a>( + &'a self, response: RawCommandResponse, - description: &StreamDescription, + mut context: ExecutionContext<'a>, ) -> Result { let op_time = response .raw_body() .get("operationTime")? - .and_then(bson::RawBsonRef::as_timestamp); - let spec = self.inner.handle_response(response, description)?; + .and_then(crate::bson::RawBsonRef::as_timestamp); + + let inner_context = ExecutionContext { + connection: context.connection, + session: context.session.as_deref_mut(), + effective_criteria: context.effective_criteria, + }; + let spec = self.inner.handle_response(response, inner_context)?; let mut data = ChangeStreamData { resume_token: ResumeToken::initial(self.args.options.as_ref(), &spec), @@ -102,8 +107,10 @@ impl OperationWithDefaults for ChangeStreamAggregate { && o.resume_after.is_none() && o.start_after.is_none() }; - if self.args.options.as_ref().map_or(true, has_no_time) - && description.max_wire_version.map_or(false, |v| v >= 7) + + let description = context.connection.stream_description()?; + if self.args.options.as_ref().is_none_or(has_no_time) + && description.max_wire_version.is_some_and(|v| v >= 7) && spec.initial_buffer.is_empty() && spec.post_batch_resume_token.is_none() { diff --git a/src/operation/aggregate/mod.rs b/src/operation/aggregate/mod.rs deleted file mode 100644 index 0b70bef5c..000000000 --- a/src/operation/aggregate/mod.rs +++ /dev/null @@ -1,202 +0,0 @@ -mod change_stream; - -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Bson, Document}, - bson_util, - cmap::{Command, RawCommandResponse, StreamDescription}, - cursor::CursorSpecification, - error::Result, - operation::{append_options, remove_empty_write_concern, Retryability}, - options::{AggregateOptions, SelectionCriteria, WriteConcern}, - Namespace, -}; - -use super::{ - CursorBody, - OperationWithDefaults, - WriteConcernOnlyBody, - SERVER_4_2_0_WIRE_VERSION, - SERVER_4_4_0_WIRE_VERSION, -}; - -pub(crate) use change_stream::ChangeStreamAggregate; - -#[derive(Debug)] -pub(crate) struct Aggregate { - target: AggregateTarget, - pipeline: Vec, - options: Option, -} - -impl Aggregate { - #[cfg(test)] - fn empty() -> Self { - Self::new(Namespace::empty(), Vec::new(), None) - } - - pub(crate) fn new( - target: impl Into, - pipeline: impl IntoIterator, - mut options: Option, - ) -> Self { - if let Some(ref mut options) = options { - if let Some(ref comment) = options.comment { - if options.comment_bson.is_none() { - options.comment_bson = Some(comment.clone().into()); - } - } - } - - Self { - target: target.into(), - pipeline: pipeline.into_iter().collect(), - options, - } - } -} - -// IMPORTANT: If new method implementations are added here, make sure `ChangeStreamAggregate` has -// the equivalent delegations. -impl OperationWithDefaults for Aggregate { - type O = CursorSpecification; - type Command = Document; - - const NAME: &'static str = "aggregate"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.target.to_bson(), - "pipeline": bson_util::to_bson_array(&self.pipeline), - "cursor": {} - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - if self.is_out_or_merge() { - if let Ok(cursor_doc) = body.get_document_mut("cursor") { - cursor_doc.remove("batchSize"); - } - } - - Ok(Command::new_read( - Self::NAME.to_string(), - self.target.db_name().to_string(), - self.options.as_ref().and_then(|o| o.read_concern.clone()), - body, - )) - } - - fn extract_at_cluster_time( - &self, - response: &bson::RawDocument, - ) -> Result> { - CursorBody::extract_at_cluster_time(response) - } - - fn handle_response( - &self, - response: RawCommandResponse, - description: &StreamDescription, - ) -> Result { - let cursor_response: CursorBody = response.body()?; - - if self.is_out_or_merge() { - let wc_error_info = response.body::()?; - wc_error_info.validate()?; - }; - - // The comment should only be propagated to getMore calls on 4.4+. - let comment = if description.max_wire_version.unwrap_or(0) < SERVER_4_4_0_WIRE_VERSION { - None - } else { - self.options - .as_ref() - .and_then(|opts| opts.comment_bson.clone()) - }; - - Ok(CursorSpecification::new( - cursor_response.cursor, - description.server_address.clone(), - self.options.as_ref().and_then(|opts| opts.batch_size), - self.options.as_ref().and_then(|opts| opts.max_await_time), - comment, - )) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.options - .as_ref() - .and_then(|opts| opts.selection_criteria.as_ref()) - } - - fn supports_read_concern(&self, description: &StreamDescription) -> bool { - // for aggregates that write, read concern is only supported in MongoDB 4.2+. - !self.is_out_or_merge() - || description.max_wire_version.unwrap_or(0) >= SERVER_4_2_0_WIRE_VERSION - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } - - fn retryability(&self) -> Retryability { - if self.is_out_or_merge() { - Retryability::None - } else { - Retryability::Read - } - } -} - -impl Aggregate { - /// Returns whether this is a $out or $merge aggregation operation. - fn is_out_or_merge(&self) -> bool { - self.pipeline - .last() - .map(|stage| { - let stage = bson_util::first_key(stage); - stage == Some("$out") || stage == Some("$merge") - }) - .unwrap_or(false) - } -} - -#[derive(Clone, Debug)] -pub(crate) enum AggregateTarget { - Database(String), - Collection(Namespace), -} - -impl AggregateTarget { - fn to_bson(&self) -> Bson { - match self { - AggregateTarget::Database(_) => Bson::Int32(1), - AggregateTarget::Collection(ref ns) => Bson::String(ns.coll.to_string()), - } - } - - fn db_name(&self) -> &str { - match self { - AggregateTarget::Database(ref s) => s.as_str(), - AggregateTarget::Collection(ref ns) => ns.db.as_str(), - } - } -} - -impl From for AggregateTarget { - fn from(ns: Namespace) -> Self { - AggregateTarget::Collection(ns) - } -} - -impl From for AggregateTarget { - fn from(db_name: String) -> Self { - AggregateTarget::Database(db_name) - } -} diff --git a/src/operation/aggregate/test.rs b/src/operation/aggregate/test.rs deleted file mode 100644 index 54c782a33..000000000 --- a/src/operation/aggregate/test.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::time::Duration; - -use super::AggregateTarget; -use crate::{ - bson::{doc, Document}, - bson_util, - cmap::StreamDescription, - concern::{ReadConcern, ReadConcernLevel}, - error::{ErrorKind, WriteFailure}, - operation::{ - test::{self, handle_response_test}, - Aggregate, - Operation, - }, - options::{AggregateOptions, Hint}, - Namespace, -}; - -fn build_test( - target: impl Into, - pipeline: Vec, - options: Option, - mut expected_body: Document, -) { - let target = target.into(); - - let mut aggregate = Aggregate::new(target.clone(), pipeline, options); - - let cmd = aggregate.build(&StreamDescription::new_testing()).unwrap(); - - assert_eq!(cmd.name.as_str(), "aggregate"); - assert_eq!(cmd.target_db.as_str(), target.db_name()); - - let cmd_bytes = aggregate.serialize_command(cmd).unwrap(); - let mut cmd_doc = bson::from_slice(&cmd_bytes).unwrap(); - - bson_util::sort_document(&mut expected_body); - bson_util::sort_document(&mut cmd_doc); - - assert_eq!(cmd_doc, expected_body); -} - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let pipeline = vec![doc! { "$match": { "x": 3 }}]; - - let options = AggregateOptions::builder() - .hint(Hint::Keys(doc! { "x": 1, "y": 2 })) - .bypass_document_validation(true) - .read_concern(ReadConcern::from(ReadConcernLevel::Available)) - .build(); - - let expected_body = doc! { - "aggregate": "test_coll", - "$db": "test_db", - "pipeline": bson_util::to_bson_array(&pipeline), - "cursor": {}, - "hint": { - "x": 1, - "y": 2, - }, - "bypassDocumentValidation": true, - "readConcern": { - "level": "available" - }, - }; - - build_test(ns, pipeline, Some(options), expected_body); -} - -#[test] -fn build_batch_size() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let pipeline = Vec::new(); - - let mut expected_body = doc! { - "aggregate": "test_coll", - "$db": "test_db", - "pipeline": [], - "cursor": {}, - }; - - build_test(ns.clone(), pipeline.clone(), None, expected_body.clone()); - - build_test( - ns.clone(), - pipeline.clone(), - Some(AggregateOptions::default()), - expected_body.clone(), - ); - - let batch_size_options = AggregateOptions::builder().batch_size(5).build(); - expected_body.insert("cursor", doc! { "batchSize": 5 }); - build_test( - ns.clone(), - pipeline, - Some(batch_size_options.clone()), - expected_body.clone(), - ); - - let out_pipeline = vec![doc! { "$out": "cat" }]; - expected_body.insert("cursor", Document::new()); - expected_body.insert("pipeline", bson_util::to_bson_array(&out_pipeline)); - build_test( - ns.clone(), - out_pipeline, - Some(batch_size_options.clone()), - expected_body.clone(), - ); - - let merge_pipeline = vec![doc! { - "$merge": { - "into": "out", - } - }]; - expected_body.insert("pipeline", bson_util::to_bson_array(&merge_pipeline)); - build_test(ns, merge_pipeline, Some(batch_size_options), expected_body); -} - -#[test] -fn build_target() { - let pipeline = Vec::new(); - - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let expected_body = doc! { - "aggregate": "test_coll", - "$db": "test_db", - "pipeline": [], - "cursor": {}, - }; - build_test(ns.clone(), pipeline.clone(), None, expected_body); - - let expected_body = doc! { - "aggregate": 1, - "$db": "test_db", - "pipeline": [], - "cursor": {} - }; - build_test(ns.db, pipeline, None, expected_body); -} - -#[test] -fn build_max_await_time() { - let options = AggregateOptions::builder() - .max_await_time(Duration::from_millis(5)) - .max_time(Duration::from_millis(10)) - .build(); - - let body = doc! { - "aggregate": 1, - "$db": "test_db", - "cursor": {}, - "maxTimeMS": 10i32, - "pipeline": [] - }; - - build_test("test_db".to_string(), Vec::new(), Some(options), body); -} - -#[test] -fn op_selection_criteria() { - test::op_selection_criteria(|selection_criteria| { - let options = AggregateOptions { - selection_criteria, - ..Default::default() - }; - Aggregate::new("".to_string(), Vec::new(), Some(options)) - }); -} - -#[test] -fn handle_max_await_time() { - let response = doc! { - "ok": 1, - "cursor": { - "id": 123, - "ns": "a.b", - "firstBatch": [] - } - }; - - let aggregate = Aggregate::empty(); - let spec = handle_response_test(&aggregate, response.clone()).unwrap(); - assert!(spec.max_time().is_none()); - - let max_await = Duration::from_millis(123); - let options = AggregateOptions::builder() - .max_await_time(max_await) - .build(); - let aggregate = Aggregate::new(Namespace::empty(), Vec::new(), Some(options)); - let spec = handle_response_test(&aggregate, response).unwrap(); - assert_eq!(spec.max_time(), Some(max_await)); -} - -#[test] -fn handle_write_concern_error() { - let response = doc! { - "ok": 1.0, - "cursor": { - "id": 0, - "ns": "test.test", - "firstBatch": [], - }, - "writeConcernError": { - "code": 64, - "codeName": "WriteConcernFailed", - "errmsg": "Waiting for replication timed out", - "errInfo": { - "wtimeout": true - } - } - }; - - let aggregate = Aggregate::new( - Namespace::empty(), - vec![doc! { "$merge": { "into": "a" } }], - None, - ); - - let error = handle_response_test(&aggregate, response).unwrap_err(); - match *error.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(_)) => {} - ref e => panic!("should have gotten WriteConcernError, got {:?} instead", e), - } -} - -#[test] -fn handle_invalid_response() { - let aggregate = Aggregate::empty(); - - let garbled = doc! { "asdfasf": "ASdfasdf" }; - handle_response_test(&aggregate, garbled).unwrap_err(); - - let missing_cursor_field = doc! { - "ok": 1.0, - "cursor": { - "ns": "test.test", - "firstBatch": [], - } - }; - handle_response_test(&aggregate, missing_cursor_field).unwrap_err(); -} diff --git a/src/operation/bulk_write.rs b/src/operation/bulk_write.rs new file mode 100644 index 000000000..c5cb933dd --- /dev/null +++ b/src/operation/bulk_write.rs @@ -0,0 +1,438 @@ +mod server_responses; + +use std::{collections::HashMap, marker::PhantomData}; + +use futures_core::TryStream; +use futures_util::{FutureExt, TryStreamExt}; + +use crate::{ + bson::{rawdoc, Bson, RawDocumentBuf}, + bson_compat::{cstr, CStr}, + bson_util::{self, extend_raw_document_buf}, + checked::Checked, + cmap::{Command, RawCommandResponse, StreamDescription}, + cursor::CursorSpecification, + error::{BulkWriteError, Error, ErrorKind, Result}, + operation::{run_command::RunCommand, GetMore, OperationWithDefaults}, + options::{BulkWriteOptions, OperationType, WriteModel}, + results::{BulkWriteResult, DeleteResult, InsertOneResult, UpdateResult}, + BoxFuture, + Client, + Cursor, + Namespace, + SessionCursor, +}; + +use super::{ + ExecutionContext, + Retryability, + WriteResponseBody, + OP_MSG_OVERHEAD_BYTES, + SERVER_8_0_0_WIRE_VERSION, +}; + +use server_responses::*; + +pub(crate) struct BulkWrite<'a, R> +where + R: BulkWriteResult, +{ + client: Client, + models: &'a [WriteModel], + offset: usize, + options: Option<&'a BulkWriteOptions>, + /// The _ids of the inserted documents. This value is populated in `build`. + inserted_ids: HashMap, + /// The number of writes that were sent to the server. This value is populated in `build`. + pub(crate) n_attempted: usize, + _phantom: PhantomData, +} + +impl<'a, R> BulkWrite<'a, R> +where + R: BulkWriteResult, +{ + pub(crate) fn new( + client: Client, + models: &'a [WriteModel], + offset: usize, + options: Option<&'a BulkWriteOptions>, + ) -> BulkWrite<'a, R> { + Self { + client, + models, + offset, + options, + n_attempted: 0, + inserted_ids: HashMap::new(), + _phantom: PhantomData, + } + } + + async fn iterate_results_cursor( + &self, + mut stream: impl TryStream + Unpin, + result: &mut impl BulkWriteResult, + error: &mut BulkWriteError, + ) -> Result<()> { + while let Some(response) = stream.try_next().await? { + self.handle_individual_response(response, result, error)?; + } + Ok(()) + } + + async fn do_get_mores( + &self, + context: &mut ExecutionContext<'_>, + cursor_specification: CursorSpecification, + result: &mut impl BulkWriteResult, + error: &mut BulkWriteError, + ) -> Result<()> { + let mut responses = cursor_specification.initial_buffer; + let mut more_responses = cursor_specification.info.id != 0; + let mut namespace = cursor_specification.info.ns.clone(); + loop { + for response_document in &responses { + let response: SingleOperationResponse = + crate::bson_compat::deserialize_from_slice(response_document.as_bytes())?; + self.handle_individual_response(response, result, error)?; + } + + if !more_responses { + return Ok(()); + } + + let mut get_more = GetMore::new(cursor_specification.info.clone(), None); + let txn_number = context + .session + .as_mut() + .and_then(|s| s.get_txn_number_for_operation(get_more.retryability())); + let get_more_result = self + .client + .execute_operation_on_connection( + &mut get_more, + context.connection, + &mut context.session, + txn_number, + Retryability::None, + context.effective_criteria.clone(), + ) + .await; + + let get_more_response = match get_more_result { + Ok(response) => response, + Err(error) => { + if !error.is_network_error() { + let kill_cursors = rawdoc! { + "killCursors": namespace.db.clone(), + "cursors": [cursor_specification.info.id], + }; + let mut run_command = + RunCommand::new(namespace.db.clone(), kill_cursors, None, None); + let _ = self + .client + .execute_operation_on_connection( + &mut run_command, + context.connection, + &mut context.session, + txn_number, + Retryability::None, + context.effective_criteria.clone(), + ) + .await; + } + return Err(error); + } + }; + + responses = get_more_response.batch; + more_responses = get_more_response.id != 0; + namespace = get_more_response.ns; + } + } + + fn handle_individual_response( + &self, + response: SingleOperationResponse, + result: &mut impl BulkWriteResult, + error: &mut BulkWriteError, + ) -> Result<()> { + let index = response.index + self.offset; + match response.result { + SingleOperationResult::Success { + n, + n_modified, + upserted, + } => { + let model = self.get_model(response.index)?; + match model.operation_type() { + OperationType::Insert => { + let inserted_id = self.get_inserted_id(index)?; + let insert_result = InsertOneResult { inserted_id }; + result.add_insert_result(index, insert_result); + } + OperationType::Update => { + let modified_count = + n_modified.ok_or_else(|| ErrorKind::InvalidResponse { + message: "nModified value not returned for update bulkWrite \ + operation" + .into(), + })?; + let update_result = UpdateResult { + matched_count: n, + modified_count, + upserted_id: upserted.map(|upserted| upserted.id), + }; + result.add_update_result(index, update_result); + } + OperationType::Delete => { + let delete_result = DeleteResult { deleted_count: n }; + result.add_delete_result(index, delete_result); + } + } + } + SingleOperationResult::Error(write_error) => { + error.write_errors.insert(index, write_error); + } + } + Ok(()) + } + + fn get_model(&self, index: usize) -> Result<&WriteModel> { + self.models.get(index).ok_or_else(|| { + ErrorKind::InvalidResponse { + message: format!("invalid operation index returned from bulkWrite: {}", index), + } + .into() + }) + } + + fn get_inserted_id(&self, index: usize) -> Result { + match self.inserted_ids.get(&index) { + Some(inserted_id) => Ok(inserted_id.clone()), + None => Err(ErrorKind::InvalidResponse { + message: format!("invalid index returned for insert operation: {}", index), + } + .into()), + } + } + + fn ordered(&self) -> bool { + self.options + .and_then(|options| options.ordered) + .unwrap_or(true) + } +} + +/// A helper struct for tracking namespace information. +struct NamespaceInfo<'a> { + namespaces: Vec, + // Cache the namespaces and their indexes to avoid traversing the namespaces array each time a + // namespace is looked up or added. + cache: HashMap<&'a Namespace, usize>, +} + +impl<'a> NamespaceInfo<'a> { + fn new() -> Self { + Self { + namespaces: Vec::new(), + cache: HashMap::new(), + } + } + + /// Gets the index for the given namespace in the nsInfo list, adding it to the list if it is + /// not already present. + fn get_index(&mut self, namespace: &'a Namespace) -> (usize, usize) { + match self.cache.get(namespace) { + Some(index) => (*index, 0), + None => { + let namespace_doc = rawdoc! { "ns": namespace.to_string() }; + let length_added = namespace_doc.as_bytes().len(); + self.namespaces.push(namespace_doc); + let next_index = self.cache.len(); + self.cache.insert(namespace, next_index); + (next_index, length_added) + } + } + } +} + +impl OperationWithDefaults for BulkWrite<'_, R> +where + R: BulkWriteResult, +{ + type O = R; + + const NAME: &'static CStr = cstr!("bulkWrite"); + + fn build(&mut self, description: &StreamDescription) -> Result { + if description.max_wire_version.unwrap_or(0) < SERVER_8_0_0_WIRE_VERSION { + return Err(ErrorKind::IncompatibleServer { + message: "the bulk write feature is only supported on MongoDB 8.0+".to_string(), + } + .into()); + } + + let max_message_size: usize = + Checked::new(description.max_message_size_bytes).try_into()?; + let max_operations: usize = Checked::new(description.max_write_batch_size).try_into()?; + + let mut command_body = rawdoc! { Self::NAME: 1 }; + let mut options = match self.options { + Some(options) => crate::bson_compat::serialize_to_raw_document_buf(options), + None => crate::bson_compat::serialize_to_raw_document_buf(&BulkWriteOptions::default()), + }?; + options.append(cstr!("errorsOnly"), R::errors_only()); + bson_util::extend_raw_document_buf(&mut command_body, options)?; + + let max_document_sequences_size: usize = (Checked::new(max_message_size) + - OP_MSG_OVERHEAD_BYTES + - command_body.as_bytes().len()) + .try_into()?; + + let mut namespace_info = NamespaceInfo::new(); + let mut ops = Vec::new(); + let mut current_size = Checked::new(0); + for (i, model) in self.models.iter().take(max_operations).enumerate() { + let (namespace_index, namespace_size) = namespace_info.get_index(model.namespace()); + + let operation_namespace_index: i32 = Checked::new(namespace_index).try_into()?; + let mut operation = rawdoc! { model.operation_name(): operation_namespace_index }; + let (model_doc, inserted_id) = model.get_ops_document_contents()?; + extend_raw_document_buf(&mut operation, model_doc)?; + + let operation_size = operation.as_bytes().len(); + + current_size += namespace_size + operation_size; + if current_size.get()? > max_document_sequences_size { + // Remove the namespace doc from the list if one was added for this operation. + if namespace_size > 0 { + let last_index = namespace_info.namespaces.len() - 1; + namespace_info.namespaces.remove(last_index); + } + break; + } + + if let Some(inserted_id) = inserted_id { + self.inserted_ids.insert(i, inserted_id); + } + ops.push(operation); + } + + if ops.is_empty() { + return Err(ErrorKind::InvalidArgument { + message: format!( + "operation at index {} exceeds the maximum message size ({} bytes)", + self.offset, max_message_size + ), + } + .into()); + } + + self.n_attempted = ops.len(); + + let mut command = Command::new(Self::NAME, "admin", command_body); + command.add_document_sequence("nsInfo", namespace_info.namespaces); + command.add_document_sequence("ops", ops); + Ok(command) + } + + fn handle_response_async<'b>( + &'b self, + response: RawCommandResponse, + mut context: ExecutionContext<'b>, + ) -> BoxFuture<'b, Result> { + async move { + let response: WriteResponseBody = response.body()?; + let n_errors: usize = Checked::new(response.summary.n_errors).try_into()?; + + let mut error: BulkWriteError = Default::default(); + let mut result: R = Default::default(); + + result.populate_summary_info( + response.summary.n_inserted, + response.summary.n_matched, + response.summary.n_modified, + response.summary.n_upserted, + response.summary.n_deleted, + ); + + if let Some(write_concern_error) = response.write_concern_error { + error.write_concern_errors.push(write_concern_error); + } + + let specification = CursorSpecification::new( + response.body.cursor, + context + .connection + .stream_description()? + .server_address + .clone(), + None, + None, + self.options.and_then(|options| options.comment.clone()), + ); + + let iteration_result = if self.client.is_load_balanced() { + // Using a cursor with a pinned connection is not feasible here; see RUST-2131 for + // more details. + self.do_get_mores(&mut context, specification, &mut result, &mut error) + .await + } else { + match context.session { + Some(session) => { + let mut session_cursor = + SessionCursor::new(self.client.clone(), specification, None); + self.iterate_results_cursor( + session_cursor.stream(session), + &mut result, + &mut error, + ) + .await + } + None => { + let cursor = Cursor::new(self.client.clone(), specification, None, None); + self.iterate_results_cursor(cursor, &mut result, &mut error) + .await + } + } + }; + + if iteration_result.is_ok() + && error.write_errors.is_empty() + && error.write_concern_errors.is_empty() + { + Ok(result) + } else { + // The partial result should only be populated if the response indicates that at + // least one write succeeded. + let write_succeeded = if self.ordered() { + error + .write_errors + .iter() + .next() + .map(|(index, _)| *index != self.offset) + .unwrap_or(true) + } else { + n_errors < self.n_attempted + }; + if write_succeeded { + error.partial_result = Some(result.into_partial_result()); + } + + let error = Error::new(ErrorKind::BulkWrite(error), response.labels) + .with_source(iteration_result.err()); + Err(error) + } + } + .boxed() + } + + fn retryability(&self) -> Retryability { + if self.models.iter().any(|model| model.multi() == Some(true)) { + Retryability::None + } else { + Retryability::Write + } + } +} diff --git a/src/operation/bulk_write/server_responses.rs b/src/operation/bulk_write/server_responses.rs new file mode 100644 index 000000000..287772cc4 --- /dev/null +++ b/src/operation/bulk_write/server_responses.rs @@ -0,0 +1,54 @@ +use serde::Deserialize; + +use crate::{bson::Bson, error::WriteError, operation::CursorInfo}; + +/// The top-level response to the bulkWrite command. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct Response { + pub(super) cursor: CursorInfo, + #[serde(flatten)] + pub(super) summary: SummaryInfo, +} + +/// The summary information contained within the top-level response to the bulkWrite command. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct SummaryInfo { + pub(super) n_errors: i64, + pub(super) n_inserted: i64, + pub(super) n_matched: i64, + pub(super) n_modified: i64, + pub(super) n_upserted: i64, + pub(super) n_deleted: i64, +} + +/// The structure of the response for a single operation within the results cursor. +#[derive(Debug, Deserialize)] +pub(super) struct SingleOperationResponse { + #[serde(rename = "idx")] + pub(super) index: usize, + #[serde(flatten)] + pub(super) result: SingleOperationResult, +} + +/// The structure of the non-index fields for a single operation within the results cursor. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub(super) enum SingleOperationResult { + // This variant must be listed first for proper deserialization. + Error(WriteError), + #[serde(rename_all = "camelCase")] + Success { + n: u64, + n_modified: Option, + upserted: Option, + }, +} + +/// The structure of the inserted ID for an upserted document. +#[derive(Debug, Deserialize)] +pub(super) struct UpsertedId { + #[serde(rename = "_id")] + pub(super) id: Bson, +} diff --git a/src/operation/commit_transaction.rs b/src/operation/commit_transaction.rs new file mode 100644 index 000000000..ccb417cfc --- /dev/null +++ b/src/operation/commit_transaction.rs @@ -0,0 +1,83 @@ +use std::time::Duration; + +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::Result, + operation::{append_options_to_raw_document, OperationWithDefaults, Retryability}, + options::{Acknowledgment, TransactionOptions, WriteConcern}, +}; + +use super::{ExecutionContext, WriteConcernOnlyBody}; + +pub(crate) struct CommitTransaction { + options: Option, +} + +impl CommitTransaction { + pub(crate) fn new(options: Option) -> Self { + Self { options } + } +} + +impl OperationWithDefaults for CommitTransaction { + type O = (); + + const NAME: &'static CStr = cstr!("commitTransaction"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: 1, + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, "admin", body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate() + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } + + fn retryability(&self) -> Retryability { + Retryability::Write + } + + // Updates the write concern to use w: majority and a w_timeout of 10000 if w_timeout is not + // already set. The write concern on a commitTransaction command should be updated if a + // commit is being retried internally or by the user. + fn update_for_retry(&mut self) { + let options = self + .options + .get_or_insert_with(|| TransactionOptions::builder().build()); + match &mut options.write_concern { + Some(write_concern) => { + write_concern.w = Some(Acknowledgment::Majority); + if write_concern.w_timeout.is_none() { + write_concern.w_timeout = Some(Duration::from_millis(10000)); + } + } + None => { + options.write_concern = Some( + WriteConcern::builder() + .w(Acknowledgment::Majority) + .w_timeout(Duration::from_millis(10000)) + .build(), + ); + } + } + } +} diff --git a/src/operation/commit_transaction/mod.rs b/src/operation/commit_transaction/mod.rs deleted file mode 100644 index 3c3d455d6..000000000 --- a/src/operation/commit_transaction/mod.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::time::Duration; - -use bson::{doc, Document}; - -use crate::{ - cmap::{Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::{append_options, remove_empty_write_concern, OperationWithDefaults, Retryability}, - options::{Acknowledgment, TransactionOptions, WriteConcern}, -}; - -use super::WriteConcernOnlyBody; - -pub(crate) struct CommitTransaction { - options: Option, -} - -impl CommitTransaction { - pub(crate) fn new(options: Option) -> Self { - Self { options } - } -} - -impl OperationWithDefaults for CommitTransaction { - type O = (); - type Command = Document; - - const NAME: &'static str = "commitTransaction"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: 1, - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - "admin".to_string(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate() - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } - - fn retryability(&self) -> Retryability { - Retryability::Write - } - - // Updates the write concern to use w: majority and a w_timeout of 10000 if w_timeout is not - // already set. The write concern on a commitTransaction command should be updated if a - // commit is being retried internally or by the user. - fn update_for_retry(&mut self) { - let options = self - .options - .get_or_insert_with(|| TransactionOptions::builder().build()); - match &mut options.write_concern { - Some(write_concern) => { - write_concern.w = Some(Acknowledgment::Majority); - if write_concern.w_timeout.is_none() { - write_concern.w_timeout = Some(Duration::from_millis(10000)); - } - } - None => { - options.write_concern = Some( - WriteConcern::builder() - .w(Acknowledgment::Majority) - .w_timeout(Duration::from_millis(10000)) - .build(), - ); - } - } - } -} diff --git a/src/operation/count.rs b/src/operation/count.rs new file mode 100644 index 000000000..bf3757611 --- /dev/null +++ b/src/operation/count.rs @@ -0,0 +1,83 @@ +use crate::bson::rawdoc; +use serde::Deserialize; + +use crate::{ + bson::doc, + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + coll::{options::EstimatedDocumentCountOptions, Namespace}, + error::{Error, Result}, + operation::{OperationWithDefaults, Retryability}, + selection_criteria::SelectionCriteria, +}; + +use super::{append_options_to_raw_document, ExecutionContext}; + +pub(crate) struct Count { + ns: Namespace, + options: Option, +} + +impl Count { + pub fn new(ns: Namespace, options: Option) -> Self { + Count { ns, options } + } +} + +impl OperationWithDefaults for Count { + type O = u64; + + const NAME: &'static CStr = cstr!("count"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new_read( + Self::NAME, + &self.ns.db, + self.options.as_ref().and_then(|o| o.read_concern.clone()), + body, + )) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response_body: ResponseBody = response.body()?; + Ok(response_body.n) + } + + fn handle_error(&self, error: Error) -> Result { + if error.is_ns_not_found() { + Ok(0) + } else { + Err(error) + } + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + if let Some(ref options) = self.options { + return options.selection_criteria.as_ref(); + } + None + } + + fn supports_read_concern(&self, _description: &StreamDescription) -> bool { + true + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct ResponseBody { + n: u64, +} diff --git a/src/operation/count/mod.rs b/src/operation/count/mod.rs deleted file mode 100644 index 15c0f5fe5..000000000 --- a/src/operation/count/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::Document; -use serde::Deserialize; - -use crate::{ - bson::doc, - cmap::{Command, RawCommandResponse, StreamDescription}, - coll::{options::EstimatedDocumentCountOptions, Namespace}, - error::{Error, Result}, - operation::{append_options, OperationWithDefaults, Retryability}, - selection_criteria::SelectionCriteria, -}; - -pub(crate) struct Count { - ns: Namespace, - options: Option, -} - -impl Count { - pub fn new(ns: Namespace, options: Option) -> Self { - Count { ns, options } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - Count { - ns: Namespace { - db: String::new(), - coll: String::new(), - }, - options: None, - } - } -} - -impl OperationWithDefaults for Count { - type O = u64; - type Command = Document; - - const NAME: &'static str = "count"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - }; - - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new_read( - Self::NAME.to_string(), - self.ns.db.clone(), - self.options.as_ref().and_then(|o| o.read_concern.clone()), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response_body: ResponseBody = response.body()?; - Ok(response_body.n) - } - - fn handle_error(&self, error: Error) -> Result { - if error.is_ns_not_found() { - Ok(0) - } else { - Err(error) - } - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - if let Some(ref options) = self.options { - return options.selection_criteria.as_ref(); - } - None - } - - fn supports_read_concern(&self, _description: &StreamDescription) -> bool { - true - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct ResponseBody { - n: u64, -} diff --git a/src/operation/count/test.rs b/src/operation/count/test.rs deleted file mode 100644 index 22bffd4bd..000000000 --- a/src/operation/count/test.rs +++ /dev/null @@ -1,97 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::doc, - bson_util, - cmap::StreamDescription, - coll::{options::EstimatedDocumentCountOptions, Namespace}, - concern::ReadConcern, - operation::{ - test::{self, handle_response_test}, - Count, - Operation, - }, - options::ReadConcernLevel, -}; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut count_op = Count::new(ns, None); - let count_command = count_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - assert_eq!( - count_command.body, - doc! { - "count": "test_coll", - } - ); - assert_eq!(count_command.target_db, "test_db"); -} - -#[test] -fn build_with_options() { - let read_concern: ReadConcern = ReadConcernLevel::Local.into(); - let max_time = Duration::from_millis(2_u64); - let options: EstimatedDocumentCountOptions = EstimatedDocumentCountOptions::builder() - .max_time(max_time) - .read_concern(read_concern.clone()) - .build(); - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut count_op = Count::new(ns, Some(options)); - let count_command = count_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - - assert_eq!(count_command.target_db, "test_db"); - - let mut expected_body = doc! { - "count": "test_coll", - "$db": "test_db", - "maxTimeMS": max_time.as_millis() as i32, - "readConcern": doc! {"level": read_concern.level.as_str().to_string() }, - }; - - let cmd_bytes = count_op.serialize_command(count_command).unwrap(); - let mut cmd_doc = bson::from_slice(&cmd_bytes).unwrap(); - - bson_util::sort_document(&mut cmd_doc); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd_doc, expected_body); -} - -#[test] -fn op_selection_criteria() { - test::op_selection_criteria(|selection_criteria| { - let options = EstimatedDocumentCountOptions { - selection_criteria, - ..Default::default() - }; - Count::new(Namespace::empty(), Some(options)) - }); -} - -#[test] -fn handle_success() { - let count_op = Count::empty(); - - let n = 26; - let response = doc! { "ok": 1.0, "n": n as i32 }; - - let actual_values = handle_response_test(&count_op, response).unwrap(); - assert_eq!(actual_values, n); -} - -#[test] -fn handle_response_no_n() { - let count_op = Count::empty(); - handle_response_test(&count_op, doc! { "ok": 1.0 }).unwrap_err(); -} diff --git a/src/operation/count_documents.rs b/src/operation/count_documents.rs new file mode 100644 index 000000000..9ba6bfe8c --- /dev/null +++ b/src/operation/count_documents.rs @@ -0,0 +1,118 @@ +use std::convert::TryInto; + +use serde::Deserialize; + +use crate::{ + bson::{doc, Document, RawDocument}, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::{Error, ErrorKind, Result}, + operation::aggregate::Aggregate, + options::{AggregateOptions, CountOptions}, + selection_criteria::SelectionCriteria, + Namespace, +}; + +use super::{ExecutionContext, OperationWithDefaults, Retryability, SingleCursorResult}; + +pub(crate) struct CountDocuments { + aggregate: Aggregate, +} + +impl CountDocuments { + pub(crate) fn new( + namespace: Namespace, + filter: Document, + options: Option, + ) -> Result { + let mut pipeline = vec![doc! { + "$match": filter, + }]; + + if let Some(skip) = options.as_ref().and_then(|opts| opts.skip) { + let s: i64 = skip.try_into().map_err(|_| { + Error::from(ErrorKind::InvalidArgument { + message: format!("skip exceeds range of i64: {}", skip), + }) + })?; + pipeline.push(doc! { + "$skip": s + }); + } + + if let Some(limit) = options.as_ref().and_then(|opts| opts.limit) { + let l: i64 = limit.try_into().map_err(|_| { + Error::from(ErrorKind::InvalidArgument { + message: format!("limit exceeds range of i64: {}", limit), + }) + })?; + pipeline.push(doc! { + "$limit": l + }); + } + + pipeline.push(doc! { + "$group": { + "_id": 1, + "n": { "$sum": 1 }, + } + }); + + let aggregate_options = options.map(|opts| { + AggregateOptions::builder() + .hint(opts.hint) + .max_time(opts.max_time) + .collation(opts.collation) + .selection_criteria(opts.selection_criteria) + .read_concern(opts.read_concern) + .comment(opts.comment) + .build() + }); + + Ok(Self { + aggregate: Aggregate::new(namespace, pipeline, aggregate_options), + }) + } +} + +impl OperationWithDefaults for CountDocuments { + type O = u64; + + const NAME: &'static crate::bson_compat::CStr = Aggregate::NAME; + + fn build(&mut self, description: &StreamDescription) -> Result { + self.aggregate.build(description) + } + + fn extract_at_cluster_time( + &self, + response: &RawDocument, + ) -> Result> { + self.aggregate.extract_at_cluster_time(response) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: SingleCursorResult = response.body()?; + Ok(response.0.map(|r| r.n).unwrap_or(0)) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.aggregate.selection_criteria() + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } + + fn supports_read_concern(&self, description: &StreamDescription) -> bool { + self.aggregate.supports_read_concern(description) + } +} + +#[derive(Debug, Deserialize)] +struct Body { + n: u64, +} diff --git a/src/operation/count_documents/mod.rs b/src/operation/count_documents/mod.rs deleted file mode 100644 index 2d8841a34..000000000 --- a/src/operation/count_documents/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -#[cfg(test)] -mod test; - -use std::convert::TryInto; - -use serde::Deserialize; - -use super::{OperationWithDefaults, Retryability, SingleCursorResult}; -use crate::{ - cmap::{Command, RawCommandResponse, StreamDescription}, - error::{Error, ErrorKind, Result}, - operation::aggregate::Aggregate, - options::{AggregateOptions, CountOptions}, - selection_criteria::SelectionCriteria, - Namespace, -}; -use bson::{doc, Document, RawDocument}; - -pub(crate) struct CountDocuments { - aggregate: Aggregate, -} - -impl CountDocuments { - pub(crate) fn new( - namespace: Namespace, - filter: Option, - options: Option, - ) -> Result { - let mut pipeline = vec![doc! { - "$match": filter.unwrap_or_default(), - }]; - - if let Some(skip) = options.as_ref().and_then(|opts| opts.skip) { - let s: i64 = skip.try_into().map_err(|_| { - Error::from(ErrorKind::InvalidArgument { - message: format!("skip exceeds range of i64: {}", skip), - }) - })?; - pipeline.push(doc! { - "$skip": s - }); - } - - if let Some(limit) = options.as_ref().and_then(|opts| opts.limit) { - let l: i64 = limit.try_into().map_err(|_| { - Error::from(ErrorKind::InvalidArgument { - message: format!("limit exceeds range of i64: {}", limit), - }) - })?; - pipeline.push(doc! { - "$limit": l - }); - } - - pipeline.push(doc! { - "$group": { - "_id": 1, - "n": { "$sum": 1 }, - } - }); - - let aggregate_options = options.map(|opts| { - AggregateOptions::builder() - .hint(opts.hint) - .max_time(opts.max_time) - .collation(opts.collation) - .selection_criteria(opts.selection_criteria) - .read_concern(opts.read_concern) - .comment_bson(opts.comment) - .build() - }); - - Ok(Self { - aggregate: Aggregate::new(namespace, pipeline, aggregate_options), - }) - } -} - -impl OperationWithDefaults for CountDocuments { - type O = u64; - type Command = Document; - - const NAME: &'static str = Aggregate::NAME; - - fn build(&mut self, description: &StreamDescription) -> Result { - self.aggregate.build(description) - } - - fn extract_at_cluster_time(&self, response: &RawDocument) -> Result> { - self.aggregate.extract_at_cluster_time(response) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: SingleCursorResult = response.body()?; - Ok(response.0.map(|r| r.n).unwrap_or(0)) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.aggregate.selection_criteria() - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } - - fn supports_read_concern(&self, description: &StreamDescription) -> bool { - self.aggregate.supports_read_concern(description) - } -} - -#[derive(Debug, Deserialize)] -struct Body { - n: u64, -} diff --git a/src/operation/count_documents/test.rs b/src/operation/count_documents/test.rs deleted file mode 100644 index 42260199a..000000000 --- a/src/operation/count_documents/test.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::{ - bson::doc, - bson_util, - cmap::StreamDescription, - coll::Namespace, - concern::ReadConcern, - operation::{ - test::{self, handle_response_test}, - Operation, - }, - options::{CountOptions, Hint}, -}; - -use super::CountDocuments; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut count_op = CountDocuments::new(ns, Some(doc! { "x": 1 }), None).unwrap(); - let mut count_command = count_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - - let mut expected_body = doc! { - "aggregate": "test_coll", - "pipeline": [ - { "$match": { "x": 1 } }, - { "$group": { "_id": 1, "n": { "$sum": 1 } } }, - ], - "cursor": { } - }; - - bson_util::sort_document(&mut expected_body); - bson_util::sort_document(&mut count_command.body); - - assert_eq!(count_command.body, expected_body); - assert_eq!(count_command.target_db, "test_db"); -} - -#[test] -fn build_with_options() { - let skip = 2; - let limit = 5; - let options = CountOptions::builder() - .skip(skip) - .limit(limit) - .hint(Hint::Name("_id_1".to_string())) - .read_concern(ReadConcern::available()) - .build(); - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut count_op = CountDocuments::new(ns, None, Some(options)).unwrap(); - let count_command = count_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - assert_eq!(count_command.target_db, "test_db"); - - let mut expected_body = doc! { - "aggregate": "test_coll", - "$db": "test_db", - "pipeline": [ - { "$match": {} }, - { "$skip": skip as i64 }, - { "$limit": limit as i64 }, - { "$group": { "_id": 1, "n": { "$sum": 1 } } }, - ], - "hint": "_id_1", - "cursor": { }, - "readConcern": { "level": "available" }, - }; - - bson_util::sort_document(&mut expected_body); - let serialized_command = count_op.serialize_command(count_command).unwrap(); - let mut cmd_doc = bson::from_slice(&serialized_command).unwrap(); - bson_util::sort_document(&mut cmd_doc); - - assert_eq!(cmd_doc, expected_body); -} - -#[test] -fn op_selection_criteria() { - test::op_selection_criteria(|selection_criteria| { - let options = CountOptions { - selection_criteria, - ..Default::default() - }; - CountDocuments::new(Namespace::empty(), None, Some(options)).unwrap() - }); -} - -#[test] -fn handle_success() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let count_op = CountDocuments::new(ns, None, None).unwrap(); - - let n = 26; - let response = doc! { - "ok": 1.0, - "cursor": { - "id": 0, - "ns": "test_db.test_coll", - "firstBatch": [ { "_id": 1, "n": n as i32 } ], - } - }; - - let actual_values = handle_response_test(&count_op, response).unwrap(); - assert_eq!(actual_values, n); -} diff --git a/src/operation/create.rs b/src/operation/create.rs new file mode 100644 index 000000000..56b849ec0 --- /dev/null +++ b/src/operation/create.rs @@ -0,0 +1,55 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::Result, + operation::{append_options_to_raw_document, OperationWithDefaults, WriteConcernOnlyBody}, + options::{CreateCollectionOptions, WriteConcern}, + Namespace, +}; + +use super::ExecutionContext; + +#[derive(Debug)] +pub(crate) struct Create { + ns: Namespace, + options: Option, +} + +impl Create { + pub(crate) fn new(ns: Namespace, options: Option) -> Self { + Self { ns, options } + } +} + +impl OperationWithDefaults for Create { + type O = (); + + const NAME: &'static CStr = cstr!("create"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate() + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } +} diff --git a/src/operation/create/mod.rs b/src/operation/create/mod.rs deleted file mode 100644 index d531e8ca1..000000000 --- a/src/operation/create/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::Document; - -use crate::{ - bson::doc, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::{ - append_options, - remove_empty_write_concern, - OperationWithDefaults, - WriteConcernOnlyBody, - }, - options::{CreateCollectionOptions, WriteConcern}, - Namespace, -}; - -#[derive(Debug)] -pub(crate) struct Create { - ns: Namespace, - options: Option, -} - -impl Create { - #[cfg(test)] - fn empty() -> Self { - Self::new( - Namespace { - db: String::new(), - coll: String::new(), - }, - None, - ) - } - - pub(crate) fn new(ns: Namespace, options: Option) -> Self { - Self { ns, options } - } -} - -impl OperationWithDefaults for Create { - type O = (); - type Command = Document; - - const NAME: &'static str = "create"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate() - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } -} diff --git a/src/operation/create/test.rs b/src/operation/create/test.rs deleted file mode 100644 index 3a8ea1843..000000000 --- a/src/operation/create/test.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{ - bson::{doc, Bson}, - cmap::StreamDescription, - concern::WriteConcern, - error::{ErrorKind, WriteFailure}, - operation::{test::handle_response_test, Create, Operation}, - options::{CreateCollectionOptions, ValidationAction, ValidationLevel}, - Namespace, -}; - -#[test] -fn build() { - let mut op = Create::new( - Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }, - Some(CreateCollectionOptions { - write_concern: Some(WriteConcern { - journal: Some(true), - ..Default::default() - }), - validation_level: Some(ValidationLevel::Moderate), - validation_action: Some(ValidationAction::Warn), - ..Default::default() - }), - ); - - let description = StreamDescription::new_testing(); - let cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "create"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - assert_eq!( - cmd.body, - doc! { - "create": "test_coll", - "validationLevel": "moderate", - "validationAction": "warn", - "writeConcern": { "j": true }, - } - ); -} - -#[test] -fn build_validator() { - let query = doc! { "x": { "$gt": 1 } }; - let mut op = Create::new( - Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }, - Some(CreateCollectionOptions { - validator: Some(query.clone()), - ..Default::default() - }), - ); - - let description = StreamDescription::new_testing(); - let cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "create"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - assert_eq!( - cmd.body, - doc! { - "create": "test_coll", - "validator": Bson::Document(query) - } - ); -} - -#[test] -fn handle_success() { - let op = Create::empty(); - handle_response_test(&op, doc! { "ok": 1.0 }).unwrap(); -} - -#[test] -fn handle_write_concern_error() { - let op = Create::empty(); - - let response = doc! { - "writeConcernError": { - "code": 100, - "codeName": "hello world", - "errmsg": "12345" - }, - "ok": 1 - }; - - let err = handle_response_test(&op, response).unwrap_err(); - match *err.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_err)) => { - assert_eq!(wc_err.code, 100); - assert_eq!(wc_err.code_name, "hello world"); - assert_eq!(wc_err.message, "12345"); - } - ref e => panic!("expected write concern error, got {:?}", e), - } -} diff --git a/src/operation/create_indexes.rs b/src/operation/create_indexes.rs new file mode 100644 index 000000000..621b0d332 --- /dev/null +++ b/src/operation/create_indexes.rs @@ -0,0 +1,86 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + bson_util::to_raw_bson_array_ser, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::{ErrorKind, Result}, + index::IndexModel, + operation::{append_options_to_raw_document, OperationWithDefaults}, + options::{CreateIndexOptions, WriteConcern}, + results::CreateIndexesResult, + Namespace, +}; + +use super::{ExecutionContext, WriteConcernOnlyBody}; + +#[derive(Debug)] +pub(crate) struct CreateIndexes { + ns: Namespace, + indexes: Vec, + options: Option, +} + +impl CreateIndexes { + pub(crate) fn new( + ns: Namespace, + indexes: Vec, + options: Option, + ) -> Self { + Self { + ns, + indexes, + options, + } + } +} + +impl OperationWithDefaults for CreateIndexes { + type O = CreateIndexesResult; + const NAME: &'static CStr = cstr!("createIndexes"); + + fn build(&mut self, description: &StreamDescription) -> Result { + // commit quorum is not supported on < 4.4 + if description.max_wire_version.unwrap_or(0) < 9 + && self + .options + .as_ref() + .is_some_and(|options| options.commit_quorum.is_some()) + { + return Err(ErrorKind::InvalidArgument { + message: "Specifying a commit quorum to create_index(es) is not supported on \ + server versions < 4.4" + .to_string(), + } + .into()); + } + + self.indexes.iter_mut().for_each(|i| i.update_name()); // Generate names for unnamed indexes. + let indexes = to_raw_bson_array_ser(&self.indexes)?; + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + "indexes": indexes, + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate()?; + let index_names = self.indexes.iter().filter_map(|i| i.get_name()).collect(); + Ok(CreateIndexesResult { index_names }) + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } +} diff --git a/src/operation/create_indexes/mod.rs b/src/operation/create_indexes/mod.rs deleted file mode 100644 index 247e6cb40..000000000 --- a/src/operation/create_indexes/mod.rs +++ /dev/null @@ -1,104 +0,0 @@ -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::{ErrorKind, Result}, - index::IndexModel, - operation::{append_options, remove_empty_write_concern, OperationWithDefaults}, - options::{CreateIndexOptions, WriteConcern}, - results::CreateIndexesResult, - Namespace, -}; - -use super::WriteConcernOnlyBody; - -#[derive(Debug)] -pub(crate) struct CreateIndexes { - ns: Namespace, - indexes: Vec, - options: Option, -} - -impl CreateIndexes { - pub(crate) fn new( - ns: Namespace, - indexes: Vec, - options: Option, - ) -> Self { - Self { - ns, - indexes, - options, - } - } - - #[cfg(test)] - pub(crate) fn with_indexes(indexes: Vec) -> Self { - Self { - ns: Namespace { - db: String::new(), - coll: String::new(), - }, - indexes, - options: None, - } - } -} - -impl OperationWithDefaults for CreateIndexes { - type O = CreateIndexesResult; - type Command = Document; - const NAME: &'static str = "createIndexes"; - - fn build(&mut self, description: &StreamDescription) -> Result { - // commit quorum is not supported on < 4.4 - if description.max_wire_version.unwrap_or(0) < 9 - && self - .options - .as_ref() - .map_or(false, |options| options.commit_quorum.is_some()) - { - return Err(ErrorKind::InvalidArgument { - message: "Specifying a commit quorum to create_index(es) is not supported on \ - server versions < 4.4" - .to_string(), - } - .into()); - } - - self.indexes.iter_mut().for_each(|i| i.update_name()); // Generate names for unnamed indexes. - let indexes = bson::to_bson(&self.indexes)?; - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - "indexes": indexes, - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate()?; - let index_names = self.indexes.iter().filter_map(|i| i.get_name()).collect(); - Ok(CreateIndexesResult { index_names }) - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } -} diff --git a/src/operation/create_indexes/test.rs b/src/operation/create_indexes/test.rs deleted file mode 100644 index 59d1ab6d2..000000000 --- a/src/operation/create_indexes/test.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::doc, - cmap::StreamDescription, - coll::{ - options::{CommitQuorum, CreateIndexOptions}, - Namespace, - }, - concern::WriteConcern, - index::{options::IndexOptions, IndexModel}, - operation::{test::handle_response_test, CreateIndexes, Operation}, - results::CreateIndexesResult, -}; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let index_options = IndexOptions::builder() - .name(Some("foo".to_string())) - .build(); - let index_model = IndexModel::builder() - .keys(doc! { "x": 1 }) - .options(Some(index_options)) - .build(); - let create_options = CreateIndexOptions::builder() - .commit_quorum(Some(CommitQuorum::Majority)) - .max_time(Some(Duration::from_millis(42))) - .write_concern(Some(WriteConcern::builder().journal(Some(true)).build())) - .build(); - let mut create_indexes = CreateIndexes::new(ns, vec![index_model], Some(create_options)); - - let cmd = create_indexes - .build(&StreamDescription::with_wire_version(10)) - .expect("CreateIndexes command failed to build when it should have succeeded."); - - assert_eq!( - cmd.body, - doc! { - "createIndexes": "test_coll", - "indexes": [{ - "key": { "x": 1 }, - "name": "foo" - }], - "commitQuorum": "majority", - "maxTimeMS": 42, - "writeConcern": { "j": true }, - } - ) -} - -#[test] -fn handle_success() { - let a = IndexModel::builder() - .keys(doc! { "a": 1 }) - .options(Some( - IndexOptions::builder().name(Some("a".to_string())).build(), - )) - .build(); - let b = IndexModel::builder() - .keys(doc! { "b": 1 }) - .options(Some( - IndexOptions::builder().name(Some("b".to_string())).build(), - )) - .build(); - let op = CreateIndexes::with_indexes(vec![a, b]); - - let response = doc! { - "ok": 1, - "createdCollectionAutomatically": false, - "numIndexesBefore": 1, - "numIndexesAfter": 3, - "commitQuorum": "votingMembers", - }; - - let expected_values = CreateIndexesResult { - index_names: vec!["a".to_string(), "b".to_string()], - }; - let actual_values = handle_response_test(&op, response).unwrap(); - assert_eq!(actual_values, expected_values); -} diff --git a/src/operation/delete.rs b/src/operation/delete.rs new file mode 100644 index 000000000..5a388c1d2 --- /dev/null +++ b/src/operation/delete.rs @@ -0,0 +1,102 @@ +use crate::{ + bson::{doc, Document}, + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + coll::Namespace, + collation::Collation, + error::{convert_insert_many_error, Result}, + operation::{append_options, OperationWithDefaults, Retryability, WriteResponseBody}, + options::{DeleteOptions, Hint, WriteConcern}, + results::DeleteResult, +}; + +use super::ExecutionContext; + +#[derive(Debug)] +pub(crate) struct Delete { + ns: Namespace, + filter: Document, + limit: u32, + options: Option, + collation: Option, + hint: Option, +} + +impl Delete { + pub(crate) fn new( + ns: Namespace, + filter: Document, + limit: Option, + mut options: Option, + ) -> Self { + Self { + ns, + filter, + limit: limit.unwrap_or(0), // 0 = no limit + collation: options.as_mut().and_then(|opts| opts.collation.take()), + hint: options.as_mut().and_then(|opts| opts.hint.take()), + options, + } + } +} + +impl OperationWithDefaults for Delete { + type O = DeleteResult; + + const NAME: &'static CStr = cstr!("delete"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut delete = doc! { + "q": self.filter.clone(), + "limit": self.limit, + }; + + if let Some(ref collation) = self.collation { + delete.insert( + "collation", + crate::bson_compat::serialize_to_bson(&collation)?, + ); + } + + if let Some(ref hint) = self.hint { + delete.insert("hint", crate::bson_compat::serialize_to_bson(&hint)?); + } + + let mut body = doc! { + crate::bson_compat::cstr_to_str(Self::NAME): self.ns.coll.clone(), + "deletes": [delete], + "ordered": true, // command monitoring tests expect this (SPEC-1130) + }; + + append_options(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, (&body).try_into()?)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteResponseBody = response.body()?; + response.validate().map_err(convert_insert_many_error)?; + + Ok(DeleteResult { + deleted_count: response.n, + }) + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } + + fn retryability(&self) -> Retryability { + if self.limit == 1 { + Retryability::Write + } else { + Retryability::None + } + } +} diff --git a/src/operation/delete/mod.rs b/src/operation/delete/mod.rs deleted file mode 100644 index 7458011d4..000000000 --- a/src/operation/delete/mod.rs +++ /dev/null @@ -1,124 +0,0 @@ -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - coll::Namespace, - collation::Collation, - error::{convert_bulk_errors, Result}, - operation::{ - append_options, - remove_empty_write_concern, - OperationWithDefaults, - Retryability, - WriteResponseBody, - }, - options::{DeleteOptions, Hint, WriteConcern}, - results::DeleteResult, -}; - -#[derive(Debug)] -pub(crate) struct Delete { - ns: Namespace, - filter: Document, - limit: u32, - options: Option, - collation: Option, - hint: Option, -} - -impl Delete { - #[cfg(test)] - fn empty() -> Self { - Self::new( - Namespace { - db: String::new(), - coll: String::new(), - }, - Document::new(), - None, - None, - ) - } - - pub(crate) fn new( - ns: Namespace, - filter: Document, - limit: Option, - mut options: Option, - ) -> Self { - Self { - ns, - filter, - limit: limit.unwrap_or(0), // 0 = no limit - collation: options.as_mut().and_then(|opts| opts.collation.take()), - hint: options.as_mut().and_then(|opts| opts.hint.take()), - options, - } - } -} - -impl OperationWithDefaults for Delete { - type O = DeleteResult; - type Command = Document; - - const NAME: &'static str = "delete"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut delete = doc! { - "q": self.filter.clone(), - "limit": self.limit, - }; - - if let Some(ref collation) = self.collation { - delete.insert("collation", bson::to_bson(&collation)?); - } - - if let Some(ref hint) = self.hint { - delete.insert("hint", bson::to_bson(&hint)?); - } - - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - "deletes": [delete], - "ordered": true, // command monitoring tests expect this (SPEC-1130) - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteResponseBody = response.body()?; - response.validate().map_err(convert_bulk_errors)?; - - Ok(DeleteResult { - deleted_count: response.n, - }) - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } - - fn retryability(&self) -> Retryability { - if self.limit == 1 { - Retryability::Write - } else { - Retryability::None - } - } -} diff --git a/src/operation/delete/test.rs b/src/operation/delete/test.rs deleted file mode 100644 index 50f9427cf..000000000 --- a/src/operation/delete/test.rs +++ /dev/null @@ -1,196 +0,0 @@ -use pretty_assertions::assert_eq; - -use crate::{ - bson::doc, - bson_util, - cmap::StreamDescription, - concern::{Acknowledgment, WriteConcern}, - error::{ErrorKind, WriteConcernError, WriteError, WriteFailure}, - operation::{test::handle_response_test, Delete, Operation}, - options::DeleteOptions, - Namespace, -}; - -#[test] -fn build_many() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - - let wc = WriteConcern { - w: Some(Acknowledgment::Majority), - ..Default::default() - }; - let options = DeleteOptions::builder().write_concern(wc).build(); - - let mut op = Delete::new(ns, filter.clone(), None, Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "delete"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "delete": "test_coll", - "deletes": [ - { - "q": filter, - "limit": 0, - } - ], - "writeConcern": { - "w": "majority" - }, - "ordered": true, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_one() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - - let wc = WriteConcern { - w: Some(Acknowledgment::Majority), - ..Default::default() - }; - let options = DeleteOptions::builder().write_concern(wc).build(); - - let mut op = Delete::new(ns, filter.clone(), Some(1), Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "delete"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "delete": "test_coll", - "deletes": [ - { - "q": filter, - "limit": 1, - } - ], - "writeConcern": { - "w": "majority" - }, - "ordered": true, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn handle_success() { - let op = Delete::empty(); - - let delete_result = handle_response_test( - &op, - doc! { - "ok": 1.0, - "n": 3 - }, - ) - .expect("should succeed"); - assert_eq!(delete_result.deleted_count, 3); -} - -#[test] -fn handle_invalid_response() { - let op = Delete::empty(); - handle_response_test( - &op, - doc! { - "ok": 1.0, - "asffasdf": 123123 - }, - ) - .expect_err("should fail"); -} - -#[test] -fn handle_write_failure() { - let op = Delete::empty(); - - let write_error_response = doc! { - "ok": 1.0, - "n": 0, - "writeErrors": [ - { - "index": 0, - "code": 1234, - "errmsg": "my error string" - } - ] - }; - let write_error = handle_response_test(&op, write_error_response).unwrap_err(); - match *write_error.kind { - ErrorKind::Write(WriteFailure::WriteError(ref error)) => { - let expected_err = WriteError { - code: 1234, - code_name: None, - message: "my error string".to_string(), - details: None, - }; - assert_eq!(error, &expected_err); - } - ref e => panic!("expected write error, got {:?}", e), - }; -} - -#[test] -fn handle_write_concern_failure() { - let op = Delete::empty(); - - let wc_error_response = doc! { - "ok": 1.0, - "n": 0, - "writeConcernError": { - "code": 456, - "codeName": "wcError", - "errmsg": "some message", - "errInfo": { - "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } - } - } - }; - - let wc_error = handle_response_test(&op, wc_error_response) - .expect_err("should fail with write concern error"); - match *wc_error.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_error)) => { - let expected_wc_err = WriteConcernError { - code: 456, - code_name: "wcError".to_string(), - message: "some message".to_string(), - details: Some(doc! { "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } }), - labels: vec![], - }; - assert_eq!(wc_error, &expected_wc_err); - } - ref e => panic!("expected write concern error, got {:?}", e), - } -} diff --git a/src/operation/distinct.rs b/src/operation/distinct.rs new file mode 100644 index 000000000..cbd20762e --- /dev/null +++ b/src/operation/distinct.rs @@ -0,0 +1,97 @@ +use serde::Deserialize; + +use crate::{ + bson::{doc, rawdoc, Bson, Document, RawBsonRef, RawDocumentBuf}, + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + coll::{options::DistinctOptions, Namespace}, + error::Result, + operation::{OperationWithDefaults, Retryability}, + selection_criteria::SelectionCriteria, +}; + +use super::{append_options_to_raw_document, ExecutionContext}; + +pub(crate) struct Distinct { + ns: Namespace, + field_name: String, + query: Document, + options: Option, +} + +impl Distinct { + pub fn new( + ns: Namespace, + field_name: String, + query: Document, + options: Option, + ) -> Self { + Distinct { + ns, + field_name, + query, + options, + } + } +} + +impl OperationWithDefaults for Distinct { + type O = Vec; + + const NAME: &'static CStr = cstr!("distinct"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + "key": self.field_name.clone(), + "query": RawDocumentBuf::from_document(&self.query)?, + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new_read( + Self::NAME, + &self.ns.db, + self.options.as_ref().and_then(|o| o.read_concern.clone()), + body, + )) + } + + fn extract_at_cluster_time( + &self, + response: &crate::bson::RawDocument, + ) -> Result> { + Ok(response + .get("atClusterTime")? + .and_then(RawBsonRef::as_timestamp)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: Response = response.body()?; + Ok(response.values) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + if let Some(ref options) = self.options { + return options.selection_criteria.as_ref(); + } + None + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } + + fn supports_read_concern(&self, _description: &StreamDescription) -> bool { + true + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct Response { + values: Vec, +} diff --git a/src/operation/distinct/mod.rs b/src/operation/distinct/mod.rs deleted file mode 100644 index e5f18cdca..000000000 --- a/src/operation/distinct/mod.rs +++ /dev/null @@ -1,115 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::RawBsonRef; -use serde::Deserialize; - -use crate::{ - bson::{doc, Bson, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - coll::{options::DistinctOptions, Namespace}, - error::Result, - operation::{append_options, OperationWithDefaults, Retryability}, - selection_criteria::SelectionCriteria, -}; - -pub(crate) struct Distinct { - ns: Namespace, - field_name: String, - query: Option, - options: Option, -} - -impl Distinct { - pub fn new( - ns: Namespace, - field_name: String, - query: Option, - options: Option, - ) -> Self { - Distinct { - ns, - field_name, - query, - options, - } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - Distinct { - ns: Namespace { - db: String::new(), - coll: String::new(), - }, - field_name: String::new(), - query: None, - options: None, - } - } -} - -impl OperationWithDefaults for Distinct { - type O = Vec; - type Command = Document; - - const NAME: &'static str = "distinct"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body: Document = doc! { - Self::NAME: self.ns.coll.clone(), - "key": self.field_name.clone(), - }; - - if let Some(ref query) = self.query { - body.insert("query", query.clone()); - } - - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new_read( - Self::NAME.to_string(), - self.ns.db.clone(), - self.options.as_ref().and_then(|o| o.read_concern.clone()), - body, - )) - } - - fn extract_at_cluster_time( - &self, - response: &bson::RawDocument, - ) -> Result> { - Ok(response - .get("atClusterTime")? - .and_then(RawBsonRef::as_timestamp)) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: Response = response.body()?; - Ok(response.values) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - if let Some(ref options) = self.options { - return options.selection_criteria.as_ref(); - } - None - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } - - fn supports_read_concern(&self, _description: &StreamDescription) -> bool { - true - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct Response { - values: Vec, -} diff --git a/src/operation/distinct/test.rs b/src/operation/distinct/test.rs deleted file mode 100644 index 955e725f1..000000000 --- a/src/operation/distinct/test.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::{doc, Bson}, - cmap::StreamDescription, - coll::{options::DistinctOptions, Namespace}, - error::ErrorKind, - operation::{ - test::{self, handle_response_test}, - Distinct, - Operation, - }, -}; - -#[test] -fn build() { - let field_name = "field_name".to_string(); - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut distinct_op = Distinct::new(ns, field_name.clone(), None, None); - let distinct_command = distinct_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - assert_eq!( - distinct_command.body, - doc! { - "distinct": "test_coll", - "key": field_name - } - ); - assert_eq!(distinct_command.target_db, "test_db"); -} - -#[test] -fn build_with_query() { - let field_name = "field_name".to_string(); - let query = doc! {"something" : "something else"}; - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut distinct_op = Distinct::new(ns, field_name.clone(), Some(query.clone()), None); - let distinct_command = distinct_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - assert_eq!( - distinct_command.body, - doc! { - "distinct": "test_coll", - "key": field_name, - "query": Bson::Document(query) - } - ); - assert_eq!(distinct_command.target_db, "test_db"); -} - -#[test] -fn build_with_options() { - let field_name = "field_name".to_string(); - let max_time = Duration::new(2_u64, 0); - let options: DistinctOptions = DistinctOptions::builder().max_time(max_time).build(); - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let mut distinct_op = Distinct::new(ns, field_name.clone(), None, Some(options)); - let distinct_command = distinct_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - - assert_eq!( - distinct_command.body, - doc! { - "distinct": "test_coll", - "key": field_name, - "maxTimeMS": max_time.as_millis() as i32 - } - ); - assert_eq!(distinct_command.target_db, "test_db"); -} - -#[test] -fn op_selection_criteria() { - test::op_selection_criteria(|selection_criteria| { - let options = DistinctOptions { - selection_criteria, - ..Default::default() - }; - Distinct::new(Namespace::empty(), String::new(), None, Some(options)) - }); -} - -#[test] -fn handle_success() { - let distinct_op = Distinct::empty(); - - let expected_values: Vec = - vec![Bson::String("A".to_string()), Bson::String("B".to_string())]; - - let response = doc! { - "values" : expected_values.clone(), - "ok" : 1 - }; - - let actual_values = handle_response_test(&distinct_op, response).unwrap(); - assert_eq!(actual_values, expected_values); -} - -#[test] -fn handle_response_with_empty_values() { - let distinct_op = Distinct::empty(); - - let response = doc! { - "values" : [], - "ok" : 1 - }; - - let expected_values: Vec = Vec::new(); - let actual_values = handle_response_test(&distinct_op, response).unwrap(); - assert_eq!(actual_values, expected_values); -} - -#[test] -fn handle_response_no_values() { - let distinct_op = Distinct::empty(); - - let response = doc! { - "ok" : 1 - }; - - let result = handle_response_test(&distinct_op, response); - match result.map_err(|e| *e.kind) { - Err(ErrorKind::InvalidResponse { .. }) => {} - other => panic!("expected response error, but got {:?}", other), - } -} diff --git a/src/operation/drop_collection.rs b/src/operation/drop_collection.rs new file mode 100644 index 000000000..48fc24edc --- /dev/null +++ b/src/operation/drop_collection.rs @@ -0,0 +1,63 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::{Error, Result}, + operation::{append_options_to_raw_document, OperationWithDefaults, WriteConcernOnlyBody}, + options::{DropCollectionOptions, WriteConcern}, + Namespace, +}; + +use super::ExecutionContext; + +#[derive(Debug)] +pub(crate) struct DropCollection { + ns: Namespace, + options: Option, +} + +impl DropCollection { + pub(crate) fn new(ns: Namespace, options: Option) -> Self { + DropCollection { ns, options } + } +} + +impl OperationWithDefaults for DropCollection { + type O = (); + + const NAME: &'static CStr = cstr!("drop"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate() + } + + fn handle_error(&self, error: Error) -> Result { + if error.is_ns_not_found() { + Ok(()) + } else { + Err(error) + } + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } +} diff --git a/src/operation/drop_collection/mod.rs b/src/operation/drop_collection/mod.rs deleted file mode 100644 index 7d270ef4e..000000000 --- a/src/operation/drop_collection/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::Document; - -use crate::{ - bson::doc, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::{Error, Result}, - operation::{ - append_options, - remove_empty_write_concern, - OperationWithDefaults, - WriteConcernOnlyBody, - }, - options::{DropCollectionOptions, WriteConcern}, - Namespace, -}; - -#[derive(Debug)] -pub(crate) struct DropCollection { - ns: Namespace, - options: Option, -} - -impl DropCollection { - pub(crate) fn new(ns: Namespace, options: Option) -> Self { - DropCollection { ns, options } - } - - #[cfg(test)] - fn empty() -> Self { - Self::new( - Namespace { - db: String::new(), - coll: String::new(), - }, - None, - ) - } -} - -impl OperationWithDefaults for DropCollection { - type O = (); - type Command = Document; - - const NAME: &'static str = "drop"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate() - } - - fn handle_error(&self, error: Error) -> Result { - if error.is_ns_not_found() { - Ok(()) - } else { - Err(error) - } - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } -} diff --git a/src/operation/drop_collection/test.rs b/src/operation/drop_collection/test.rs deleted file mode 100644 index 4c74a8802..000000000 --- a/src/operation/drop_collection/test.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::{ - bson::doc, - error::{ErrorKind, WriteFailure}, - operation::{test::handle_response_test, DropCollection}, -}; - -#[test] -fn handle_success() { - let op = DropCollection::empty(); - - let ok_response = doc! { "ok": 1.0 }; - handle_response_test(&op, ok_response).unwrap(); - let ok_extra = doc! { "ok": 1.0, "hello": "world" }; - handle_response_test(&op, ok_extra).unwrap(); -} - -#[test] -fn handle_write_concern_error() { - let op = DropCollection::empty(); - - let response = doc! { - "writeConcernError": { - "code": 100, - "codeName": "hello world", - "errmsg": "12345" - }, - "ok": 1 - }; - - let err = handle_response_test(&op, response).unwrap_err(); - match *err.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_err)) => { - assert_eq!(wc_err.code, 100); - assert_eq!(wc_err.code_name, "hello world"); - assert_eq!(wc_err.message, "12345"); - } - ref e => panic!("expected write concern error, got {:?}", e), - } -} diff --git a/src/operation/drop_database.rs b/src/operation/drop_database.rs new file mode 100644 index 000000000..f0009f3c6 --- /dev/null +++ b/src/operation/drop_database.rs @@ -0,0 +1,55 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + db::options::DropDatabaseOptions, + error::Result, + operation::{append_options_to_raw_document, OperationWithDefaults, WriteConcernOnlyBody}, + options::WriteConcern, +}; + +use super::ExecutionContext; + +#[derive(Debug)] +pub(crate) struct DropDatabase { + target_db: String, + options: Option, +} + +impl DropDatabase { + pub(crate) fn new(target_db: String, options: Option) -> Self { + Self { target_db, options } + } +} + +impl OperationWithDefaults for DropDatabase { + type O = (); + + const NAME: &'static CStr = cstr!("dropDatabase"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: 1, + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.target_db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteConcernOnlyBody = response.body()?; + response.validate() + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } +} diff --git a/src/operation/drop_database/mod.rs b/src/operation/drop_database/mod.rs deleted file mode 100644 index 2cd2b8cf7..000000000 --- a/src/operation/drop_database/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::Document; - -use crate::{ - bson::doc, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::{ - append_options, - remove_empty_write_concern, - OperationWithDefaults, - WriteConcernOnlyBody, - }, - options::{DropDatabaseOptions, WriteConcern}, -}; - -#[derive(Debug)] -pub(crate) struct DropDatabase { - target_db: String, - options: Option, -} - -impl DropDatabase { - #[cfg(test)] - fn empty() -> Self { - Self::new(String::new(), None) - } - - pub(crate) fn new(target_db: String, options: Option) -> Self { - Self { target_db, options } - } -} - -impl OperationWithDefaults for DropDatabase { - type O = (); - type Command = Document; - - const NAME: &'static str = "dropDatabase"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: 1, - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.target_db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteConcernOnlyBody = response.body()?; - response.validate() - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } -} diff --git a/src/operation/drop_database/test.rs b/src/operation/drop_database/test.rs deleted file mode 100644 index f092a2b37..000000000 --- a/src/operation/drop_database/test.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - bson::doc, - cmap::StreamDescription, - concern::{Acknowledgment, WriteConcern}, - error::{ErrorKind, WriteFailure}, - operation::{test::handle_response_test, DropDatabase, Operation}, - options::DropDatabaseOptions, -}; - -#[test] -fn build() { - let mut op = DropDatabase { - target_db: "test_db".to_string(), - options: Some(DropDatabaseOptions { - write_concern: Some(WriteConcern { - w: Some(Acknowledgment::Custom("abc".to_string())), - ..Default::default() - }), - }), - }; - - let description = StreamDescription::new_testing(); - let cmd = op.build(&description).expect("build should succeed"); - - assert_eq!(cmd.name.as_str(), "dropDatabase"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - assert_eq!( - cmd.body, - doc! { - "dropDatabase": 1, - "writeConcern": { "w": "abc" } - } - ); - - let mut op = DropDatabase { - target_db: "test_db".to_string(), - options: None, - }; - let cmd = op.build(&description).expect("build should succeed"); - assert_eq!(cmd.name.as_str(), "dropDatabase"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - assert_eq!( - cmd.body, - doc! { - "dropDatabase": 1, - } - ); -} - -#[test] -fn handle_success() { - let op = DropDatabase::empty(); - - let ok_response = doc! { "ok": 1.0 }; - handle_response_test(&op, ok_response).unwrap(); - let ok_extra = doc! { "ok": 1.0, "hello": "world" }; - handle_response_test(&op, ok_extra).unwrap(); -} - -#[test] -fn handle_write_concern_error() { - let op = DropDatabase::empty(); - - let response = doc! { - "writeConcernError": { - "code": 100, - "codeName": "hello world", - "errmsg": "12345" - }, - "ok": 1 - }; - - let err = handle_response_test(&op, response).unwrap_err(); - - match *err.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_err)) => { - assert_eq!(wc_err.code, 100); - assert_eq!(wc_err.code_name, "hello world"); - assert_eq!(wc_err.message, "12345"); - } - ref e => panic!("expected write concern error, got {:?}", e), - } -} diff --git a/src/operation/drop_indexes.rs b/src/operation/drop_indexes.rs new file mode 100644 index 000000000..a9f7c0b4a --- /dev/null +++ b/src/operation/drop_indexes.rs @@ -0,0 +1,54 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::Result, + operation::{append_options_to_raw_document, OperationWithDefaults}, + options::{DropIndexOptions, WriteConcern}, + Namespace, +}; + +use super::ExecutionContext; + +pub(crate) struct DropIndexes { + ns: Namespace, + name: String, + options: Option, +} + +impl DropIndexes { + pub(crate) fn new(ns: Namespace, name: String, options: Option) -> Self { + Self { ns, name, options } + } +} + +impl OperationWithDefaults for DropIndexes { + type O = (); + const NAME: &'static CStr = cstr!("dropIndexes"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + "index": self.name.clone(), + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + _response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + Ok(()) + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } +} diff --git a/src/operation/drop_indexes/mod.rs b/src/operation/drop_indexes/mod.rs deleted file mode 100644 index dd7c9fbd5..000000000 --- a/src/operation/drop_indexes/mod.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::{append_options, remove_empty_write_concern, OperationWithDefaults}, - options::{DropIndexOptions, WriteConcern}, - Namespace, -}; - -pub(crate) struct DropIndexes { - ns: Namespace, - name: String, - options: Option, -} - -impl DropIndexes { - pub(crate) fn new(ns: Namespace, name: String, options: Option) -> Self { - Self { ns, name, options } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - Self { - ns: Namespace { - db: String::new(), - coll: String::new(), - }, - name: String::new(), - options: None, - } - } -} - -impl OperationWithDefaults for DropIndexes { - type O = (); - type Command = Document; - const NAME: &'static str = "dropIndexes"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - "index": self.name.clone(), - }; - - remove_empty_write_concern!(self.options); - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - _response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - Ok(()) - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } -} diff --git a/src/operation/drop_indexes/test.rs b/src/operation/drop_indexes/test.rs deleted file mode 100644 index 3a6b1fd13..000000000 --- a/src/operation/drop_indexes/test.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::doc, - cmap::StreamDescription, - coll::{options::DropIndexOptions, Namespace}, - concern::WriteConcern, - operation::{test::handle_response_test, DropIndexes, Operation}, -}; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let options = DropIndexOptions::builder() - .max_time(Some(Duration::from_secs(1))) - .write_concern(Some(WriteConcern::builder().journal(Some(true)).build())) - .build(); - - let mut drop_index = DropIndexes::new(ns, "foo".to_string(), Some(options)); - let cmd = drop_index - .build(&StreamDescription::new_testing()) - .expect("DropIndex command failed to build when it should have succeeded."); - assert_eq!( - cmd.body, - doc! { - "dropIndexes": "test_coll", - "index": "foo", - "maxTimeMS": 1000, - "writeConcern": { "j": true }, - } - ) -} - -#[test] -fn handle_success() { - let op = DropIndexes::empty(); - let response = doc! { "ok": 1 }; - handle_response_test(&op, response).unwrap(); -} diff --git a/src/operation/find.rs b/src/operation/find.rs new file mode 100644 index 000000000..fafffd1be --- /dev/null +++ b/src/operation/find.rs @@ -0,0 +1,131 @@ +use crate::bson::RawDocumentBuf; + +use crate::{ + bson::{rawdoc, Document}, + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + cursor::CursorSpecification, + error::{Error, Result}, + operation::{CursorBody, OperationWithDefaults, Retryability, SERVER_4_4_0_WIRE_VERSION}, + options::{CursorType, FindOptions, SelectionCriteria}, + Namespace, +}; + +use super::{append_options_to_raw_document, ExecutionContext}; + +#[derive(Debug)] +pub(crate) struct Find { + ns: Namespace, + filter: Document, + options: Option>, +} + +impl Find { + pub(crate) fn new(ns: Namespace, filter: Document, options: Option) -> Self { + Self { + ns, + filter, + options: options.map(Box::new), + } + } +} + +impl OperationWithDefaults for Find { + type O = CursorSpecification; + const NAME: &'static CStr = cstr!("find"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + if let Some(ref mut options) = self.options { + // negative limits should be interpreted as request for single batch as per crud spec. + if options.limit.map(|limit| limit < 0) == Some(true) { + body.append(cstr!("singleBatch"), true); + } + + if let Some(ref mut batch_size) = options.batch_size { + if i32::try_from(*batch_size).is_err() { + return Err(Error::invalid_argument( + "the batch size must fit into a signed 32-bit integer", + )); + } + if let Some(limit) = options.limit.and_then(|limit| u32::try_from(limit).ok()) { + if *batch_size == limit { + *batch_size += 1; + } + } + } + + match options.cursor_type { + Some(CursorType::Tailable) => { + body.append(cstr!("tailable"), true); + } + Some(CursorType::TailableAwait) => { + body.append(cstr!("tailable"), true); + body.append(cstr!("awaitData"), true); + } + _ => {} + }; + } + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + let raw_filter: RawDocumentBuf = (&self.filter).try_into()?; + body.append(cstr!("filter"), raw_filter); + + Ok(Command::new_read( + Self::NAME, + &self.ns.db, + self.options.as_ref().and_then(|o| o.read_concern.clone()), + body, + )) + } + + fn extract_at_cluster_time( + &self, + response: &crate::bson::RawDocument, + ) -> Result> { + CursorBody::extract_at_cluster_time(response) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> Result { + let response: CursorBody = response.body()?; + + let description = context.connection.stream_description()?; + + // The comment should only be propagated to getMore calls on 4.4+. + let comment = if description.max_wire_version.unwrap_or(0) < SERVER_4_4_0_WIRE_VERSION { + None + } else { + self.options.as_ref().and_then(|opts| opts.comment.clone()) + }; + + Ok(CursorSpecification::new( + response.cursor, + description.server_address.clone(), + self.options.as_ref().and_then(|opts| opts.batch_size), + self.options.as_ref().and_then(|opts| opts.max_await_time), + comment, + )) + } + + fn supports_read_concern(&self, _description: &StreamDescription) -> bool { + true + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.options + .as_ref() + .and_then(|opts| opts.selection_criteria.as_ref()) + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } +} diff --git a/src/operation/find/mod.rs b/src/operation/find/mod.rs deleted file mode 100644 index 947fc23cc..000000000 --- a/src/operation/find/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - cursor::CursorSpecification, - error::{ErrorKind, Result}, - operation::{ - append_options, - CursorBody, - OperationWithDefaults, - Retryability, - SERVER_4_4_0_WIRE_VERSION, - }, - options::{CursorType, FindOptions, SelectionCriteria}, - Namespace, -}; - -#[derive(Debug)] -pub(crate) struct Find { - ns: Namespace, - filter: Option, - options: Option>, -} - -impl Find { - #[cfg(test)] - fn empty() -> Self { - Self::new( - Namespace { - db: String::new(), - coll: String::new(), - }, - None, - None, - ) - } - - pub(crate) fn new( - ns: Namespace, - filter: Option, - mut options: Option, - ) -> Self { - if let Some(ref mut options) = options { - if let Some(ref comment) = options.comment { - if options.comment_bson.is_none() { - options.comment_bson = Some(comment.clone().into()); - } - } - } - - Self { - ns, - filter, - options: options.map(Box::new), - } - } -} - -impl OperationWithDefaults for Find { - type O = CursorSpecification; - type Command = Document; - const NAME: &'static str = "find"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - }; - - if let Some(ref options) = self.options { - // negative limits should be interpreted as request for single batch as per crud spec. - if options.limit.map(|limit| limit < 0) == Some(true) { - body.insert("singleBatch", true); - } - - if options - .batch_size - .map(|batch_size| batch_size > std::i32::MAX as u32) - == Some(true) - { - return Err(ErrorKind::InvalidArgument { - message: "The batch size must fit into a signed 32-bit integer".to_string(), - } - .into()); - } - - match options.cursor_type { - Some(CursorType::Tailable) => { - body.insert("tailable", true); - } - Some(CursorType::TailableAwait) => { - body.insert("tailable", true); - body.insert("awaitData", true); - } - _ => {} - }; - } - - append_options(&mut body, self.options.as_ref())?; - - if let Some(ref filter) = self.filter { - body.insert("filter", filter.clone()); - } - - Ok(Command::new_read( - Self::NAME.to_string(), - self.ns.db.clone(), - self.options.as_ref().and_then(|o| o.read_concern.clone()), - body, - )) - } - - fn extract_at_cluster_time( - &self, - response: &bson::RawDocument, - ) -> Result> { - CursorBody::extract_at_cluster_time(response) - } - - fn handle_response( - &self, - response: RawCommandResponse, - description: &StreamDescription, - ) -> Result { - let response: CursorBody = response.body()?; - - // The comment should only be propagated to getMore calls on 4.4+. - let comment = if description.max_wire_version.unwrap_or(0) < SERVER_4_4_0_WIRE_VERSION { - None - } else { - self.options - .as_ref() - .and_then(|opts| opts.comment_bson.clone()) - }; - - Ok(CursorSpecification::new( - response.cursor, - description.server_address.clone(), - self.options.as_ref().and_then(|opts| opts.batch_size), - self.options.as_ref().and_then(|opts| opts.max_await_time), - comment, - )) - } - - fn supports_read_concern(&self, _description: &StreamDescription) -> bool { - true - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.options - .as_ref() - .and_then(|opts| opts.selection_criteria.as_ref()) - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } -} diff --git a/src/operation/find/test.rs b/src/operation/find/test.rs deleted file mode 100644 index aafe7374a..000000000 --- a/src/operation/find/test.rs +++ /dev/null @@ -1,260 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::{doc, Document}, - bson_util, - cmap::StreamDescription, - operation::{ - test::{self, handle_response_test}, - Find, - Operation, - }, - options::{CursorType, FindOptions, Hint, ReadConcern, ReadConcernLevel}, - Namespace, -}; - -fn build_test( - ns: Namespace, - filter: Option, - options: Option, - mut expected_body: Document, -) { - let mut find = Find::new(ns.clone(), filter, options); - - let cmd = find.build(&StreamDescription::new_testing()).unwrap(); - - assert_eq!(cmd.name.as_str(), "find"); - assert_eq!(cmd.target_db.as_str(), ns.db.as_str()); - - let cmd_bytes = find.serialize_command(cmd).unwrap(); - let mut cmd_doc = bson::from_slice(&cmd_bytes).unwrap(); - - bson_util::sort_document(&mut expected_body); - bson_util::sort_document(&mut cmd_doc); - - assert_eq!(cmd_doc, expected_body); -} - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let filter = doc! { - "x": 2, - "y": { "$gt": 1 }, - }; - - let options = FindOptions::builder() - .hint(Hint::Keys(doc! { "x": 1, "y": 2 })) - .projection(doc! { "x": 0 }) - .allow_partial_results(true) - .read_concern(ReadConcern::from(ReadConcernLevel::Available)) - .build(); - - let expected_body = doc! { - "find": "test_coll", - "$db": "test_db", - "filter": filter.clone(), - "hint": { - "x": 1, - "y": 2, - }, - "projection": { - "x": 0 - }, - "allowPartialResults": true, - "readConcern": { - "level": "available" - } - }; - - build_test(ns, Some(filter), Some(options), expected_body); -} - -#[test] -fn build_cursor_type() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let non_tailable_options = FindOptions::builder() - .cursor_type(CursorType::NonTailable) - .build(); - - let non_tailable_body = doc! { - "find": "test_coll", - "$db": "test_db", - }; - - build_test( - ns.clone(), - None, - Some(non_tailable_options), - non_tailable_body, - ); - - let tailable_options = FindOptions::builder() - .cursor_type(CursorType::Tailable) - .build(); - - let tailable_body = doc! { - "find": "test_coll", - "tailable": true, - "$db": "test_db", - }; - - build_test(ns.clone(), None, Some(tailable_options), tailable_body); - - let tailable_await_options = FindOptions::builder() - .cursor_type(CursorType::TailableAwait) - .build(); - - let tailable_await_body = doc! { - "find": "test_coll", - "$db": "test_db", - "tailable": true, - "awaitData": true, - }; - - build_test(ns, None, Some(tailable_await_options), tailable_await_body); -} - -#[test] -fn build_max_await_time() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let options = FindOptions::builder() - .max_await_time(Duration::from_millis(5)) - .max_time(Duration::from_millis(10)) - .build(); - - let body = doc! { - "find": "test_coll", - "$db": "test_db", - "maxTimeMS": 10i32 - }; - - build_test(ns, None, Some(options), body); -} - -#[test] -fn build_limit() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let positive_options = FindOptions::builder().limit(5).build(); - - let positive_body = doc! { - "find": "test_coll", - "$db": "test_db", - "limit": 5_i64 - }; - - build_test(ns.clone(), None, Some(positive_options), positive_body); - - let negative_options = FindOptions::builder().limit(-5).build(); - - let negative_body = doc! { - "find": "test_coll", - "$db": "test_db", - "limit": 5_i64, - "singleBatch": true - }; - - build_test(ns, None, Some(negative_options), negative_body); -} - -#[test] -fn build_batch_size() { - let options = FindOptions::builder().batch_size(1).build(); - let body = doc! { - "find": "", - "$db": "", - "batchSize": 1 - }; - build_test(Namespace::empty(), None, Some(options), body); - - let options = FindOptions::builder() - .batch_size((std::i32::MAX as u32) + 1) - .build(); - let mut op = Find::new(Namespace::empty(), None, Some(options)); - assert!(op.build(&StreamDescription::new_testing()).is_err()) -} - -#[test] -fn op_selection_criteria() { - test::op_selection_criteria(|selection_criteria| { - let options = FindOptions { - selection_criteria, - ..Default::default() - }; - Find::new(Namespace::empty(), None, Some(options)) - }); -} - -fn verify_max_await_time(max_await_time: Option, cursor_type: Option) { - let ns = Namespace::empty(); - let find = Find::new( - ns, - None, - Some(FindOptions { - cursor_type, - max_await_time, - ..Default::default() - }), - ); - - let spec = handle_response_test( - &find, - doc! { - "cursor": { - "id": 123, - "ns": "a.b", - "firstBatch": [], - }, - "ok": 1 - }, - ) - .unwrap(); - assert_eq!(spec.max_time(), max_await_time); -} - -#[test] -fn handle_max_await_time() { - verify_max_await_time(None, None); - verify_max_await_time(Some(Duration::from_millis(5)), None); - verify_max_await_time( - Some(Duration::from_millis(5)), - Some(CursorType::NonTailable), - ); - verify_max_await_time(Some(Duration::from_millis(5)), Some(CursorType::Tailable)); - verify_max_await_time( - Some(Duration::from_millis(5)), - Some(CursorType::TailableAwait), - ); -} - -#[test] -fn handle_invalid_response() { - let find = Find::empty(); - - let garbled = doc! { "asdfasf": "ASdfasdf" }; - handle_response_test(&find, garbled).unwrap_err(); - - let missing_cursor_field = doc! { - "cursor": { - "ns": "test.test", - "firstBatch": [], - } - }; - handle_response_test(&find, missing_cursor_field).unwrap_err(); -} diff --git a/src/operation/find_and_modify.rs b/src/operation/find_and_modify.rs new file mode 100644 index 000000000..2e7d26147 --- /dev/null +++ b/src/operation/find_and_modify.rs @@ -0,0 +1,122 @@ +pub(crate) mod options; + +use std::{fmt::Debug, marker::PhantomData}; + +use serde::{de::DeserializeOwned, Deserialize}; + +use self::options::FindAndModifyOptions; +use crate::{ + bson::{doc, rawdoc, Document, RawBson, RawDocumentBuf}, + bson_compat::{cstr, deserialize_from_slice, CStr}, + bson_util, + cmap::{Command, RawCommandResponse, StreamDescription}, + coll::{options::UpdateModifications, Namespace}, + error::{ErrorKind, Result}, + operation::{ + append_options_to_raw_document, + find_and_modify::options::Modification, + OperationWithDefaults, + Retryability, + }, + options::WriteConcern, +}; + +use super::{ExecutionContext, UpdateOrReplace}; + +pub(crate) struct FindAndModify { + ns: Namespace, + query: Document, + modification: Modification, + options: Option, + _phantom: PhantomData T>, +} + +impl FindAndModify { + pub(crate) fn with_modification( + ns: Namespace, + query: Document, + modification: Modification, + options: Option, + ) -> Result { + if let Modification::Update(UpdateOrReplace::UpdateModifications( + UpdateModifications::Document(d), + )) = &modification + { + bson_util::update_document_check(d)?; + }; + Ok(Self { + ns, + query, + modification, + options, + _phantom: PhantomData, + }) + } +} + +impl OperationWithDefaults for FindAndModify { + type O = Option; + const NAME: &'static CStr = cstr!("findAndModify"); + + fn build(&mut self, description: &StreamDescription) -> Result { + if let Some(ref options) = self.options { + if options.hint.is_some() && description.max_wire_version.unwrap_or(0) < 8 { + return Err(ErrorKind::InvalidArgument { + message: "Specifying a hint to find_one_and_x is not supported on server \ + versions < 4.4" + .to_string(), + } + .into()); + } + } + + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + "query": RawDocumentBuf::from_document(&self.query)?, + }; + + match &self.modification { + Modification::Delete => body.append(cstr!("remove"), true), + Modification::Update(update_or_replace) => { + update_or_replace.append_to_rawdoc(&mut body, cstr!("update"))? + } + } + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + #[derive(Debug, Deserialize)] + pub(crate) struct Response { + value: RawBson, + } + let response: Response = response.body()?; + + match response.value { + RawBson::Document(doc) => Ok(Some(deserialize_from_slice(doc.as_bytes())?)), + RawBson::Null => Ok(None), + other => Err(ErrorKind::InvalidResponse { + message: format!( + "expected document for value field of findAndModify response, but instead got \ + {:?}", + other + ), + } + .into()), + } + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options.as_ref().and_then(|o| o.write_concern.as_ref()) + } + + fn retryability(&self) -> Retryability { + Retryability::Write + } +} diff --git a/src/operation/find_and_modify/mod.rs b/src/operation/find_and_modify/mod.rs deleted file mode 100644 index bdc4815e0..000000000 --- a/src/operation/find_and_modify/mod.rs +++ /dev/null @@ -1,164 +0,0 @@ -mod options; -#[cfg(test)] -mod test; - -use std::fmt::Debug; - -use serde::{de::DeserializeOwned, Deserialize}; - -use self::options::FindAndModifyOptions; -use crate::{ - bson::{doc, from_document, Bson, Document}, - bson_util, - cmap::{Command, RawCommandResponse, StreamDescription}, - coll::{ - options::{ - FindOneAndDeleteOptions, - FindOneAndReplaceOptions, - FindOneAndUpdateOptions, - UpdateModifications, - }, - Namespace, - }, - error::{ErrorKind, Result}, - operation::{append_options, remove_empty_write_concern, OperationWithDefaults, Retryability}, - options::WriteConcern, -}; - -pub(crate) struct FindAndModify -where - T: DeserializeOwned, -{ - ns: Namespace, - query: Document, - options: FindAndModifyOptions, - _phantom: std::marker::PhantomData, -} - -impl FindAndModify -where - T: DeserializeOwned, -{ - pub fn with_delete( - ns: Namespace, - query: Document, - options: Option, - ) -> Self { - let options = - FindAndModifyOptions::from_find_one_and_delete_options(options.unwrap_or_default()); - FindAndModify { - ns, - query, - options, - _phantom: Default::default(), - } - } - - pub fn with_replace( - ns: Namespace, - query: Document, - replacement: Document, - options: Option, - ) -> Result { - bson_util::replacement_document_check(&replacement)?; - let options = FindAndModifyOptions::from_find_one_and_replace_options( - replacement, - options.unwrap_or_default(), - ); - Ok(FindAndModify { - ns, - query, - options, - _phantom: Default::default(), - }) - } - - pub fn with_update( - ns: Namespace, - query: Document, - update: UpdateModifications, - options: Option, - ) -> Result { - if let UpdateModifications::Document(ref d) = update { - bson_util::update_document_check(d)?; - }; - let options = FindAndModifyOptions::from_find_one_and_update_options( - update, - options.unwrap_or_default(), - ); - Ok(FindAndModify { - ns, - query, - options, - _phantom: Default::default(), - }) - } -} - -impl OperationWithDefaults for FindAndModify -where - T: DeserializeOwned, -{ - type O = Option; - type Command = Document; - const NAME: &'static str = "findAndModify"; - - fn build(&mut self, description: &StreamDescription) -> Result { - if self.options.hint.is_some() && description.max_wire_version.unwrap_or(0) < 8 { - return Err(ErrorKind::InvalidArgument { - message: "Specifying a hint to find_one_and_x is not supported on server versions \ - < 4.4" - .to_string(), - } - .into()); - } - - let mut body: Document = doc! { - Self::NAME: self.ns.coll.clone(), - "query": self.query.clone(), - }; - - remove_empty_write_concern!(Some(&mut self.options)); - append_options(&mut body, Some(&self.options).as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: Response = response.body()?; - - match response.value { - Bson::Document(doc) => Ok(Some(from_document(doc)?)), - Bson::Null => Ok(None), - other => Err(ErrorKind::InvalidResponse { - message: format!( - "expected document for value field of findAndModify response, but instead got \ - {:?}", - other - ), - } - .into()), - } - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options.write_concern.as_ref() - } - - fn retryability(&self) -> Retryability { - Retryability::Write - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct Response { - value: Bson, -} diff --git a/src/operation/find_and_modify/options.rs b/src/operation/find_and_modify/options.rs index e65f0396a..76b18e217 100644 --- a/src/operation/find_and_modify/options.rs +++ b/src/operation/find_and_modify/options.rs @@ -1,160 +1,123 @@ use std::time::Duration; -use serde::{Serialize, Serializer}; +use serde::Serialize; use typed_builder::TypedBuilder; use crate::{ bson::{doc, Bson, Document}, - bson_util, coll::options::{ FindOneAndDeleteOptions, FindOneAndReplaceOptions, FindOneAndUpdateOptions, Hint, ReturnDocument, - UpdateModifications, }, collation::Collation, concern::WriteConcern, + operation::UpdateOrReplace, + serde_util::{self, write_concern_is_empty}, }; -#[derive(Clone, Debug, Serialize)] -pub(super) enum Modification { - #[serde(rename = "remove", serialize_with = "self::serialize_true")] +#[derive(Clone, Debug)] +pub(crate) enum Modification { Delete, - #[serde(rename = "update")] - Update(UpdateModifications), -} - -fn serialize_true(s: S) -> std::result::Result { - s.serialize_bool(true) + Update(UpdateOrReplace), } #[serde_with::skip_serializing_none] -#[derive(Clone, Debug, TypedBuilder, Serialize)] -#[builder(field_defaults(setter(into)))] +#[derive(Clone, Debug, TypedBuilder, Serialize, Default)] +#[builder(field_defaults(default, setter(into)))] #[serde(rename_all = "camelCase")] -pub(super) struct FindAndModifyOptions { - #[serde(flatten)] - pub(crate) modification: Modification, - - #[builder(default)] +pub(crate) struct FindAndModifyOptions { pub(crate) sort: Option, - #[builder(default)] pub(crate) new: Option, - #[builder(default)] pub(crate) upsert: Option, - #[builder(default)] pub(crate) bypass_document_validation: Option, - #[builder(default)] + #[serde(skip_serializing_if = "write_concern_is_empty")] pub(crate) write_concern: Option, - #[builder(default)] pub(crate) array_filters: Option>, #[serde( - serialize_with = "bson_util::serialize_duration_option_as_int_millis", + serialize_with = "serde_util::serialize_duration_option_as_int_millis", rename = "maxTimeMS" )] - #[builder(default)] pub(crate) max_time: Option, #[serde(rename = "fields")] - #[builder(default)] pub(crate) projection: Option, - #[builder(default)] pub(crate) collation: Option, - #[builder(default)] pub(crate) hint: Option, - #[builder(default)] #[serde(rename = "let")] pub(crate) let_vars: Option, - #[builder(default)] pub(crate) comment: Option, } -impl FindAndModifyOptions { - pub(super) fn from_find_one_and_delete_options( - opts: FindOneAndDeleteOptions, - ) -> FindAndModifyOptions { - let mut modify_opts = FindAndModifyOptions::builder() - .modification(Modification::Delete) - .build(); - - modify_opts.collation = opts.collation; - modify_opts.max_time = opts.max_time; - modify_opts.projection = opts.projection; - modify_opts.sort = opts.sort; - modify_opts.write_concern = opts.write_concern; - modify_opts.hint = opts.hint; - modify_opts.let_vars = opts.let_vars; - modify_opts.comment = opts.comment; - modify_opts +impl From for FindAndModifyOptions { + fn from(options: FindOneAndDeleteOptions) -> Self { + Self { + sort: options.sort, + new: None, + upsert: None, + bypass_document_validation: None, + write_concern: options.write_concern, + array_filters: None, + max_time: options.max_time, + projection: options.projection, + collation: options.collation, + hint: options.hint, + let_vars: options.let_vars, + comment: options.comment, + } } +} - pub(super) fn from_find_one_and_replace_options( - replacement: Document, - opts: FindOneAndReplaceOptions, - ) -> FindAndModifyOptions { - let replacement = UpdateModifications::Document(replacement); - let mut modify_opts = FindAndModifyOptions::builder() - .modification(Modification::Update(replacement)) - .build(); - - modify_opts.collation = opts.collation; - modify_opts.bypass_document_validation = opts.bypass_document_validation; - modify_opts.max_time = opts.max_time; - modify_opts.projection = opts.projection; - modify_opts.new = return_document_to_bool(opts.return_document); - modify_opts.sort = opts.sort; - modify_opts.upsert = opts.upsert; - modify_opts.write_concern = opts.write_concern; - modify_opts.hint = opts.hint; - modify_opts.let_vars = opts.let_vars; - modify_opts.comment = opts.comment; - - modify_opts +impl From for FindAndModifyOptions { + fn from(options: FindOneAndUpdateOptions) -> Self { + Self { + sort: options.sort, + new: return_document_to_bool(options.return_document), + upsert: options.upsert, + bypass_document_validation: options.bypass_document_validation, + write_concern: options.write_concern, + array_filters: options.array_filters, + max_time: options.max_time, + projection: options.projection, + collation: options.collation, + hint: options.hint, + let_vars: options.let_vars, + comment: options.comment, + } } +} - pub(super) fn from_find_one_and_update_options( - update: UpdateModifications, - opts: FindOneAndUpdateOptions, - ) -> FindAndModifyOptions { - let mut modify_opts = FindAndModifyOptions::builder() - .modification(Modification::Update(update)) - .build(); - - modify_opts.collation = opts.collation; - modify_opts.array_filters = opts.array_filters; - modify_opts.bypass_document_validation = opts.bypass_document_validation; - modify_opts.max_time = opts.max_time; - modify_opts.projection = opts.projection; - modify_opts.new = return_document_to_bool(opts.return_document); - modify_opts.sort = opts.sort; - modify_opts.upsert = opts.upsert; - modify_opts.write_concern = opts.write_concern; - modify_opts.hint = opts.hint; - modify_opts.let_vars = opts.let_vars; - modify_opts.comment = opts.comment; - - modify_opts +impl From for FindAndModifyOptions { + fn from(options: FindOneAndReplaceOptions) -> Self { + Self { + sort: options.sort, + new: return_document_to_bool(options.return_document), + upsert: options.upsert, + bypass_document_validation: options.bypass_document_validation, + write_concern: options.write_concern, + array_filters: None, + max_time: options.max_time, + projection: options.projection, + collation: options.collation, + hint: options.hint, + let_vars: options.let_vars, + comment: options.comment, + } } } fn return_document_to_bool(return_document: Option) -> Option { - if let Some(return_document) = return_document { - return match return_document { - ReturnDocument::After => Some(true), - ReturnDocument::Before => Some(false), - }; - } - None + return_document.as_ref().map(ReturnDocument::as_bool) } diff --git a/src/operation/find_and_modify/test.rs b/src/operation/find_and_modify/test.rs deleted file mode 100644 index 3c95ab9f6..000000000 --- a/src/operation/find_and_modify/test.rs +++ /dev/null @@ -1,513 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::{doc, oid::ObjectId, Bson, Document}, - bson_util, - cmap::StreamDescription, - coll::options::ReturnDocument, - operation::{test::handle_response_test, FindAndModify, Operation}, - options::{ - FindOneAndDeleteOptions, - FindOneAndReplaceOptions, - FindOneAndUpdateOptions, - Hint, - UpdateModifications, - }, - Namespace, -}; - -// delete tests - -fn empty_delete() -> FindAndModify { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! {}; - FindAndModify::with_delete(ns, filter, None) -} - -#[test] -fn build_with_delete_hint() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { - "x": 2, - "y": { "$gt": 1 }, - }; - - let options = FindOneAndDeleteOptions { - hint: Some(Hint::Keys(doc! { "x": 1, "y": -1 })), - ..Default::default() - }; - - let mut op = FindAndModify::::with_delete(ns, filter.clone(), Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "hint": { - "x": 1, - "y": -1, - }, - "remove": true - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_delete_no_options() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - - let mut op = FindAndModify::::with_delete(ns, filter.clone(), None); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "remove": true - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_delete() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let max_time = Duration::from_millis(2u64); - let options = FindOneAndDeleteOptions { - max_time: Some(max_time), - ..Default::default() - }; - - let mut op = FindAndModify::::with_delete(ns, filter.clone(), Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "maxTimeMS": max_time.as_millis() as i32, - "remove": true - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn handle_success_delete() { - let op = empty_delete(); - let value = doc! { - "_id" : Bson::ObjectId(ObjectId::new()), - "name" : "Tom", - "state" : "active", - "rating" : 100, - "score" : 5 - }; - let ok_response = doc! { - "lastErrorObject" : { - "connectionId" : 1, - "updatedExisting" : true, - "n" : 1, - "syncMillis" : 0, - "writtenTo" : null, - "err" : null, - "ok" : 1 - }, - "value" : value.clone(), - "ok" : 1 - }; - - let result = handle_response_test(&op, ok_response).unwrap(); - assert_eq!(result.unwrap(), value); -} - -#[test] -fn handle_null_value_delete() { - let op = empty_delete(); - - let result = handle_response_test(&op, doc! { "ok": 1.0, "value": Bson::Null }).unwrap(); - assert_eq!(result, None); -} - -#[test] -fn handle_no_value_delete() { - let op = empty_delete(); - - handle_response_test(&op, doc! { "ok": 1.0 }).unwrap_err(); -} - -// replace tests - -fn empty_replace() -> FindAndModify { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! {}; - let replacement = doc! { "x": { "inc": 1 } }; - FindAndModify::with_replace(ns, filter, replacement, None).unwrap() -} - -#[test] -fn build_with_replace_hint() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let replacement = doc! { "x": { "inc": 1 } }; - let options = FindOneAndReplaceOptions { - hint: Some(Hint::Keys(doc! { "x": 1, "y": -1 })), - upsert: Some(false), - bypass_document_validation: Some(true), - return_document: Some(ReturnDocument::After), - ..Default::default() - }; - - let mut op = FindAndModify::::with_replace( - ns, - filter.clone(), - replacement.clone(), - Some(options), - ) - .unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": replacement, - "upsert": false, - "bypassDocumentValidation": true, - "new": true, - "hint": { - "x": 1, - "y": -1, - }, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_replace_no_options() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let replacement = doc! { "x": { "inc": 1 } }; - - let mut op = - FindAndModify::::with_replace(ns, filter.clone(), replacement.clone(), None) - .unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": replacement, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_replace() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let replacement = doc! { "x": { "inc": 1 } }; - let options = FindOneAndReplaceOptions { - upsert: Some(false), - bypass_document_validation: Some(true), - return_document: Some(ReturnDocument::After), - ..Default::default() - }; - - let mut op = FindAndModify::::with_replace( - ns, - filter.clone(), - replacement.clone(), - Some(options), - ) - .unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": replacement, - "upsert": false, - "bypassDocumentValidation": true, - "new": true - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn handle_success_replace() { - let op = empty_replace(); - let value = doc! { - "_id" : Bson::ObjectId(ObjectId::new()), - "name" : "Tom", - "state" : "active", - "rating" : 100, - "score" : 5 - }; - let ok_response = doc! { - "lastErrorObject" : { - "connectionId" : 1, - "updatedExisting" : true, - "n" : 1, - "syncMillis" : 0, - "writtenTo" : null, - "err" : null, - "ok" : 1 - }, - "value" : value.clone(), - "ok" : 1 - }; - - let result = handle_response_test(&op, ok_response).unwrap(); - assert_eq!(result.unwrap(), value); -} - -#[test] -fn handle_null_value_replace() { - let op = empty_replace(); - let result = handle_response_test(&op, doc! { "ok": 1.0, "value": Bson::Null }).unwrap(); - assert_eq!(result, None); -} - -#[test] -fn handle_no_value_replace() { - let op = empty_replace(); - handle_response_test(&op, doc! { "ok": 1.0 }).unwrap_err(); -} - -// update tests - -fn empty_update() -> FindAndModify { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! {}; - let update = UpdateModifications::Document(doc! { "$x": { "$inc": 1 } }); - FindAndModify::with_update(ns, filter, update, None).unwrap() -} - -#[test] -fn build_with_update_hint() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "$x": { "$inc": 1 } }); - let options = FindOneAndUpdateOptions { - hint: Some(Hint::Keys(doc! { "x": 1, "y": -1 })), - upsert: Some(false), - bypass_document_validation: Some(true), - ..Default::default() - }; - - let mut op = - FindAndModify::::with_update(ns, filter.clone(), update.clone(), Some(options)) - .unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": update.to_bson(), - "upsert": false, - "bypassDocumentValidation": true, - "hint": { - "x": 1, - "y": -1, - }, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_update_no_options() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "$x": { "$inc": 1 } }); - let mut op = - FindAndModify::::with_update(ns, filter.clone(), update.clone(), None).unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": update.to_bson(), - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_with_update() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "$x": { "$inc": 1 } }); - let options = FindOneAndUpdateOptions { - upsert: Some(false), - bypass_document_validation: Some(true), - ..Default::default() - }; - - let mut op = - FindAndModify::::with_update(ns, filter.clone(), update.clone(), Some(options)) - .unwrap(); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "findAndModify"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "findAndModify": "test_coll", - "query": filter, - "update": update.to_bson(), - "upsert": false, - "bypassDocumentValidation": true - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn handle_success_update() { - let op = empty_update(); - let value = doc! { - "_id" : Bson::ObjectId(ObjectId::new()), - "name" : "Tom", - "state" : "active", - "rating" : 100, - "score" : 5 - }; - let ok_response = doc! { - "lastErrorObject" : { - "connectionId" : 1, - "updatedExisting" : true, - "n" : 1, - "syncMillis" : 0, - "writtenTo" : null, - "err" : null, - "ok" : 1 - }, - "value" : value.clone(), - "ok" : 1 - }; - - let result = handle_response_test(&op, ok_response).unwrap(); - assert_eq!(result.unwrap(), value); -} - -#[test] -fn handle_null_value_update() { - let op = empty_update(); - let result = handle_response_test(&op, doc! { "ok": 1.0, "value": Bson::Null }).unwrap(); - assert_eq!(result, None); -} - -#[test] -fn handle_no_value_update() { - let op = empty_update(); - handle_response_test(&op, doc! { "ok": 1.0 }).unwrap_err(); -} diff --git a/src/operation/get_more.rs b/src/operation/get_more.rs new file mode 100644 index 000000000..046cccbde --- /dev/null +++ b/src/operation/get_more.rs @@ -0,0 +1,122 @@ +use std::{collections::VecDeque, time::Duration}; + +use crate::{ + bson::{rawdoc, RawBson}, + bson_compat::{cstr, CStr}, +}; +use serde::Deserialize; + +use crate::{ + bson::{doc, Bson, RawDocumentBuf}, + change_stream::event::ResumeToken, + checked::Checked, + cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, + cursor::CursorInformation, + error::Result, + operation::OperationWithDefaults, + options::SelectionCriteria, + results::GetMoreResult, + Namespace, +}; + +use super::ExecutionContext; + +#[derive(Debug)] +pub(crate) struct GetMore<'conn> { + ns: Namespace, + cursor_id: i64, + selection_criteria: SelectionCriteria, + batch_size: Option, + max_time: Option, + pinned_connection: Option<&'conn PinnedConnectionHandle>, + comment: Option, +} + +impl<'conn> GetMore<'conn> { + pub(crate) fn new( + info: CursorInformation, + pinned: Option<&'conn PinnedConnectionHandle>, + ) -> Self { + Self { + ns: info.ns, + cursor_id: info.id, + selection_criteria: SelectionCriteria::from_address(info.address), + batch_size: info.batch_size, + max_time: info.max_time, + pinned_connection: pinned, + comment: info.comment, + } + } +} + +impl OperationWithDefaults for GetMore<'_> { + type O = GetMoreResult; + + const NAME: &'static CStr = cstr!("getMore"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.cursor_id, + "collection": self.ns.coll.clone(), + }; + + if let Some(batch_size) = self.batch_size { + let batch_size = Checked::from(batch_size).try_into::()?; + if batch_size != 0 { + body.append(cstr!("batchSize"), batch_size); + } + } + + if let Some(ref max_time) = self.max_time { + body.append( + cstr!("maxTimeMS"), + max_time.as_millis().try_into().unwrap_or(i32::MAX), + ); + } + + if let Some(comment) = &self.comment { + let raw_comment: RawBson = comment.clone().try_into()?; + body.append(cstr!("comment"), raw_comment); + } + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: GetMoreResponseBody = response.body()?; + + Ok(GetMoreResult { + batch: response.cursor.next_batch, + exhausted: response.cursor.id == 0, + post_batch_resume_token: ResumeToken::from_raw(response.cursor.post_batch_resume_token), + id: response.cursor.id, + ns: Namespace::from_str(response.cursor.ns.as_str()).unwrap(), + }) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + Some(&self.selection_criteria) + } + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + self.pinned_connection + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct GetMoreResponseBody { + cursor: NextBatchBody, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct NextBatchBody { + id: i64, + next_batch: VecDeque, + post_batch_resume_token: Option, + ns: String, +} diff --git a/src/operation/get_more/mod.rs b/src/operation/get_more/mod.rs deleted file mode 100644 index cf1762619..000000000 --- a/src/operation/get_more/mod.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[cfg(test)] -mod test; - -use std::{collections::VecDeque, time::Duration}; - -use bson::{Document, RawDocumentBuf}; -use serde::Deserialize; - -use crate::{ - bson::{doc, Bson}, - change_stream::event::ResumeToken, - cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, - cursor::CursorInformation, - error::{ErrorKind, Result}, - operation::OperationWithDefaults, - options::SelectionCriteria, - results::GetMoreResult, - Namespace, -}; - -#[derive(Debug)] -pub(crate) struct GetMore<'conn> { - ns: Namespace, - cursor_id: i64, - selection_criteria: SelectionCriteria, - batch_size: Option, - max_time: Option, - pinned_connection: Option<&'conn PinnedConnectionHandle>, - comment: Option, -} - -impl<'conn> GetMore<'conn> { - pub(crate) fn new( - info: CursorInformation, - pinned: Option<&'conn PinnedConnectionHandle>, - ) -> Self { - Self { - ns: info.ns, - cursor_id: info.id, - selection_criteria: SelectionCriteria::from_address(info.address), - batch_size: info.batch_size, - max_time: info.max_time, - pinned_connection: pinned, - comment: info.comment, - } - } -} - -impl<'conn> OperationWithDefaults for GetMore<'conn> { - type O = GetMoreResult; - type Command = Document; - - const NAME: &'static str = "getMore"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.cursor_id, - "collection": self.ns.coll.clone(), - }; - - if let Some(batch_size) = self.batch_size { - if batch_size > std::i32::MAX as u32 { - return Err(ErrorKind::InvalidArgument { - message: "The batch size must fit into a signed 32-bit integer".to_string(), - } - .into()); - } else if batch_size != 0 { - body.insert("batchSize", batch_size); - } - } - - if let Some(ref max_time) = self.max_time { - body.insert("maxTimeMS", max_time.as_millis() as i32); - } - - if let Some(ref comment) = self.comment { - body.insert("comment", comment); - } - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: GetMoreResponseBody = response.body()?; - - Ok(GetMoreResult { - batch: response.cursor.next_batch, - exhausted: response.cursor.id == 0, - post_batch_resume_token: ResumeToken::from_raw(response.cursor.post_batch_resume_token), - }) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - Some(&self.selection_criteria) - } - - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - self.pinned_connection - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct GetMoreResponseBody { - cursor: NextBatchBody, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct NextBatchBody { - id: i64, - next_batch: VecDeque, - post_batch_resume_token: Option, -} diff --git a/src/operation/get_more/test.rs b/src/operation/get_more/test.rs deleted file mode 100644 index d93b2a746..000000000 --- a/src/operation/get_more/test.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{ - cursor::CursorInformation, - operation::{GetMore, Operation}, - options::ServerAddress, - sdam::{ServerDescription, ServerInfo, ServerType}, - Namespace, -}; - -#[test] -fn op_selection_criteria() { - let address = ServerAddress::Tcp { - host: "myhost.com".to_string(), - port: Some(1234), - }; - - let info = CursorInformation { - ns: Namespace::empty(), - address: address.clone(), - id: 123, - batch_size: None, - max_time: None, - comment: None, - }; - let get_more = GetMore::new(info, None); - let server_description = ServerDescription { - address, - server_type: ServerType::Unknown, - reply: Ok(None), - last_update_time: None, - average_round_trip_time: None, - }; - let server_info = ServerInfo::new_borrowed(&server_description); - - let predicate = get_more - .selection_criteria() - .expect("should not be none") - .as_predicate() - .expect("should be predicate"); - assert!(predicate(&server_info)); - - let server_description = ServerDescription { - address: ServerAddress::default(), - ..server_description - }; - let server_info = ServerInfo::new_borrowed(&server_description); - assert!(!predicate(&server_info)); -} diff --git a/src/operation/insert.rs b/src/operation/insert.rs new file mode 100644 index 000000000..d42ef5b47 --- /dev/null +++ b/src/operation/insert.rs @@ -0,0 +1,181 @@ +use std::collections::HashMap; + +use crate::{ + bson::{rawdoc, Bson, RawDocument}, + bson_compat::{cstr, CStr}, + bson_util::{ + array_entry_size_bytes, + extend_raw_document_buf, + get_or_prepend_id_field, + vec_to_raw_array_buf, + }, + checked::Checked, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::{Error, ErrorKind, InsertManyError, Result}, + operation::{OperationWithDefaults, Retryability, WriteResponseBody}, + options::{InsertManyOptions, WriteConcern}, + results::InsertManyResult, + Namespace, +}; + +use super::{ExecutionContext, MAX_ENCRYPTED_WRITE_SIZE, OP_MSG_OVERHEAD_BYTES}; + +#[derive(Debug)] +pub(crate) struct Insert<'a> { + ns: Namespace, + documents: Vec<&'a RawDocument>, + inserted_ids: Vec, + options: InsertManyOptions, + encrypted: bool, +} + +impl<'a> Insert<'a> { + pub(crate) fn new( + ns: Namespace, + documents: Vec<&'a RawDocument>, + options: Option, + encrypted: bool, + ) -> Self { + let mut options = options.unwrap_or_default(); + if options.ordered.is_none() { + options.ordered = Some(true); + } + + Self { + ns, + options, + documents, + inserted_ids: vec![], + encrypted, + } + } +} + +impl OperationWithDefaults for Insert<'_> { + type O = InsertManyResult; + + const NAME: &'static CStr = cstr!("insert"); + + fn build(&mut self, description: &StreamDescription) -> Result { + self.inserted_ids.clear(); + + let max_doc_size: usize = Checked::new(description.max_bson_object_size).try_into()?; + let max_message_size: usize = + Checked::new(description.max_message_size_bytes).try_into()?; + let max_operations: usize = Checked::new(description.max_write_batch_size).try_into()?; + + let mut command_body = rawdoc! { Self::NAME: self.ns.coll.clone() }; + let options = crate::bson_compat::serialize_to_raw_document_buf(&self.options)?; + extend_raw_document_buf(&mut command_body, options)?; + + let max_document_sequence_size: usize = (Checked::new(max_message_size) + - OP_MSG_OVERHEAD_BYTES + - command_body.as_bytes().len()) + .try_into()?; + + let mut docs = Vec::new(); + let mut current_size = Checked::new(0); + for (i, document) in self.documents.iter().take(max_operations).enumerate() { + let mut document = crate::bson_compat::serialize_to_raw_document_buf(document)?; + let id = get_or_prepend_id_field(&mut document)?; + + let doc_size = document.as_bytes().len(); + if doc_size > max_doc_size { + return Err(ErrorKind::InvalidArgument { + message: format!( + "insert document must be within {} bytes, but document provided is {} \ + bytes", + max_doc_size, doc_size + ), + } + .into()); + } + + // From the spec: Drivers MUST not reduce the size limits for a single write before + // automatic encryption. I.e. if a single document has size larger than 2MiB (but less + // than `maxBsonObjectSize`) proceed with automatic encryption. + if self.encrypted { + let doc_entry_size = array_entry_size_bytes(i, document.as_bytes().len())?; + current_size += doc_entry_size; + if i != 0 && current_size.get()? >= MAX_ENCRYPTED_WRITE_SIZE { + break; + } + } else { + current_size += doc_size; + if current_size.get()? > max_document_sequence_size { + break; + } + } + + self.inserted_ids.push(id); + docs.push(document); + } + + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + let options_doc = crate::bson_compat::serialize_to_raw_document_buf(&self.options)?; + extend_raw_document_buf(&mut body, options_doc)?; + + if self.encrypted { + // Auto-encryption does not support document sequences + body.append(cstr!("documents"), vec_to_raw_array_buf(docs)); + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } else { + let mut command = Command::new(Self::NAME, &self.ns.db, body); + command.add_document_sequence("documents", docs); + Ok(command) + } + } + + fn handle_response<'b>( + &'b self, + response: RawCommandResponse, + _context: ExecutionContext<'b>, + ) -> Result { + let response: WriteResponseBody = response.body()?; + let response_n = Checked::::try_from(response.n)?; + + let mut map = HashMap::new(); + if self.options.ordered == Some(true) { + // in ordered inserts, only the first n were attempted. + for (i, id) in self.inserted_ids.iter().enumerate().take(response_n.get()?) { + map.insert(i, id.clone()); + } + } else { + // for unordered, add all the attempted ids and then remove the ones that have + // associated write errors. + for (i, id) in self.inserted_ids.iter().enumerate() { + map.insert(i, id.clone()); + } + + if let Some(write_errors) = response.write_errors.as_ref() { + for err in write_errors { + map.remove(&err.index); + } + } + } + + if response.write_errors.is_some() || response.write_concern_error.is_some() { + return Err(Error::new( + ErrorKind::InsertMany(InsertManyError { + write_errors: response.write_errors, + write_concern_error: response.write_concern_error, + inserted_ids: map, + }), + response.labels, + )); + } + + Ok(InsertManyResult { inserted_ids: map }) + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options.write_concern.as_ref() + } + + fn retryability(&self) -> Retryability { + Retryability::Write + } +} diff --git a/src/operation/insert/mod.rs b/src/operation/insert/mod.rs deleted file mode 100644 index 90307e8a7..000000000 --- a/src/operation/insert/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -#[cfg(test)] -mod test; - -use std::{collections::HashMap, convert::TryInto}; - -use bson::{oid::ObjectId, Bson, RawArrayBuf, RawDocumentBuf}; -use serde::Serialize; - -use crate::{ - bson::doc, - bson_util, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::{BulkWriteFailure, Error, ErrorKind, Result}, - operation::{ - remove_empty_write_concern, - OperationWithDefaults, - Retryability, - WriteResponseBody, - }, - options::{InsertManyOptions, WriteConcern}, - results::InsertManyResult, - Namespace, -}; - -use super::CommandBody; - -#[derive(Debug)] -pub(crate) struct Insert<'a, T> { - ns: Namespace, - documents: Vec<&'a T>, - inserted_ids: Vec, - options: Option, - encrypted: bool, -} - -impl<'a, T> Insert<'a, T> { - pub(crate) fn new( - ns: Namespace, - documents: Vec<&'a T>, - options: Option, - ) -> Self { - Self::new_encrypted(ns, documents, options, false) - } - - pub(crate) fn new_encrypted( - ns: Namespace, - documents: Vec<&'a T>, - options: Option, - encrypted: bool, - ) -> Self { - Self { - ns, - options, - documents, - inserted_ids: vec![], - encrypted, - } - } - - fn is_ordered(&self) -> bool { - self.options - .as_ref() - .and_then(|o| o.ordered) - .unwrap_or(true) - } -} - -impl<'a, T: Serialize> OperationWithDefaults for Insert<'a, T> { - type O = InsertManyResult; - type Command = InsertCommand; - - const NAME: &'static str = "insert"; - - fn build(&mut self, description: &StreamDescription) -> Result> { - let mut docs = RawArrayBuf::new(); - let mut size = 0; - let batch_size_limit = description.max_bson_object_size as u64; - - for (i, d) in self - .documents - .iter() - .take(description.max_write_batch_size as usize) - .enumerate() - { - let mut doc = bson::to_raw_document_buf(d)?; - let id = match doc.get("_id")? { - Some(b) => b.try_into()?, - None => { - let mut new_doc = RawDocumentBuf::new(); - let oid = ObjectId::new(); - new_doc.append("_id", oid); - - let mut new_bytes = new_doc.into_bytes(); - new_bytes.pop(); // remove trailing null byte - - let mut bytes = doc.into_bytes(); - let oid_slice = &new_bytes[4..]; - // insert oid at beginning of document - bytes.splice(4..4, oid_slice.iter().cloned()); - - // overwrite old length - let new_length = (bytes.len() as i32).to_le_bytes(); - bytes[0..4].copy_from_slice(&new_length); - doc = RawDocumentBuf::from_bytes(bytes)?; - - Bson::ObjectId(oid) - } - }; - - let doc_size = bson_util::array_entry_size_bytes(i, doc.as_bytes().len()); - - if self.encrypted && size > 0 && size + doc_size >= 2_097_152 { - break; - } - if size + doc_size <= batch_size_limit { - if self.inserted_ids.len() <= i { - self.inserted_ids.push(id); - } - docs.push(doc); - size += doc_size; - } else { - break; - } - } - - if docs.is_empty() { - return Err(ErrorKind::InvalidArgument { - message: "document exceeds maxBsonObjectSize".to_string(), - } - .into()); - } - let mut options = self.options.clone().unwrap_or_default(); - options.ordered = Some(self.is_ordered()); - remove_empty_write_concern!(Some(&mut options)); - - let body = InsertCommand { - insert: self.ns.coll.clone(), - documents: docs, - options, - }; - - Ok(Command::new("insert".to_string(), self.ns.db.clone(), body)) - } - - fn serialize_command(&mut self, cmd: Command) -> Result> { - let mut doc = bson::to_raw_document_buf(&cmd)?; - // need to append documents separately because #[serde(flatten)] breaks the custom - // serialization logic. See https://github.com/serde-rs/serde/issues/2106. - doc.append("documents", cmd.body.documents); - Ok(doc.into_bytes()) - } - - fn handle_response( - &self, - raw_response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteResponseBody = raw_response.body_utf8_lossy()?; - - let mut map = HashMap::new(); - if self.is_ordered() { - // in ordered inserts, only the first n were attempted. - for (i, id) in self - .inserted_ids - .iter() - .enumerate() - .take(response.n as usize) - { - map.insert(i, id.clone()); - } - } else { - // for unordered, add all the attempted ids and then remove the ones that have - // associated write errors. - for (i, id) in self.inserted_ids.iter().enumerate() { - map.insert(i, id.clone()); - } - - if let Some(write_errors) = response.write_errors.as_ref() { - for err in write_errors { - map.remove(&err.index); - } - } - } - - if response.write_errors.is_some() || response.write_concern_error.is_some() { - return Err(Error::new( - ErrorKind::BulkWrite(BulkWriteFailure { - write_errors: response.write_errors, - write_concern_error: response.write_concern_error, - inserted_ids: map, - }), - response.labels, - )); - } - - Ok(InsertManyResult { inserted_ids: map }) - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options.as_ref().and_then(|o| o.write_concern.as_ref()) - } - - fn retryability(&self) -> Retryability { - Retryability::Write - } -} - -#[derive(Serialize)] -pub(crate) struct InsertCommand { - insert: String, - - /// will be serialized in `serialize_command` - #[serde(skip)] - documents: RawArrayBuf, - - #[serde(flatten)] - options: InsertManyOptions, -} - -impl CommandBody for InsertCommand {} diff --git a/src/operation/insert/test.rs b/src/operation/insert/test.rs deleted file mode 100644 index 4e2b02ad8..000000000 --- a/src/operation/insert/test.rs +++ /dev/null @@ -1,360 +0,0 @@ -use bson::{ - oid::ObjectId, - spec::BinarySubtype, - Binary, - DateTime, - JavaScriptCodeWithScope, - Regex, - Timestamp, -}; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; - -use crate::{ - bson::{doc, Bson, Document}, - cmap::StreamDescription, - concern::WriteConcern, - error::{BulkWriteError, ErrorKind, WriteConcernError}, - operation::{test::handle_response_test, Insert, Operation}, - options::InsertManyOptions, - Namespace, -}; - -struct TestFixtures { - op: Insert<'static, Document>, - documents: Vec, - options: InsertManyOptions, -} - -/// Get an Insert operation and the documents/options used to construct it. -fn fixtures(opts: Option) -> TestFixtures { - lazy_static! { - static ref DOCUMENTS: Vec = vec![ - Document::new(), - doc! {"_id": 1234, "a": 1}, - doc! {"a": 123, "b": "hello world" }, - ]; - } - - let options = opts.unwrap_or(InsertManyOptions { - ordered: Some(true), - write_concern: Some(WriteConcern::builder().journal(true).build()), - ..Default::default() - }); - - let op = Insert::new( - Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }, - DOCUMENTS.iter().collect(), - Some(options.clone()), - ); - - TestFixtures { - op, - documents: DOCUMENTS.clone(), - options, - } -} - -#[test] -fn build() { - let mut fixtures = fixtures(None); - - let description = StreamDescription::new_testing(); - let cmd = fixtures.op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "insert"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - assert_eq!(cmd.body.insert, "test_coll".to_string()); - - let mut cmd_docs: Vec = cmd - .body - .documents - .as_ref() - .into_iter() - .map(|b| Document::from_reader(b.unwrap().as_document().unwrap().as_bytes()).unwrap()) - .collect(); - assert_eq!(cmd_docs.len(), fixtures.documents.len()); - - for (original_doc, cmd_doc) in fixtures.documents.iter().zip(cmd_docs.iter_mut()) { - assert!(cmd_doc.get("_id").is_some()); - if original_doc.get("_id").is_none() { - cmd_doc.remove("_id"); - } - assert_eq!(original_doc, cmd_doc); - } - - let serialized = fixtures.op.serialize_command(cmd).unwrap(); - let cmd_doc = Document::from_reader(serialized.as_slice()).unwrap(); - - assert_eq!( - cmd_doc.get("ordered"), - fixtures.options.ordered.map(Bson::Boolean).as_ref() - ); - assert_eq!( - cmd_doc.get("bypassDocumentValidation"), - fixtures - .options - .bypass_document_validation - .map(Bson::Boolean) - .as_ref() - ); - assert_eq!( - cmd_doc.get("writeConcern"), - fixtures - .options - .write_concern - .as_ref() - .map(|wc| bson::to_bson(wc).unwrap()) - .as_ref() - ); -} - -#[test] -fn build_ordered() { - let docs = vec![Document::new()]; - let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None); - let cmd = insert - .build(&StreamDescription::new_testing()) - .expect("should succeed"); - let serialized = insert.serialize_command(cmd).unwrap(); - let cmd_doc = Document::from_reader(serialized.as_slice()).unwrap(); - assert_eq!(cmd_doc.get("ordered"), Some(&Bson::Boolean(true))); - - let mut insert = Insert::new( - Namespace::empty(), - docs.iter().collect(), - Some(InsertManyOptions::builder().ordered(false).build()), - ); - let cmd = insert - .build(&StreamDescription::new_testing()) - .expect("should succeed"); - let serialized = insert.serialize_command(cmd).unwrap(); - let cmd_doc = Document::from_reader(serialized.as_slice()).unwrap(); - assert_eq!(cmd_doc.get("ordered"), Some(&Bson::Boolean(false))); - - let mut insert = Insert::new( - Namespace::empty(), - docs.iter().collect(), - Some(InsertManyOptions::builder().ordered(true).build()), - ); - let cmd = insert - .build(&StreamDescription::new_testing()) - .expect("should succeed"); - let serialized = insert.serialize_command(cmd).unwrap(); - let cmd_doc = Document::from_reader(serialized.as_slice()).unwrap(); - assert_eq!(cmd_doc.get("ordered"), Some(&Bson::Boolean(true))); - - let mut insert = Insert::new( - Namespace::empty(), - docs.iter().collect(), - Some(InsertManyOptions::builder().build()), - ); - let cmd = insert - .build(&StreamDescription::new_testing()) - .expect("should succeed"); - let serialized = insert.serialize_command(cmd).unwrap(); - let cmd_doc = Document::from_reader(serialized.as_slice()).unwrap(); - assert_eq!(cmd_doc.get("ordered"), Some(&Bson::Boolean(true))); -} - -#[derive(Debug, Serialize, Deserialize)] -struct Documents { - documents: Vec, -} - -#[test] -fn generate_ids() { - let docs = vec![doc! { "x": 1 }, doc! { "_id": 1_i32, "x": 2 }]; - - let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None); - let cmd = insert.build(&StreamDescription::new_testing()).unwrap(); - let serialized = insert.serialize_command(cmd).unwrap(); - - #[derive(Debug, Serialize, Deserialize)] - struct D { - x: i32, - - #[serde(rename = "_id")] - id: Bson, - } - - let docs: Documents = bson::from_slice(serialized.as_slice()).unwrap(); - - assert_eq!(docs.documents.len(), 2); - let docs = docs.documents; - - docs[0].id.as_object_id().unwrap(); - assert_eq!(docs[0].x, 1); - - assert_eq!(docs[1].id, Bson::Int32(1)); - assert_eq!(docs[1].x, 2); - - // ensure the _id was prepended to the document - let docs: Documents = bson::from_slice(serialized.as_slice()).unwrap(); - assert_eq!(docs.documents[0].iter().next().unwrap().0, "_id") -} - -#[test] -fn serialize_all_types() { - let binary = Binary { - bytes: vec![36, 36, 36], - subtype: BinarySubtype::Generic, - }; - let date = DateTime::now(); - let regex = Regex { - pattern: "hello".to_string(), - options: "x".to_string(), - }; - let timestamp = Timestamp { - time: 123, - increment: 456, - }; - let code = Bson::JavaScriptCode("console.log(1)".to_string()); - let code_w_scope = JavaScriptCodeWithScope { - code: "console.log(a)".to_string(), - scope: doc! { "a": 1 }, - }; - let oid = ObjectId::new(); - let subdoc = doc! { "k": true, "b": { "hello": "world" } }; - - let decimal = { - let bytes = hex::decode("18000000136400D0070000000000000000000000003A3000").unwrap(); - let d = Document::from_reader(bytes.as_slice()).unwrap(); - d.get("d").unwrap().clone() - }; - - let docs = vec![doc! { - "x": 1_i32, - "y": 2_i64, - "s": "oke", - "array": [ true, "oke", { "12": 24 } ], - "bson": 1234.5, - "oid": oid, - "null": Bson::Null, - "subdoc": subdoc, - "b": true, - "d": 12.5, - "binary": binary, - "date": date, - "regex": regex, - "ts": timestamp, - "i": { "a": 300, "b": 12345 }, - "undefined": Bson::Undefined, - "code": code, - "code_w_scope": code_w_scope, - "decimal": decimal, - "symbol": Bson::Symbol("ok".to_string()), - "min_key": Bson::MinKey, - "max_key": Bson::MaxKey, - "_id": ObjectId::new(), - }]; - - let mut insert = Insert::new(Namespace::empty(), docs.iter().collect(), None); - let cmd = insert.build(&StreamDescription::new_testing()).unwrap(); - let serialized = insert.serialize_command(cmd).unwrap(); - let cmd: Documents = bson::from_slice(serialized.as_slice()).unwrap(); - - assert_eq!(cmd.documents, docs); -} - -#[test] -fn handle_success() { - let mut fixtures = fixtures(None); - - // populate _id for documents that don't provide it - fixtures - .op - .build(&StreamDescription::new_testing()) - .unwrap(); - let response = handle_response_test(&fixtures.op, doc! { "ok": 1.0, "n": 3 }).unwrap(); - let inserted_ids = response.inserted_ids; - assert_eq!(inserted_ids.len(), 3); - assert_eq!( - inserted_ids.get(&1).unwrap(), - fixtures.documents[1].get("_id").unwrap() - ); -} - -#[test] -fn handle_invalid_response() { - let fixtures = fixtures(None); - handle_response_test(&fixtures.op, doc! { "ok": 1.0, "asdfadsf": 123123 }).unwrap_err(); -} - -#[test] -fn handle_write_failure() { - let mut fixtures = fixtures(None); - - // generate _id for operations missing it. - let _ = fixtures - .op - .build(&StreamDescription::new_testing()) - .unwrap(); - - let write_error_response = doc! { - "ok": 1.0, - "n": 1, - "writeErrors": [ - { - "index": 1, - "code": 11000, - "errmsg": "duplicate key", - "errInfo": { - "test key": "test value", - } - } - ], - "writeConcernError": { - "code": 123, - "codeName": "woohoo", - "errmsg": "error message", - "errInfo": { - "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } - } - } - }; - - let write_error_response = - handle_response_test(&fixtures.op, write_error_response).unwrap_err(); - match *write_error_response.kind { - ErrorKind::BulkWrite(bwe) => { - let write_errors = bwe.write_errors.expect("write errors should be present"); - assert_eq!(write_errors.len(), 1); - let expected_err = BulkWriteError { - index: 1, - code: 11000, - code_name: None, - message: "duplicate key".to_string(), - details: Some(doc! { "test key": "test value" }), - }; - assert_eq!(write_errors.first().unwrap(), &expected_err); - - let write_concern_error = bwe - .write_concern_error - .expect("write concern error should be present"); - let expected_wc_err = WriteConcernError { - code: 123, - code_name: "woohoo".to_string(), - message: "error message".to_string(), - details: Some(doc! { "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } }), - labels: vec![], - }; - assert_eq!(write_concern_error, expected_wc_err); - - assert_eq!(bwe.inserted_ids.len(), 1); - } - e => panic!("expected bulk write error, got {:?}", e), - }; -} diff --git a/src/operation/list_collections.rs b/src/operation/list_collections.rs new file mode 100644 index 000000000..eaad8a0a9 --- /dev/null +++ b/src/operation/list_collections.rs @@ -0,0 +1,84 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + cursor::CursorSpecification, + error::Result, + operation::{CursorBody, OperationWithDefaults, Retryability}, + options::{ListCollectionsOptions, ReadPreference, SelectionCriteria}, +}; + +use super::{append_options_to_raw_document, ExecutionContext}; + +#[derive(Debug)] +pub(crate) struct ListCollections { + db: String, + name_only: bool, + options: Option, +} + +impl ListCollections { + pub(crate) fn new( + db: String, + name_only: bool, + options: Option, + ) -> Self { + Self { + db, + name_only, + options, + } + } +} + +impl OperationWithDefaults for ListCollections { + type O = CursorSpecification; + + const NAME: &'static CStr = cstr!("listCollections"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: 1, + }; + + let mut name_only = self.name_only; + if let Some(filter) = self.options.as_ref().and_then(|o| o.filter.as_ref()) { + if name_only && filter.keys().any(|k| k != "name") { + name_only = false; + } + } + body.append(cstr!("nameOnly"), name_only); + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> Result { + let response: CursorBody = response.body()?; + Ok(CursorSpecification::new( + response.cursor, + context + .connection + .stream_description()? + .server_address + .clone(), + self.options.as_ref().and_then(|opts| opts.batch_size), + None, + None, + )) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } +} diff --git a/src/operation/list_collections/mod.rs b/src/operation/list_collections/mod.rs deleted file mode 100644 index 8d1212642..000000000 --- a/src/operation/list_collections/mod.rs +++ /dev/null @@ -1,90 +0,0 @@ -#[cfg(test)] -mod test; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - cursor::CursorSpecification, - error::Result, - operation::{append_options, CursorBody, OperationWithDefaults, Retryability}, - options::{ListCollectionsOptions, ReadPreference, SelectionCriteria}, -}; - -#[derive(Debug)] -pub(crate) struct ListCollections { - db: String, - filter: Option, - name_only: bool, - options: Option, -} - -impl ListCollections { - #[cfg(test)] - fn empty() -> Self { - Self::new(String::new(), None, false, None) - } - - pub(crate) fn new( - db: String, - filter: Option, - name_only: bool, - options: Option, - ) -> Self { - Self { - db, - filter, - name_only, - options, - } - } -} - -impl OperationWithDefaults for ListCollections { - type O = CursorSpecification; - type Command = Document; - - const NAME: &'static str = "listCollections"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: 1, - }; - - let mut name_only = self.name_only; - if let Some(ref filter) = self.filter { - body.insert("filter", filter.clone()); - - if name_only && filter.keys().any(|k| k != "name") { - name_only = false; - } - } - body.insert("nameOnly", name_only); - - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new(Self::NAME.to_string(), self.db.clone(), body)) - } - - fn handle_response( - &self, - raw_response: RawCommandResponse, - description: &StreamDescription, - ) -> Result { - let response: CursorBody = raw_response.body()?; - Ok(CursorSpecification::new( - response.cursor, - description.server_address.clone(), - self.options.as_ref().and_then(|opts| opts.batch_size), - None, - None, - )) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } -} diff --git a/src/operation/list_collections/test.rs b/src/operation/list_collections/test.rs deleted file mode 100644 index a1ed84478..000000000 --- a/src/operation/list_collections/test.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::{ - bson::{doc, Document}, - bson_util, - cmap::StreamDescription, - operation::{test::handle_response_test, ListCollections, Operation}, - options::ListCollectionsOptions, -}; - -fn build_test(db_name: &str, mut list_collections: ListCollections, mut expected_body: Document) { - let mut cmd = list_collections - .build(&StreamDescription::new_testing()) - .expect("build should succeed"); - assert_eq!(cmd.name, "listCollections"); - assert_eq!(cmd.target_db, db_name); - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build() { - let list_collections = ListCollections::new("test_db".to_string(), None, false, None); - let expected_body = doc! { - "listCollections": 1, - "nameOnly": false, - }; - build_test("test_db", list_collections, expected_body); - - let filter = doc! { "x": 1 }; - let list_collections = - ListCollections::new("test_db".to_string(), Some(filter.clone()), false, None); - let expected_body = doc! { - "listCollections": 1, - "nameOnly": false, - "filter": filter - }; - build_test("test_db", list_collections, expected_body); -} - -#[test] -fn build_name_only() { - let list_collections = ListCollections::new("test_db".to_string(), None, true, None); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "nameOnly": true - }, - ); - - let list_collections = ListCollections::new("test_db".to_string(), None, false, None); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "nameOnly": false - }, - ); - - // flip nameOnly if filter has non-name fields - let filter = doc! { "x": 3 }; - let list_collections = - ListCollections::new("test_db".to_string(), Some(filter.clone()), true, None); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "filter": filter, - "nameOnly": false, - }, - ); - - // don't flip if filter is name only. - let filter = doc! { "name": "cat" }; - let list_collections = - ListCollections::new("test_db".to_string(), Some(filter.clone()), true, None); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "filter": filter, - "nameOnly": true, - }, - ); -} - -#[test] -fn build_batch_size() { - let list_collections = ListCollections::new("test_db".to_string(), None, true, None); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "nameOnly": true, - }, - ); - - let options = ListCollectionsOptions::builder().batch_size(123).build(); - let list_collections = ListCollections::new("test_db".to_string(), None, true, Some(options)); - build_test( - "test_db", - list_collections, - doc! { - "listCollections": 1, - "nameOnly": true, - "cursor": { - "batchSize": 123 - } - }, - ); -} - -#[test] -fn op_selection_criteria() { - assert!(ListCollections::empty() - .selection_criteria() - .expect("should have criteria") - .is_read_pref_primary()); -} - -#[test] -fn handle_invalid_response() { - let list_collections = ListCollections::empty(); - - let garbled = doc! { "asdfasf": "ASdfasdf" }; - handle_response_test(&list_collections, garbled).expect_err("garbled response should fail"); - - let missing_cursor_field = doc! { - "cursor": { - "ns": "test.test", - "firstBatch": [], - } - }; - handle_response_test(&list_collections, missing_cursor_field) - .expect_err("missing cursor field should fail"); -} diff --git a/src/operation/list_databases.rs b/src/operation/list_databases.rs new file mode 100644 index 000000000..cf5b67d02 --- /dev/null +++ b/src/operation/list_databases.rs @@ -0,0 +1,65 @@ +use crate::bson::rawdoc; +use serde::Deserialize; + +use crate::{ + bson::{doc, RawDocumentBuf}, + bson_compat::{cstr, CStr}, + cmap::{Command, RawCommandResponse, StreamDescription}, + db::options::ListDatabasesOptions, + error::Result, + operation::{OperationWithDefaults, Retryability}, + selection_criteria::{ReadPreference, SelectionCriteria}, +}; + +use super::{append_options_to_raw_document, ExecutionContext}; + +#[derive(Debug)] +pub(crate) struct ListDatabases { + name_only: bool, + options: Option, +} + +impl ListDatabases { + pub fn new(name_only: bool, options: Option) -> Self { + ListDatabases { name_only, options } + } +} + +impl OperationWithDefaults for ListDatabases { + type O = Vec; + + const NAME: &'static CStr = cstr!("listDatabases"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: 1, + "nameOnly": self.name_only + }; + + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, "admin", body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: Response = response.body()?; + Ok(response.databases) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct Response { + databases: Vec, +} diff --git a/src/operation/list_databases/mod.rs b/src/operation/list_databases/mod.rs deleted file mode 100644 index 8360fd78f..000000000 --- a/src/operation/list_databases/mod.rs +++ /dev/null @@ -1,92 +0,0 @@ -#[cfg(test)] -mod test; - -use bson::RawDocumentBuf; -use serde::Deserialize; - -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::Result, - operation::{append_options, OperationWithDefaults, Retryability}, - options::ListDatabasesOptions, - selection_criteria::{ReadPreference, SelectionCriteria}, -}; - -#[derive(Debug)] -pub(crate) struct ListDatabases { - filter: Option, - name_only: bool, - options: Option, -} - -impl ListDatabases { - pub fn new( - filter: Option, - name_only: bool, - options: Option, - ) -> Self { - ListDatabases { - filter, - name_only, - options, - } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - ListDatabases { - filter: None, - name_only: false, - options: None, - } - } -} - -impl OperationWithDefaults for ListDatabases { - type O = Vec; - type Command = Document; - - const NAME: &'static str = "listDatabases"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body: Document = doc! { - Self::NAME: 1, - "nameOnly": self.name_only - }; - - if let Some(ref filter) = self.filter { - body.insert("filter", filter.clone()); - } - - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - "admin".to_string(), - body, - )) - } - - fn handle_response( - &self, - raw_response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: Response = raw_response.body()?; - Ok(response.databases) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct Response { - databases: Vec, -} diff --git a/src/operation/list_databases/test.rs b/src/operation/list_databases/test.rs deleted file mode 100644 index 2341923fe..000000000 --- a/src/operation/list_databases/test.rs +++ /dev/null @@ -1,147 +0,0 @@ -use bson::RawDocumentBuf; - -use crate::{ - bson::{doc, Bson}, - cmap::StreamDescription, - error::ErrorKind, - operation::{test::handle_response_test, ListDatabases, Operation}, - options::ListDatabasesOptions, - selection_criteria::ReadPreference, -}; - -#[test] -fn build() { - let mut list_databases_op = ListDatabases::empty(); - let list_databases_command = list_databases_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - assert_eq!( - list_databases_command.body, - doc! { - "listDatabases": 1, - "nameOnly": false - } - ); - assert_eq!(list_databases_command.target_db, "admin"); -} - -#[test] -fn build_with_name_only() { - let name_only = true; - - let mut list_databases_op = ListDatabases::new(None, name_only, None); - let list_databases_command = list_databases_op - .build(&StreamDescription::new_testing()) - .expect("error on build"); - - assert_eq!( - list_databases_command.body, - doc! { - "listDatabases": 1, - "nameOnly": name_only - } - ); - assert_eq!(list_databases_command.target_db, "admin"); -} - -#[test] -fn build_with_filter() { - let filter = doc! {"something" : "something else"}; - - let mut list_databases_op = ListDatabases::new(Some(filter.clone()), false, None); - let list_databases_command = list_databases_op - .build(&StreamDescription::new_testing()) - .unwrap(); - assert_eq!( - list_databases_command.body, - doc! { - "listDatabases": 1, - "nameOnly": false, - "filter": Bson::Document(filter) - } - ); - assert_eq!(list_databases_command.target_db, "admin"); -} - -#[test] -fn build_with_options() { - let options = ListDatabasesOptions::builder() - .authorized_databases(true) - .build(); - - let mut list_databases_op = ListDatabases::new(None, false, Some(options)); - let list_databases_command = list_databases_op - .build(&StreamDescription::new_testing()) - .unwrap(); - assert_eq!( - list_databases_command.body, - doc! { - "listDatabases": 1, - "nameOnly": false, - "authorizedDatabases": true - } - ); - assert_eq!(list_databases_command.target_db, "admin"); -} - -#[test] -fn handle_success() { - let total_size = 251658240; - - let databases = vec![ - doc! { - "name" : "admin", - "sizeOnDisk" : 83886080, - "empty" : false - }, - doc! { - "name" : "local", - "sizeOnDisk" : 83886080, - "empty" : false - }, - doc! { - "name" : "test", - "sizeOnDisk" : 83886080, - "empty" : false - }, - ]; - - let raw_databases = databases - .iter() - .map(|d| RawDocumentBuf::from_document(d).unwrap()) - .collect::>(); - - let actual_values = handle_response_test( - &ListDatabases::empty(), - doc! { - "databases" : databases, - "totalSize" : total_size, - "ok" : 1 - }, - ) - .expect("should succeed"); - - assert_eq!(actual_values, raw_databases); -} - -#[test] -fn handle_response_no_databases() { - let result = handle_response_test(&ListDatabases::empty(), doc! { "ok": 1 }); - match result.map_err(|e| *e.kind) { - Err(ErrorKind::InvalidResponse { .. }) => {} - other => panic!("expected response error, but got {:?}", other), - } -} - -#[test] -fn op_selection_criteria() { - let list_databases_op = ListDatabases::empty(); - assert_eq!( - *list_databases_op - .selection_criteria() - .unwrap() - .as_read_pref() - .unwrap(), - ReadPreference::Primary - ); -} diff --git a/src/operation/list_indexes.rs b/src/operation/list_indexes.rs new file mode 100644 index 000000000..591c32d18 --- /dev/null +++ b/src/operation/list_indexes.rs @@ -0,0 +1,72 @@ +use crate::bson::rawdoc; + +use crate::{ + bson_compat::{cstr, CStr}, + checked::Checked, + cmap::{Command, RawCommandResponse, StreamDescription}, + cursor::CursorSpecification, + error::Result, + operation::OperationWithDefaults, + options::ListIndexesOptions, + selection_criteria::{ReadPreference, SelectionCriteria}, + Namespace, +}; + +use super::{append_options_to_raw_document, CursorBody, ExecutionContext, Retryability}; + +pub(crate) struct ListIndexes { + ns: Namespace, + options: Option, +} + +impl ListIndexes { + pub(crate) fn new(ns: Namespace, options: Option) -> Self { + ListIndexes { ns, options } + } +} + +impl OperationWithDefaults for ListIndexes { + type O = CursorSpecification; + + const NAME: &'static CStr = cstr!("listIndexes"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + if let Some(size) = self.options.as_ref().and_then(|o| o.batch_size) { + let size = Checked::from(size).try_into::()?; + body.append(cstr!("cursor"), rawdoc! { "batchSize": size }); + } + append_options_to_raw_document(&mut body, self.options.as_ref())?; + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> Result { + let response: CursorBody = response.body()?; + Ok(CursorSpecification::new( + response.cursor, + context + .connection + .stream_description()? + .server_address + .clone(), + self.options.as_ref().and_then(|o| o.batch_size), + self.options.as_ref().and_then(|o| o.max_time), + None, + )) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() + } + + fn retryability(&self) -> Retryability { + Retryability::Read + } +} diff --git a/src/operation/list_indexes/mod.rs b/src/operation/list_indexes/mod.rs deleted file mode 100644 index b8986816a..000000000 --- a/src/operation/list_indexes/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - bson::{doc, Document}, - cmap::{Command, RawCommandResponse, StreamDescription}, - cursor::CursorSpecification, - error::Result, - operation::{append_options, OperationWithDefaults}, - options::ListIndexesOptions, - selection_criteria::{ReadPreference, SelectionCriteria}, - Namespace, -}; - -use super::{CursorBody, Retryability}; - -#[cfg(test)] -mod test; - -pub(crate) struct ListIndexes { - ns: Namespace, - options: Option, -} - -impl ListIndexes { - pub(crate) fn new(ns: Namespace, options: Option) -> Self { - ListIndexes { ns, options } - } - - #[cfg(test)] - pub(crate) fn empty() -> Self { - Self { - ns: Namespace { - db: String::new(), - coll: String::new(), - }, - options: None, - } - } -} - -impl OperationWithDefaults for ListIndexes { - type O = CursorSpecification; - type Command = Document; - - const NAME: &'static str = "listIndexes"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - "listIndexes": self.ns.coll.clone(), - }; - if let Some(size) = self.options.as_ref().and_then(|o| o.batch_size) { - body.insert("cursor", doc! { "batchSize": size }); - } - append_options(&mut body, self.options.as_ref())?; - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - raw_response: RawCommandResponse, - description: &StreamDescription, - ) -> Result { - let response: CursorBody = raw_response.body()?; - Ok(CursorSpecification::new( - response.cursor, - description.server_address.clone(), - self.options.as_ref().and_then(|o| o.batch_size), - self.options.as_ref().and_then(|o| o.max_time), - None, - )) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - Some(SelectionCriteria::ReadPreference(ReadPreference::Primary)).as_ref() - } - - fn retryability(&self) -> Retryability { - Retryability::Read - } -} diff --git a/src/operation/list_indexes/test.rs b/src/operation/list_indexes/test.rs deleted file mode 100644 index 317327313..000000000 --- a/src/operation/list_indexes/test.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::time::Duration; - -use crate::{ - bson::doc, - client::options::ServerAddress, - cmap::StreamDescription, - operation::{test::handle_response_test, ListIndexes, Operation}, - options::{IndexOptions, IndexVersion, ListIndexesOptions, TextIndexVersion}, - IndexModel, - Namespace, -}; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - - let list_options = ListIndexesOptions::builder() - .max_time(Some(Duration::from_millis(42))) - .batch_size(Some(4)) - .build(); - let mut list_indexes = ListIndexes::new(ns, Some(list_options)); - - let cmd = list_indexes - .build(&StreamDescription::new_testing()) - .expect("ListIndexes command failed to build when it should have succeeded."); - - assert_eq!( - cmd.body, - doc! { - "listIndexes": "test_coll", - "maxTimeMS": 42, - "cursor": doc! { - "batchSize": 4, - }, - } - ); -} - -#[test] -fn handle_success() { - let op = ListIndexes::empty(); - - let first_batch = vec![ - IndexModel::builder() - .keys(doc! {"x": 1}) - .options(Some( - IndexOptions::builder() - .version(Some(IndexVersion::V1)) - .name(Some("foo".to_string())) - .sparse(Some(false)) - .build(), - )) - .build(), - IndexModel::builder() - .keys(doc! {"y": 1, "z": -1}) - .options(Some( - IndexOptions::builder() - .version(Some(IndexVersion::V1)) - .name(Some("x_1_z_-1".to_string())) - .text_index_version(Some(TextIndexVersion::V3)) - .default_language(Some("spanish".to_string())) - .build(), - )) - .build(), - ]; - - let response = doc! { - "cursor": { - "id": 123, - "ns": "test_db.test_coll", - "firstBatch": bson::to_bson(&first_batch).unwrap(), - }, - "ok": 1, - }; - - let cursor_spec = handle_response_test(&op, response).unwrap(); - - assert_eq!(cursor_spec.id(), 123); - assert_eq!(cursor_spec.address(), &ServerAddress::default()); - assert_eq!(cursor_spec.batch_size(), None); - assert_eq!(cursor_spec.max_time(), None); - - assert_eq!( - bson::to_bson(&cursor_spec.initial_buffer).unwrap(), - bson::to_bson(&first_batch).unwrap(), - ); -} diff --git a/src/operation/mod.rs b/src/operation/mod.rs deleted file mode 100644 index f3e8fbd55..000000000 --- a/src/operation/mod.rs +++ /dev/null @@ -1,531 +0,0 @@ -mod abort_transaction; -mod aggregate; -mod commit_transaction; -mod count; -mod count_documents; -mod create; -mod create_indexes; -mod delete; -mod distinct; -mod drop_collection; -mod drop_database; -mod drop_indexes; -mod find; -mod find_and_modify; -mod get_more; -mod insert; -mod list_collections; -mod list_databases; -mod list_indexes; -mod raw_output; -mod run_command; -mod update; - -#[cfg(test)] -mod test; - -use std::{collections::VecDeque, fmt::Debug, ops::Deref}; - -use bson::{RawBsonRef, RawDocument, RawDocumentBuf, Timestamp}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; - -use crate::{ - bson::{self, Bson, Document}, - bson_util, - client::{ClusterTime, HELLO_COMMAND_NAMES, REDACTED_COMMANDS}, - cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, - error::{ - BulkWriteError, - BulkWriteFailure, - CommandError, - Error, - ErrorKind, - Result, - WriteConcernError, - WriteFailure, - }, - options::WriteConcern, - selection_criteria::SelectionCriteria, - Namespace, -}; - -pub(crate) use abort_transaction::AbortTransaction; -pub(crate) use aggregate::{Aggregate, AggregateTarget, ChangeStreamAggregate}; -pub(crate) use commit_transaction::CommitTransaction; -pub(crate) use count::Count; -pub(crate) use count_documents::CountDocuments; -pub(crate) use create::Create; -pub(crate) use create_indexes::CreateIndexes; -pub(crate) use delete::Delete; -pub(crate) use distinct::Distinct; -pub(crate) use drop_collection::DropCollection; -pub(crate) use drop_database::DropDatabase; -pub(crate) use drop_indexes::DropIndexes; -pub(crate) use find::Find; -pub(crate) use find_and_modify::FindAndModify; -pub(crate) use get_more::GetMore; -pub(crate) use insert::Insert; -pub(crate) use list_collections::ListCollections; -pub(crate) use list_databases::ListDatabases; -pub(crate) use list_indexes::ListIndexes; -#[cfg(feature = "in-use-encryption-unstable")] -pub(crate) use raw_output::RawOutput; -pub(crate) use run_command::RunCommand; -pub(crate) use update::Update; - -const SERVER_4_2_0_WIRE_VERSION: i32 = 8; -const SERVER_4_4_0_WIRE_VERSION: i32 = 9; - -/// A trait modeling the behavior of a server side operation. -/// -/// No methods in this trait should have default behaviors to ensure that wrapper operations -/// replicate all behavior. Default behavior is provided by the `OperationDefault` trait. -pub(crate) trait Operation { - /// The output type of this operation. - type O; - - /// The format of the command body constructed in `build`. - type Command: CommandBody; - - /// The name of the server side command associated with this operation. - const NAME: &'static str; - - /// Returns the command that should be sent to the server as part of this operation. - /// The operation may store some additional state that is required for handling the response. - fn build(&mut self, description: &StreamDescription) -> Result>; - - /// Perform custom serialization of the built command. - /// By default, this will just call through to the `Serialize` implementation of the command. - fn serialize_command(&mut self, cmd: Command) -> Result>; - - /// Parse the response for the atClusterTime field. - /// Depending on the operation, this may be found in different locations. - fn extract_at_cluster_time(&self, _response: &RawDocument) -> Result>; - - /// Interprets the server response to the command. - fn handle_response( - &self, - response: RawCommandResponse, - description: &StreamDescription, - ) -> Result; - - /// Interpret an error encountered while sending the built command to the server, potentially - /// recovering. - fn handle_error(&self, error: Error) -> Result; - - /// Criteria to use for selecting the server that this operation will be executed on. - fn selection_criteria(&self) -> Option<&SelectionCriteria>; - - /// Whether or not this operation will request acknowledgment from the server. - fn is_acknowledged(&self) -> bool; - - /// The write concern to use for this operation, if any. - fn write_concern(&self) -> Option<&WriteConcern>; - - /// Returns whether or not this command supports the `readConcern` field. - fn supports_read_concern(&self, _description: &StreamDescription) -> bool; - - /// Whether this operation supports sessions or not. - fn supports_sessions(&self) -> bool; - - /// The level of retryability the operation supports. - fn retryability(&self) -> Retryability; - - /// Updates this operation as needed for a retry. - fn update_for_retry(&mut self); - - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle>; - - fn name(&self) -> &str; -} - -pub(crate) trait CommandBody: Serialize { - fn should_redact(&self) -> bool { - false - } -} - -impl CommandBody for Document { - fn should_redact(&self) -> bool { - if let Some(command_name) = bson_util::first_key(self) { - HELLO_COMMAND_NAMES.contains(command_name.to_lowercase().as_str()) - && self.contains_key("speculativeAuthenticate") - } else { - false - } - } -} - -impl CommandBody for RawDocumentBuf { - fn should_redact(&self) -> bool { - if let Some(Ok((command_name, _))) = self.into_iter().next() { - HELLO_COMMAND_NAMES.contains(command_name.to_lowercase().as_str()) - && self.get("speculativeAuthenticate").ok().flatten().is_some() - } else { - false - } - } -} - -impl Command { - pub(crate) fn should_redact(&self) -> bool { - let name = self.name.to_lowercase(); - REDACTED_COMMANDS.contains(name.as_str()) || self.body.should_redact() - } - - pub(crate) fn should_compress(&self) -> bool { - let name = self.name.to_lowercase(); - !REDACTED_COMMANDS.contains(name.as_str()) && !HELLO_COMMAND_NAMES.contains(name.as_str()) - } -} - -/// A response to a command with a body shaped deserialized to a `T`. -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub(crate) struct CommandResponse { - pub(crate) ok: Bson, - - #[serde(rename = "$clusterTime")] - pub(crate) cluster_time: Option, - - #[serde(flatten)] - pub(crate) body: T, -} - -impl CommandResponse { - /// Whether the command succeeeded or not (i.e. if this response is ok: 1). - pub(crate) fn is_success(&self) -> bool { - bson_util::get_int(&self.ok) == Some(1) - } - - pub(crate) fn cluster_time(&self) -> Option<&ClusterTime> { - self.cluster_time.as_ref() - } -} - -/// A response body useful for deserializing command errors. -#[derive(Deserialize, Debug)] -pub(crate) struct CommandErrorBody { - #[serde(rename = "errorLabels")] - pub(crate) error_labels: Option>, - - #[serde(flatten)] - pub(crate) command_error: CommandError, -} - -impl From for Error { - fn from(command_error_response: CommandErrorBody) -> Error { - Error::new( - ErrorKind::Command(command_error_response.command_error), - command_error_response.error_labels, - ) - } -} - -/// Appends a serializable struct to the input document. -/// The serializable struct MUST serialize to a Document, otherwise an error will be thrown. -pub(crate) fn append_options( - doc: &mut Document, - options: Option<&T>, -) -> Result<()> { - match options { - Some(options) => { - let temp_doc = bson::to_bson(options)?; - match temp_doc { - Bson::Document(d) => { - doc.extend(d); - Ok(()) - } - _ => Err(ErrorKind::Internal { - message: format!("options did not serialize to a Document: {:?}", options), - } - .into()), - } - } - None => Ok(()), - } -} - -#[derive(Deserialize, Debug)] -pub(crate) struct EmptyBody {} - -/// Body of a write response that could possibly have a write concern error but not write errors. -#[derive(Debug, Deserialize, Default, Clone)] -pub(crate) struct WriteConcernOnlyBody { - #[serde(rename = "writeConcernError")] - write_concern_error: Option, - - #[serde(rename = "errorLabels")] - labels: Option>, -} - -impl WriteConcernOnlyBody { - fn validate(&self) -> Result<()> { - match self.write_concern_error { - Some(ref wc_error) => Err(Error::new( - ErrorKind::Write(WriteFailure::WriteConcernError(wc_error.clone())), - self.labels.clone(), - )), - None => Ok(()), - } - } -} - -#[derive(Deserialize, Debug)] -pub(crate) struct WriteResponseBody { - #[serde(flatten)] - body: T, - - n: u64, - - #[serde(rename = "writeErrors")] - write_errors: Option>, - - #[serde(rename = "writeConcernError")] - write_concern_error: Option, - - #[serde(rename = "errorLabels")] - labels: Option>, -} - -impl WriteResponseBody { - fn validate(&self) -> Result<()> { - if self.write_errors.is_none() && self.write_concern_error.is_none() { - return Ok(()); - }; - - let failure = BulkWriteFailure { - write_errors: self.write_errors.clone(), - write_concern_error: self.write_concern_error.clone(), - inserted_ids: Default::default(), - }; - - Err(Error::new( - ErrorKind::BulkWrite(failure), - self.labels.clone(), - )) - } -} - -impl Deref for WriteResponseBody { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.body - } -} - -#[derive(Debug, Deserialize)] -pub(crate) struct CursorBody { - cursor: CursorInfo, -} - -impl CursorBody { - fn extract_at_cluster_time(response: &RawDocument) -> Result> { - Ok(response - .get("cursor")? - .and_then(RawBsonRef::as_document) - .map(|d| d.get("atClusterTime")) - .transpose()? - .flatten() - .and_then(RawBsonRef::as_timestamp)) - } -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "camelCase")] -pub(crate) struct CursorInfo { - pub(crate) id: i64, - - pub(crate) ns: Namespace, - - pub(crate) first_batch: VecDeque, - - pub(crate) post_batch_resume_token: Option, -} - -/// Type used to deserialize just the first result from a cursor, if any. -#[derive(Debug, Clone)] -pub(crate) struct SingleCursorResult(Option); - -impl<'de, T> Deserialize<'de> for SingleCursorResult -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct FullCursorBody { - cursor: InteriorBody, - } - - #[derive(Deserialize)] - struct InteriorBody { - #[serde(rename = "firstBatch")] - first_batch: Vec, - } - - let mut full_body = FullCursorBody::deserialize(deserializer)?; - Ok(SingleCursorResult(full_body.cursor.first_batch.pop())) - } -} - -#[derive(Debug, PartialEq, Clone, Copy)] -pub(crate) enum Retryability { - Write, - Read, - None, -} - -macro_rules! remove_empty_write_concern { - ($opts:expr) => { - if let Some(ref mut options) = $opts { - if let Some(ref write_concern) = options.write_concern { - if write_concern.is_empty() { - options.write_concern = None; - } - } - } - }; -} - -pub(crate) use remove_empty_write_concern; - -// A mirror of the `Operation` trait, with default behavior where appropriate. Should only be -// implemented by operation types that do not delegate to other operations. -pub(crate) trait OperationWithDefaults { - /// The output type of this operation. - type O; - - /// The format of the command body constructed in `build`. - type Command: CommandBody; - - /// The name of the server side command associated with this operation. - const NAME: &'static str; - - /// Returns the command that should be sent to the server as part of this operation. - /// The operation may store some additional state that is required for handling the response. - fn build(&mut self, description: &StreamDescription) -> Result>; - - /// Perform custom serialization of the built command. - /// By default, this will just call through to the `Serialize` implementation of the command. - fn serialize_command(&mut self, cmd: Command) -> Result> { - Ok(bson::to_vec(&cmd)?) - } - - /// Parse the response for the atClusterTime field. - /// Depending on the operation, this may be found in different locations. - fn extract_at_cluster_time(&self, _response: &RawDocument) -> Result> { - Ok(None) - } - - /// Interprets the server response to the command. - fn handle_response( - &self, - response: RawCommandResponse, - description: &StreamDescription, - ) -> Result; - - /// Interpret an error encountered while sending the built command to the server, potentially - /// recovering. - fn handle_error(&self, error: Error) -> Result { - Err(error) - } - - /// Criteria to use for selecting the server that this operation will be executed on. - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - None - } - - /// Whether or not this operation will request acknowledgment from the server. - fn is_acknowledged(&self) -> bool { - self.write_concern() - .map(WriteConcern::is_acknowledged) - .unwrap_or(true) - } - - /// The write concern to use for this operation, if any. - fn write_concern(&self) -> Option<&WriteConcern> { - None - } - - /// Returns whether or not this command supports the `readConcern` field. - fn supports_read_concern(&self, _description: &StreamDescription) -> bool { - false - } - - /// Whether this operation supports sessions or not. - fn supports_sessions(&self) -> bool { - true - } - - /// The level of retryability the operation supports. - fn retryability(&self) -> Retryability { - Retryability::None - } - - /// Updates this operation as needed for a retry. - fn update_for_retry(&mut self) {} - - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - None - } - - fn name(&self) -> &str { - Self::NAME - } -} - -impl Operation for T { - type O = T::O; - type Command = T::Command; - const NAME: &'static str = T::NAME; - fn build(&mut self, description: &StreamDescription) -> Result> { - self.build(description) - } - fn serialize_command(&mut self, cmd: Command) -> Result> { - self.serialize_command(cmd) - } - fn extract_at_cluster_time(&self, response: &RawDocument) -> Result> { - self.extract_at_cluster_time(response) - } - fn handle_response( - &self, - response: RawCommandResponse, - description: &StreamDescription, - ) -> Result { - self.handle_response(response, description) - } - fn handle_error(&self, error: Error) -> Result { - self.handle_error(error) - } - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.selection_criteria() - } - fn is_acknowledged(&self) -> bool { - self.is_acknowledged() - } - fn write_concern(&self) -> Option<&WriteConcern> { - self.write_concern() - } - fn supports_read_concern(&self, description: &StreamDescription) -> bool { - self.supports_read_concern(description) - } - fn supports_sessions(&self) -> bool { - self.supports_sessions() - } - fn retryability(&self) -> Retryability { - self.retryability() - } - fn update_for_retry(&mut self) { - self.update_for_retry() - } - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - self.pinned_connection() - } - fn name(&self) -> &str { - self.name() - } -} diff --git a/src/operation/raw_output.rs b/src/operation/raw_output.rs index 151d7b3e7..e7dec57d6 100644 --- a/src/operation/raw_output.rs +++ b/src/operation/raw_output.rs @@ -1,9 +1,13 @@ +use futures_util::FutureExt; + use crate::{ + bson_compat::CStr, cmap::{Command, RawCommandResponse, StreamDescription}, error::Result, + BoxFuture, }; -use super::Operation; +use super::{ExecutionContext, Operation}; /// Forwards all implementation to the wrapped `Operation`, but returns the response unparsed and /// unvalidated as a `RawCommandResponse`. @@ -12,30 +16,25 @@ pub(crate) struct RawOutput(pub(crate) Op); impl Operation for RawOutput { type O = RawCommandResponse; - type Command = Op::Command; - const NAME: &'static str = Op::NAME; + const NAME: &'static CStr = Op::NAME; - fn build(&mut self, description: &StreamDescription) -> Result> { + fn build(&mut self, description: &StreamDescription) -> Result { self.0.build(description) } - fn serialize_command(&mut self, cmd: Command) -> Result> { - self.0.serialize_command(cmd) - } - fn extract_at_cluster_time( &self, - response: &bson::RawDocument, - ) -> Result> { + response: &crate::bson::RawDocument, + ) -> Result> { self.0.extract_at_cluster_time(response) } - fn handle_response( - &self, + fn handle_response<'a>( + &'a self, response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - Ok(response) + _context: ExecutionContext<'a>, + ) -> BoxFuture<'a, Result> { + async move { Ok(response) }.boxed() } fn handle_error(&self, error: crate::error::Error) -> Result { @@ -70,11 +69,15 @@ impl Operation for RawOutput { self.0.update_for_retry() } + fn override_criteria(&self) -> super::OverrideCriteriaFn { + self.0.override_criteria() + } + fn pinned_connection(&self) -> Option<&crate::cmap::conn::PinnedConnectionHandle> { self.0.pinned_connection() } - fn name(&self) -> &str { + fn name(&self) -> &CStr { self.0.name() } } diff --git a/src/operation/run_command.rs b/src/operation/run_command.rs new file mode 100644 index 000000000..fe5a5a869 --- /dev/null +++ b/src/operation/run_command.rs @@ -0,0 +1,97 @@ +use std::convert::TryInto; + +use crate::{ + bson::{Document, RawBsonRef, RawDocumentBuf}, + bson_compat::{cstr, CStr}, + client::SESSIONS_UNSUPPORTED_COMMANDS, + cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, + error::{ErrorKind, Result}, + selection_criteria::SelectionCriteria, +}; + +use super::{CursorBody, ExecutionContext, OperationWithDefaults}; + +#[derive(Debug, Clone)] +pub(crate) struct RunCommand<'conn> { + db: String, + command: RawDocumentBuf, + selection_criteria: Option, + pinned_connection: Option<&'conn PinnedConnectionHandle>, +} + +impl<'conn> RunCommand<'conn> { + pub(crate) fn new( + db: String, + command: RawDocumentBuf, + selection_criteria: Option, + pinned_connection: Option<&'conn PinnedConnectionHandle>, + ) -> Self { + Self { + db, + command, + selection_criteria, + pinned_connection, + } + } + + fn command_name(&self) -> Option<&CStr> { + self.command + .into_iter() + .next() + .and_then(|r| r.ok()) + .map(|(k, _)| k) + } +} + +impl OperationWithDefaults for RunCommand<'_> { + type O = Document; + + // Since we can't actually specify a string statically here, we just put a descriptive string + // that should fail loudly if accidentally passed to the server. + const NAME: &'static CStr = cstr!("$genericRunCommand"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let command_name = self + .command_name() + .ok_or_else(|| ErrorKind::InvalidArgument { + message: "an empty document cannot be passed to a run_command operation".into(), + })?; + + Ok(Command::new(command_name, &self.db, self.command.clone())) + } + + fn extract_at_cluster_time( + &self, + response: &crate::bson::RawDocument, + ) -> Result> { + if let Some(RawBsonRef::Timestamp(ts)) = response.get("atClusterTime")? { + Ok(Some(ts)) + } else { + CursorBody::extract_at_cluster_time(response) + } + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + Ok(response.into_raw_document_buf().try_into()?) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.selection_criteria.as_ref() + } + + fn supports_sessions(&self) -> bool { + self.command_name() + .map(|command_name| { + !SESSIONS_UNSUPPORTED_COMMANDS.contains(command_name.to_lowercase().as_str()) + }) + .unwrap_or(false) + } + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + self.pinned_connection + } +} diff --git a/src/operation/run_command/mod.rs b/src/operation/run_command/mod.rs deleted file mode 100644 index a62bad1ed..000000000 --- a/src/operation/run_command/mod.rs +++ /dev/null @@ -1,139 +0,0 @@ -#[cfg(test)] -mod test; - -use std::convert::TryInto; - -use bson::{RawBsonRef, RawDocumentBuf}; - -use super::{CursorBody, OperationWithDefaults}; -use crate::{ - bson::Document, - client::SESSIONS_UNSUPPORTED_COMMANDS, - cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, - error::{ErrorKind, Result}, - options::WriteConcern, - selection_criteria::SelectionCriteria, -}; - -#[derive(Debug, Clone)] -pub(crate) struct RunCommand<'conn> { - db: String, - command: RawDocumentBuf, - selection_criteria: Option, - write_concern: Option, - pinned_connection: Option<&'conn PinnedConnectionHandle>, -} - -impl<'conn> RunCommand<'conn> { - pub(crate) fn new( - db: String, - command: Document, - selection_criteria: Option, - pinned_connection: Option<&'conn PinnedConnectionHandle>, - ) -> Result { - let write_concern = command - .get("writeConcern") - .map(|doc| bson::from_bson::(doc.clone())) - .transpose()?; - - Ok(Self { - db, - command: RawDocumentBuf::from_document(&command)?, - selection_criteria, - write_concern, - pinned_connection, - }) - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) fn new_raw( - db: String, - command: RawDocumentBuf, - selection_criteria: Option, - pinned_connection: Option<&'conn PinnedConnectionHandle>, - ) -> Result { - let write_concern = command - .get("writeConcern")? - .and_then(|b| b.as_document()) - .map(|doc| bson::from_slice::(doc.as_bytes())) - .transpose()?; - - Ok(Self { - db, - command, - selection_criteria, - write_concern, - pinned_connection, - }) - } - - fn command_name(&self) -> Option<&str> { - self.command - .into_iter() - .next() - .and_then(|r| r.ok()) - .map(|(k, _)| k) - } -} - -impl<'conn> OperationWithDefaults for RunCommand<'conn> { - type O = Document; - type Command = RawDocumentBuf; - - // Since we can't actually specify a string statically here, we just put a descriptive string - // that should fail loudly if accidentally passed to the server. - const NAME: &'static str = "$genericRunCommand"; - - fn build(&mut self, _description: &StreamDescription) -> Result> { - let command_name = self - .command_name() - .ok_or_else(|| ErrorKind::InvalidArgument { - message: "an empty document cannot be passed to a run_command operation".into(), - })?; - - Ok(Command::new( - command_name.to_string(), - self.db.clone(), - self.command.clone(), - )) - } - - fn extract_at_cluster_time( - &self, - response: &bson::RawDocument, - ) -> Result> { - if let Some(RawBsonRef::Timestamp(ts)) = response.get("atClusterTime")? { - Ok(Some(ts)) - } else { - CursorBody::extract_at_cluster_time(response) - } - } - - fn handle_response( - &self, - response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - Ok(response.into_raw_document_buf().try_into()?) - } - - fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.selection_criteria.as_ref() - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.write_concern.as_ref() - } - - fn supports_sessions(&self) -> bool { - self.command_name() - .map(|command_name| { - !SESSIONS_UNSUPPORTED_COMMANDS.contains(command_name.to_lowercase().as_str()) - }) - .unwrap_or(false) - } - - fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { - self.pinned_connection - } -} diff --git a/src/operation/run_command/test.rs b/src/operation/run_command/test.rs deleted file mode 100644 index 467ccf5e1..000000000 --- a/src/operation/run_command/test.rs +++ /dev/null @@ -1,24 +0,0 @@ -use bson::Timestamp; - -use super::RunCommand; -use crate::{bson::doc, operation::test::handle_response_test}; - -#[test] -fn handle_success() { - let op = RunCommand::new("foo".into(), doc! { "hello": 1 }, None, None).unwrap(); - - let doc = doc! { - "ok": 1, - "some": "field", - "other": true, - "$clusterTime": { - "clusterTime": Timestamp { - time: 123, - increment: 345, - }, - "signature": {} - } - }; - let result_doc = handle_response_test(&op, doc.clone()).unwrap(); - assert_eq!(result_doc, doc); -} diff --git a/src/operation/run_cursor_command.rs b/src/operation/run_cursor_command.rs new file mode 100644 index 000000000..ad96eca79 --- /dev/null +++ b/src/operation/run_cursor_command.rs @@ -0,0 +1,122 @@ +use futures_util::FutureExt; + +use crate::{ + bson_compat::{cstr, CStr}, + cmap::{conn::PinnedConnectionHandle, Command, RawCommandResponse, StreamDescription}, + concern::WriteConcern, + cursor::CursorSpecification, + error::{Error, Result}, + operation::{run_command::RunCommand, CursorBody, Operation}, + options::RunCursorCommandOptions, + selection_criteria::SelectionCriteria, + BoxFuture, +}; + +use super::ExecutionContext; + +#[derive(Debug, Clone)] +pub(crate) struct RunCursorCommand<'conn> { + run_command: RunCommand<'conn>, + options: Option, +} + +impl<'conn> RunCursorCommand<'conn> { + pub(crate) fn new( + run_command: RunCommand<'conn>, + options: Option, + ) -> Result { + Ok(Self { + run_command, + options, + }) + } +} + +impl Operation for RunCursorCommand<'_> { + type O = CursorSpecification; + + const NAME: &'static CStr = cstr!("run_cursor_command"); + + fn build(&mut self, description: &StreamDescription) -> Result { + self.run_command.build(description) + } + + fn extract_at_cluster_time( + &self, + response: &crate::bson::RawDocument, + ) -> Result> { + self.run_command.extract_at_cluster_time(response) + } + + fn handle_error(&self, error: Error) -> Result { + Err(error) + } + + fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.run_command.selection_criteria() + } + + fn is_acknowledged(&self) -> bool { + self.run_command.is_acknowledged() + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.run_command.write_concern() + } + + fn supports_read_concern(&self, description: &StreamDescription) -> bool { + self.run_command.supports_read_concern(description) + } + + fn supports_sessions(&self) -> bool { + self.run_command.supports_sessions() + } + + fn retryability(&self) -> crate::operation::Retryability { + self.run_command.retryability() + } + + fn update_for_retry(&mut self) { + self.run_command.update_for_retry() + } + + fn override_criteria(&self) -> super::OverrideCriteriaFn { + self.run_command.override_criteria() + } + + fn pinned_connection(&self) -> Option<&PinnedConnectionHandle> { + self.run_command.pinned_connection() + } + + fn name(&self) -> &CStr { + self.run_command.name() + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + context: ExecutionContext<'a>, + ) -> BoxFuture<'a, Result> { + async move { + let cursor_response: CursorBody = response.body()?; + + let comment = match &self.options { + Some(options) => options.comment.clone(), + None => None, + }; + + Ok(CursorSpecification::new( + cursor_response.cursor, + context + .connection + .stream_description()? + .server_address + .clone(), + self.options.as_ref().and_then(|opts| opts.batch_size), + self.options.as_ref().and_then(|opts| opts.max_time), + comment, + )) + } + .boxed() + } +} diff --git a/src/operation/search_index.rs b/src/operation/search_index.rs new file mode 100644 index 000000000..2248cbf95 --- /dev/null +++ b/src/operation/search_index.rs @@ -0,0 +1,182 @@ +use crate::bson::{rawdoc, RawDocumentBuf}; +use serde::Deserialize; + +use crate::{ + bson::{doc, Document}, + bson_compat::{cstr, CStr}, + bson_util::to_raw_bson_array_ser, + cmap::{Command, RawCommandResponse}, + error::Result, + Namespace, + SearchIndexModel, +}; + +use super::{ExecutionContext, OperationWithDefaults}; + +#[derive(Debug)] +pub(crate) struct CreateSearchIndexes { + ns: Namespace, + indexes: Vec, +} + +impl CreateSearchIndexes { + pub(crate) fn new(ns: Namespace, indexes: Vec) -> Self { + Self { ns, indexes } + } +} + +impl OperationWithDefaults for CreateSearchIndexes { + type O = Vec; + const NAME: &'static CStr = cstr!("createSearchIndexes"); + + fn build(&mut self, _description: &crate::cmap::StreamDescription) -> Result { + Ok(Command::new( + Self::NAME.to_string(), + self.ns.db.clone(), + rawdoc! { + Self::NAME: self.ns.coll.clone(), + "indexes": to_raw_bson_array_ser(&self.indexes)?, + }, + )) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct Response { + indexes_created: Vec, + } + + #[derive(Debug, Deserialize)] + struct CreatedIndex { + #[allow(unused)] + id: String, + name: String, + } + + let response: Response = response.body()?; + Ok(response + .indexes_created + .into_iter() + .map(|ci| ci.name) + .collect()) + } + + fn supports_sessions(&self) -> bool { + false + } + + fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { + false + } +} + +#[derive(Debug)] +pub(crate) struct UpdateSearchIndex { + ns: Namespace, + name: String, + definition: Document, +} + +impl UpdateSearchIndex { + pub(crate) fn new(ns: Namespace, name: String, definition: Document) -> Self { + Self { + ns, + name, + definition, + } + } +} + +impl OperationWithDefaults for UpdateSearchIndex { + type O = (); + const NAME: &'static CStr = cstr!("updateSearchIndex"); + + fn build( + &mut self, + _description: &crate::cmap::StreamDescription, + ) -> crate::error::Result { + let raw_def: RawDocumentBuf = (&self.definition).try_into()?; + Ok(Command::new( + Self::NAME, + &self.ns.db, + rawdoc! { + Self::NAME: self.ns.coll.as_str(), + "name": self.name.as_str(), + "definition": raw_def, + }, + )) + } + + fn handle_response<'a>( + &'a self, + _response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + Ok(()) + } + + fn supports_sessions(&self) -> bool { + false + } + + fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { + false + } +} + +#[derive(Debug)] +pub(crate) struct DropSearchIndex { + ns: Namespace, + name: String, +} + +impl DropSearchIndex { + pub(crate) fn new(ns: Namespace, name: String) -> Self { + Self { ns, name } + } +} + +impl OperationWithDefaults for DropSearchIndex { + type O = (); + const NAME: &'static CStr = cstr!("dropSearchIndex"); + + fn build(&mut self, _description: &crate::cmap::StreamDescription) -> Result { + Ok(Command::new( + Self::NAME, + &self.ns.db, + rawdoc! { + Self::NAME: self.ns.coll.as_str(), + "name": self.name.as_str(), + }, + )) + } + + fn handle_response<'a>( + &'a self, + _response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + Ok(()) + } + + fn handle_error(&self, error: crate::error::Error) -> Result { + if error.is_ns_not_found() { + Ok(()) + } else { + Err(error) + } + } + + fn supports_sessions(&self) -> bool { + false + } + + fn supports_read_concern(&self, _description: &crate::cmap::StreamDescription) -> bool { + false + } +} diff --git a/src/operation/test.rs b/src/operation/test.rs deleted file mode 100644 index 3dacc95b9..000000000 --- a/src/operation/test.rs +++ /dev/null @@ -1,149 +0,0 @@ -use bson::{doc, Document, Timestamp}; -use serde::Deserialize; - -use crate::{ - client::ClusterTime, - cmap::{RawCommandResponse, StreamDescription}, - error::{Result, TRANSIENT_TRANSACTION_ERROR}, - operation::{CommandErrorBody, CommandResponse, Operation}, - options::{ReadPreference, SelectionCriteria}, -}; - -pub(crate) fn handle_response_test(op: &T, response_doc: Document) -> Result { - let raw = RawCommandResponse::with_document(response_doc).unwrap(); - op.handle_response(raw, &StreamDescription::new_testing()) -} - -pub(crate) fn op_selection_criteria(constructor: F) -where - T: Operation, - F: Fn(Option) -> T, -{ - let op = constructor(None); - assert_eq!(op.selection_criteria(), None); - - let read_pref: SelectionCriteria = ReadPreference::Secondary { - options: Default::default(), - } - .into(); - - let op = constructor(Some(read_pref.clone())); - assert_eq!(op.selection_criteria(), Some(&read_pref)); -} - -#[test] -fn response_success() { - let cluster_timestamp = Timestamp { - time: 123, - increment: 345, - }; - let doc = doc! { - "ok": 1, - "some": "field", - "other": true, - "$clusterTime": { - "clusterTime": cluster_timestamp, - "signature": {} - } - }; - let raw = RawCommandResponse::with_document(doc.clone()).unwrap(); - let response: CommandResponse = raw.body().unwrap(); - - assert!(response.is_success()); - assert_eq!( - response.cluster_time(), - Some(&ClusterTime { - cluster_time: cluster_timestamp, - signature: doc! {}, - }) - ); - assert_eq!(response.body, doc! { "some": "field", "other": true }); - - #[derive(Deserialize, Debug, PartialEq)] - struct Body { - some: String, - #[serde(rename = "other")] - o: bool, - #[serde(default)] - default: Option, - } - - let raw = RawCommandResponse::with_document(doc).unwrap(); - let response: CommandResponse = raw.body().unwrap(); - - assert!(response.is_success()); - assert_eq!( - response.cluster_time(), - Some(&ClusterTime { - cluster_time: cluster_timestamp, - signature: doc! {}, - }) - ); - assert_eq!( - response.body, - Body { - some: "field".to_string(), - o: true, - default: None, - } - ); -} - -#[test] -fn response_failure() { - let cluster_timestamp = Timestamp { - time: 123, - increment: 345, - }; - let doc = doc! { - "ok": 0, - "code": 123, - "codeName": "name", - "errmsg": "some message", - "errorLabels": [TRANSIENT_TRANSACTION_ERROR], - "$clusterTime": { - "clusterTime": cluster_timestamp, - "signature": {} - } - }; - let raw = RawCommandResponse::with_document(doc.clone()).unwrap(); - let response: CommandResponse = raw.body().unwrap(); - - assert!(!response.is_success()); - assert_eq!( - response.cluster_time(), - Some(&ClusterTime { - cluster_time: cluster_timestamp, - signature: doc! {}, - }) - ); - assert_eq!( - response.body, - doc! { - "code": 123, - "codeName": "name", - "errmsg": "some message", - "errorLabels": [TRANSIENT_TRANSACTION_ERROR], - } - ); - - let raw = RawCommandResponse::with_document(doc).unwrap(); - let response: CommandResponse = raw.body().unwrap(); - - assert!(!response.is_success()); - assert_eq!( - response.cluster_time(), - Some(&ClusterTime { - cluster_time: cluster_timestamp, - signature: doc! {}, - }) - ); - let command_error = response.body; - assert_eq!(command_error.command_error.code, 123); - assert_eq!(command_error.command_error.code_name, "name"); - assert_eq!(command_error.command_error.message, "some message"); - assert_eq!( - command_error.error_labels, - Some(vec![TRANSIENT_TRANSACTION_ERROR.to_string()]) - ); -} diff --git a/src/operation/update.rs b/src/operation/update.rs new file mode 100644 index 000000000..c4c8e6a7d --- /dev/null +++ b/src/operation/update.rs @@ -0,0 +1,224 @@ +use serde::Deserialize; + +use crate::{ + bson::{doc, rawdoc, Document, RawArrayBuf, RawBson, RawDocumentBuf}, + bson_compat::{cstr, CStr, RawDocumentBufExt as _}, + bson_util, + cmap::{Command, RawCommandResponse, StreamDescription}, + error::{convert_insert_many_error, Result}, + operation::{OperationWithDefaults, Retryability, WriteResponseBody}, + options::{UpdateModifications, UpdateOptions, WriteConcern}, + results::UpdateResult, + Namespace, +}; + +use super::ExecutionContext; + +#[derive(Clone, Debug)] +pub(crate) enum UpdateOrReplace { + UpdateModifications(UpdateModifications), + Replacement(RawDocumentBuf), +} + +impl UpdateOrReplace { + pub(crate) fn append_to_rawdoc(&self, doc: &mut RawDocumentBuf, key: &CStr) -> Result<()> { + match self { + Self::UpdateModifications(update_modifications) => match update_modifications { + UpdateModifications::Document(document) => { + let raw = RawDocumentBuf::from_document(document)?; + doc.append(key, raw); + } + UpdateModifications::Pipeline(pipeline) => { + let raw = bson_util::to_raw_bson_array(pipeline)?; + doc.append(key, raw); + } + }, + Self::Replacement(replacement_doc) => { + bson_util::replacement_raw_document_check(replacement_doc)?; + doc.append_ref_compat(key, replacement_doc); + } + } + + Ok(()) + } +} + +impl From for UpdateOrReplace { + fn from(update_modifications: UpdateModifications) -> Self { + Self::UpdateModifications(update_modifications) + } +} + +#[derive(Debug)] +pub(crate) struct Update { + ns: Namespace, + filter: Document, + update: UpdateOrReplace, + multi: Option, + options: Option, +} + +impl Update { + pub(crate) fn with_update( + ns: Namespace, + filter: Document, + update: UpdateModifications, + multi: bool, + options: Option, + ) -> Self { + Self { + ns, + filter, + update: update.into(), + multi: multi.then_some(true), + options, + } + } + + pub(crate) fn with_replace_raw( + ns: Namespace, + filter: Document, + update: RawDocumentBuf, + multi: bool, + options: Option, + ) -> Result { + Ok(Self { + ns, + filter, + update: UpdateOrReplace::Replacement(update), + multi: multi.then_some(true), + options, + }) + } +} + +impl OperationWithDefaults for Update { + type O = UpdateResult; + + const NAME: &'static CStr = cstr!("update"); + + fn build(&mut self, _description: &StreamDescription) -> Result { + let mut body = rawdoc! { + Self::NAME: self.ns.coll.clone(), + }; + + let mut update = rawdoc! { + "q": RawDocumentBuf::from_document(&self.filter)?, + }; + self.update.append_to_rawdoc(&mut update, cstr!("u"))?; + + if let Some(ref options) = self.options { + if let Some(upsert) = options.upsert { + update.append(cstr!("upsert"), upsert); + } + + if let Some(ref array_filters) = options.array_filters { + update.append( + cstr!("arrayFilters"), + bson_util::to_raw_bson_array(array_filters)?, + ); + } + + if let Some(ref hint) = options.hint { + update.append(cstr!("hint"), hint.to_raw_bson()?); + } + + if let Some(ref collation) = options.collation { + update.append( + cstr!("collation"), + crate::bson_compat::serialize_to_raw_document_buf(&collation)?, + ); + } + + if let Some(bypass_doc_validation) = options.bypass_document_validation { + body.append(cstr!("bypassDocumentValidation"), bypass_doc_validation); + } + + if let Some(ref write_concern) = options.write_concern { + if !write_concern.is_empty() { + body.append( + cstr!("writeConcern"), + crate::bson_compat::serialize_to_raw_document_buf(write_concern)?, + ); + } + } + + if let Some(ref let_vars) = options.let_vars { + body.append( + cstr!("let"), + crate::bson_compat::serialize_to_raw_document_buf(&let_vars)?, + ); + } + + if let Some(ref comment) = options.comment { + body.append(cstr!("comment"), RawBson::try_from(comment.clone())?); + } + + if let Some(ref sort) = options.sort { + update.append(cstr!("sort"), RawDocumentBuf::from_document(sort)?); + } + }; + + if let Some(multi) = self.multi { + update.append(cstr!("multi"), multi); + } + + let mut updates = RawArrayBuf::new(); + updates.push(update); + body.append(cstr!("updates"), updates); + body.append(cstr!("ordered"), true); // command monitoring tests expect this (SPEC-1130) + + Ok(Command::new(Self::NAME, &self.ns.db, body)) + } + + fn handle_response<'a>( + &'a self, + response: RawCommandResponse, + _context: ExecutionContext<'a>, + ) -> Result { + let response: WriteResponseBody = response.body()?; + response.validate().map_err(convert_insert_many_error)?; + + let modified_count = response.n_modified; + let upserted_id = response + .upserted + .as_ref() + .and_then(|v| v.first()) + .and_then(|doc| doc.get("_id")) + .cloned(); + + let matched_count = if upserted_id.is_some() { + 0 + } else { + response.body.n + }; + + Ok(UpdateResult { + matched_count, + modified_count, + upserted_id, + }) + } + + fn write_concern(&self) -> Option<&WriteConcern> { + self.options + .as_ref() + .and_then(|opts| opts.write_concern.as_ref()) + } + + fn retryability(&self) -> Retryability { + if self.multi != Some(true) { + Retryability::Write + } else { + Retryability::None + } + } +} + +#[derive(Deserialize)] +pub(crate) struct UpdateBody { + n: u64, + #[serde(rename = "nModified")] + n_modified: u64, + upserted: Option>, +} diff --git a/src/operation/update/mod.rs b/src/operation/update/mod.rs deleted file mode 100644 index d9442aee3..000000000 --- a/src/operation/update/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -#[cfg(test)] -mod test; - -use serde::Deserialize; - -use crate::{ - bson::{doc, Bson, Document}, - bson_util, - cmap::{Command, RawCommandResponse, StreamDescription}, - error::{convert_bulk_errors, Result}, - operation::{OperationWithDefaults, Retryability, WriteResponseBody}, - options::{UpdateModifications, UpdateOptions, WriteConcern}, - results::UpdateResult, - Namespace, -}; - -#[derive(Debug)] -pub(crate) struct Update { - ns: Namespace, - filter: Document, - update: UpdateModifications, - multi: Option, - options: Option, -} - -impl Update { - #[cfg(test)] - fn empty() -> Self { - Update::new( - Namespace { - db: String::new(), - coll: String::new(), - }, - Document::new(), - UpdateModifications::Document(Document::new()), - false, - None, - ) - } - - pub(crate) fn new( - ns: Namespace, - filter: Document, - update: UpdateModifications, - multi: bool, - options: Option, - ) -> Self { - Self { - ns, - filter, - update, - multi: if multi { Some(true) } else { None }, - options, - } - } -} - -impl OperationWithDefaults for Update { - type O = UpdateResult; - type Command = Document; - - const NAME: &'static str = "update"; - - fn build(&mut self, _description: &StreamDescription) -> Result { - let mut body = doc! { - Self::NAME: self.ns.coll.clone(), - }; - - let mut update = doc! { - "q": self.filter.clone(), - "u": self.update.to_bson(), - }; - - if let Some(ref options) = self.options { - if let Some(upsert) = options.upsert { - update.insert("upsert", upsert); - } - - if let Some(ref array_filters) = options.array_filters { - update.insert("arrayFilters", bson_util::to_bson_array(array_filters)); - } - - if let Some(ref hint) = options.hint { - update.insert("hint", hint.to_bson()); - } - - if let Some(ref collation) = options.collation { - update.insert("collation", bson::to_bson(collation)?); - } - - if let Some(bypass_doc_validation) = options.bypass_document_validation { - body.insert("bypassDocumentValidation", bypass_doc_validation); - } - - if let Some(ref write_concern) = options.write_concern { - if !write_concern.is_empty() { - body.insert("writeConcern", bson::to_bson(write_concern)?); - } - } - - if let Some(ref let_vars) = options.let_vars { - body.insert("let", let_vars); - } - - if let Some(ref comment) = options.comment { - body.insert("comment", comment); - } - }; - - if let Some(multi) = self.multi { - update.insert("multi", multi); - } - - body.insert("updates", vec![Bson::Document(update)]); - body.insert("ordered", true); // command monitoring tests expect this (SPEC-1130) - - Ok(Command::new( - Self::NAME.to_string(), - self.ns.db.clone(), - body, - )) - } - - fn handle_response( - &self, - raw_response: RawCommandResponse, - _description: &StreamDescription, - ) -> Result { - let response: WriteResponseBody = raw_response.body_utf8_lossy()?; - response.validate().map_err(convert_bulk_errors)?; - - let modified_count = response.n_modified; - let upserted_id = response - .upserted - .as_ref() - .and_then(|v| v.first()) - .and_then(|doc| doc.get("_id")) - .map(Clone::clone); - - let matched_count = if upserted_id.is_some() { 0 } else { response.n }; - - Ok(UpdateResult { - matched_count, - modified_count, - upserted_id, - }) - } - - fn write_concern(&self) -> Option<&WriteConcern> { - self.options - .as_ref() - .and_then(|opts| opts.write_concern.as_ref()) - } - - fn retryability(&self) -> Retryability { - if self.multi != Some(true) { - Retryability::Write - } else { - Retryability::None - } - } -} - -#[derive(Deserialize)] -pub(crate) struct UpdateBody { - #[serde(rename = "nModified")] - n_modified: u64, - upserted: Option>, -} diff --git a/src/operation/update/test.rs b/src/operation/update/test.rs deleted file mode 100644 index 25e1c0ce4..000000000 --- a/src/operation/update/test.rs +++ /dev/null @@ -1,260 +0,0 @@ -use pretty_assertions::assert_eq; - -use crate::{ - bson::{doc, Bson}, - bson_util, - cmap::StreamDescription, - coll::options::Hint, - concern::{Acknowledgment, WriteConcern}, - error::{ErrorKind, WriteConcernError, WriteError, WriteFailure}, - operation::{test::handle_response_test, Operation, Update}, - options::{UpdateModifications, UpdateOptions}, - Namespace, -}; - -#[test] -fn build() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "x": { "$inc": 1 } }); - let wc = WriteConcern { - w: Some(Acknowledgment::Majority), - ..Default::default() - }; - let options = UpdateOptions { - upsert: Some(false), - bypass_document_validation: Some(true), - write_concern: Some(wc), - ..Default::default() - }; - - let mut op = Update::new(ns, filter.clone(), update.clone(), false, Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "update"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "update": "test_coll", - "updates": [ - { - "q": filter, - "u": update.to_bson(), - "upsert": false, - } - ], - "writeConcern": { - "w": "majority" - }, - "bypassDocumentValidation": true, - "ordered": true, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_hint() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "x": { "$inc": 1 } }); - let wc = WriteConcern { - w: Some(Acknowledgment::Majority), - ..Default::default() - }; - let options = UpdateOptions { - upsert: Some(false), - bypass_document_validation: Some(true), - write_concern: Some(wc), - hint: Some(Hint::Keys(doc! { "x": 1, "y": -1 })), - ..Default::default() - }; - - let mut op = Update::new(ns, filter.clone(), update.clone(), false, Some(options)); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "update"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "update": "test_coll", - "updates": [ - { - "q": filter, - "u": update.to_bson(), - "upsert": false, - "hint": { - "x": 1, - "y": -1, - }, - } - ], - "writeConcern": { - "w": "majority" - }, - "bypassDocumentValidation": true, - "ordered": true, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn build_many() { - let ns = Namespace { - db: "test_db".to_string(), - coll: "test_coll".to_string(), - }; - let filter = doc! { "x": { "$gt": 1 } }; - let update = UpdateModifications::Document(doc! { "x": { "$inc": 1 } }); - - let mut op = Update::new(ns, filter.clone(), update.clone(), true, None); - - let description = StreamDescription::new_testing(); - let mut cmd = op.build(&description).unwrap(); - - assert_eq!(cmd.name.as_str(), "update"); - assert_eq!(cmd.target_db.as_str(), "test_db"); - - let mut expected_body = doc! { - "update": "test_coll", - "updates": [ - { - "q": filter, - "u": update.to_bson(), - "multi": true, - } - ], - "ordered": true, - }; - - bson_util::sort_document(&mut cmd.body); - bson_util::sort_document(&mut expected_body); - - assert_eq!(cmd.body, expected_body); -} - -#[test] -fn handle_success() { - let op = Update::empty(); - - let ok_response = doc! { - "ok": 1.0, - "n": 3, - "nModified": 1, - "upserted": [ - { "index": 0, "_id": 1 } - ] - }; - - let update_result = handle_response_test(&op, ok_response).unwrap(); - assert_eq!(update_result.matched_count, 0); - assert_eq!(update_result.modified_count, 1); - assert_eq!(update_result.upserted_id, Some(Bson::Int32(1))); -} - -#[test] -fn handle_success_no_upsert() { - let op = Update::empty(); - - let ok_response = doc! { - "ok": 1.0, - "n": 5, - "nModified": 2 - }; - - let update_result = handle_response_test(&op, ok_response).unwrap(); - assert_eq!(update_result.matched_count, 5); - assert_eq!(update_result.modified_count, 2); - assert_eq!(update_result.upserted_id, None); -} - -#[test] -fn handle_write_failure() { - let op = Update::empty(); - - let write_error_response = doc! { - "ok": 1.0, - "n": 12, - "nModified": 0, - "writeErrors": [ - { - "index": 0, - "code": 1234, - "errmsg": "my error string" - } - ] - }; - - let write_error = handle_response_test(&op, write_error_response).unwrap_err(); - match *write_error.kind { - ErrorKind::Write(WriteFailure::WriteError(ref error)) => { - let expected_err = WriteError { - code: 1234, - code_name: None, - message: "my error string".to_string(), - details: None, - }; - assert_eq!(error, &expected_err); - } - ref e => panic!("expected write error, got {:?}", e), - }; -} - -#[test] -fn handle_write_concern_failure() { - let op = Update::empty(); - - let wc_error_response = doc! { - "ok": 1.0, - "n": 0, - "nModified": 0, - "writeConcernError": { - "code": 456, - "codeName": "wcError", - "errmsg": "some message", - "errInfo": { - "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } - } - } - }; - - let wc_error = handle_response_test(&op, wc_error_response).unwrap_err(); - match *wc_error.kind { - ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_error)) => { - let expected_wc_err = WriteConcernError { - code: 456, - code_name: "wcError".to_string(), - message: "some message".to_string(), - details: Some(doc! { "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } }), - labels: vec![], - }; - assert_eq!(wc_error, &expected_wc_err); - } - ref e => panic!("expected write concern error, got {:?}", e), - } -} diff --git a/src/options.rs b/src/options.rs index 2851fb009..831b3ac09 100644 --- a/src/options.rs +++ b/src/options.rs @@ -15,16 +15,27 @@ //! .build(); //! ``` +#[cfg(feature = "in-use-encryption")] +pub use crate::action::csfle::{DataKeyOptions, EncryptOptions}; +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +pub use crate::compression::compressors::Compressor; pub use crate::{ change_stream::options::*, - client::{auth::*, options::*}, + client::{ + auth::{oidc, *}, + options::*, + }, coll::options::*, collation::*, - compression::*, concern::*, db::options::*, gridfs::options::*, index::options::*, + search_index::options::*, selection_criteria::*, }; @@ -34,12 +45,9 @@ macro_rules! resolve_options { ($obj:expr, $opts:expr, [$( $field:ident ),+] ) => { $( if let Some(option) = $obj.$field() { - if !$opts - .as_ref() - .map(|opts| opts.$field.is_some()) - .unwrap_or(false) - { - $opts.get_or_insert_with(Default::default).$field = Some(option.clone()); + let options = $opts.get_or_insert_with(Default::default); + if !options.$field.is_some() { + options.$field = Some(option.clone()); } } )+ @@ -88,7 +96,7 @@ macro_rules! resolve_rw_concern_with_session { .map(|opts| opts.$concern.is_some()) .unwrap_or(false) { - return Err(ErrorKind::InvalidArgument { + return Err(crate::error::ErrorKind::InvalidArgument { message: format!( "Cannot set {} concern after starting a transaction", $name diff --git a/src/results.rs b/src/results.rs index e84e814f4..d2e5d23a9 100644 --- a/src/results.rs +++ b/src/results.rs @@ -1,20 +1,25 @@ //! Contains the types of results returned by CRUD operations. +mod bulk_write; + use std::collections::{HashMap, VecDeque}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; + use crate::{ - bson::{Bson, Document}, - bson_util, + bson::{Binary, Bson, Document, RawDocumentBuf}, change_stream::event::ResumeToken, db::options::CreateCollectionOptions, + serde_util, + Namespace, }; -use bson::{Binary, RawDocumentBuf}; -use serde::{Deserialize, Serialize}; +pub use bulk_write::*; /// The result of a [`Collection::insert_one`](../struct.Collection.html#method.insert_one) /// operation. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct InsertOneResult { @@ -32,7 +37,7 @@ impl InsertOneResult { /// The result of a [`Collection::insert_many`](../struct.Collection.html#method.insert_many) /// operation. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct InsertManyResult { @@ -40,26 +45,19 @@ pub struct InsertManyResult { pub inserted_ids: HashMap, } -impl InsertManyResult { - pub(crate) fn new() -> Self { - InsertManyResult { - inserted_ids: HashMap::new(), - } - } -} - /// The result of a [`Collection::update_one`](../struct.Collection.html#method.update_one) or /// [`Collection::update_many`](../struct.Collection.html#method.update_many) operation. -#[derive(Debug, Serialize)] +#[skip_serializing_none] +#[derive(Clone, Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct UpdateResult { /// The number of documents that matched the filter. - #[serde(serialize_with = "crate::bson::serde_helpers::serialize_u64_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_as_i64")] pub matched_count: u64, /// The number of documents that were modified by the operation. - #[serde(serialize_with = "crate::bson::serde_helpers::serialize_u64_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_as_i64")] pub modified_count: u64, /// The `_id` field of the upserted document. @@ -68,18 +66,18 @@ pub struct UpdateResult { /// The result of a [`Collection::delete_one`](../struct.Collection.html#method.delete_one) or /// [`Collection::delete_many`](../struct.Collection.html#method.delete_many) operation. -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct DeleteResult { /// The number of documents deleted by the operation. - #[serde(serialize_with = "crate::bson::serde_helpers::serialize_u64_as_i64")] + #[serde(serialize_with = "serde_util::serialize_u64_as_i64")] pub deleted_count: u64, } /// Information about the index created as a result of a /// [`Collection::create_index`](../struct.Collection.html#method.create_index). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] #[non_exhaustive] pub struct CreateIndexResult { /// The name of the index created in the `createIndex` command. @@ -88,7 +86,7 @@ pub struct CreateIndexResult { /// Information about the indexes created as a result of a /// [`Collection::create_indexes`](../struct.Collection.html#method.create_indexes). -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Default)] #[non_exhaustive] pub struct CreateIndexesResult { /// The list containing the names of all indexes created in the `createIndexes` command. @@ -108,11 +106,13 @@ pub(crate) struct GetMoreResult { pub(crate) batch: VecDeque, pub(crate) exhausted: bool, pub(crate) post_batch_resume_token: Option, + pub(crate) ns: Namespace, + pub(crate) id: i64, } /// Describes the type of data store returned when executing /// [`Database::list_collections`](../struct.Database.html#method.list_collections). -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub enum CollectionType { @@ -120,6 +120,7 @@ pub enum CollectionType { View, /// Indicates that the data store is a collection. + #[default] Collection, /// Indicates that the data store is a timeseries. @@ -132,7 +133,7 @@ pub enum CollectionType { /// /// See the MongoDB [manual](https://www.mongodb.com/docs/manual/reference/command/listCollections/#listCollections.cursor) /// for more information. -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct CollectionSpecificationInfo { @@ -147,7 +148,7 @@ pub struct CollectionSpecificationInfo { /// Information about a collection as reported by /// [`Database::list_collections`](../struct.Database.html#method.list_collections). -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct CollectionSpecification { @@ -171,7 +172,7 @@ pub struct CollectionSpecification { /// A struct modeling the information about an individual database returned from /// [`Client::list_databases`](../struct.Client.html#method.list_databases). -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct DatabaseSpecification { @@ -180,8 +181,8 @@ pub struct DatabaseSpecification { /// The amount of disk space in bytes that is consumed by the database. #[serde( - deserialize_with = "bson_util::deserialize_u64_from_bson_number", - serialize_with = "crate::bson::serde_helpers::serialize_u64_as_i64" + deserialize_with = "serde_util::deserialize_u64_from_bson_number", + serialize_with = "serde_util::serialize_u64_as_i64" )] pub size_on_disk: u64, diff --git a/src/results/bulk_write.rs b/src/results/bulk_write.rs new file mode 100644 index 000000000..2fdf010e5 --- /dev/null +++ b/src/results/bulk_write.rs @@ -0,0 +1,183 @@ +use std::{collections::HashMap, fmt::Debug}; + +use crate::{ + error::bulk_write::PartialBulkWriteResult, + results::{DeleteResult, InsertOneResult, UpdateResult}, +}; + +/// Summary results returned from a [`bulk_write`](crate::Client::bulk_write) operation. +#[cfg_attr(test, serde_with::skip_serializing_none)] +#[derive(Clone, Debug, Default)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(rename_all = "camelCase"))] +#[non_exhaustive] +pub struct SummaryBulkWriteResult { + /// The total number of documents inserted across all operations. + pub inserted_count: i64, + + /// The total number of documents matched across all operations. + pub matched_count: i64, + + /// The total number of documents modified across all operations. + pub modified_count: i64, + + /// The total number of documents upserted across all operations. + pub upserted_count: i64, + + /// The total number of documents deleted across all operations. + pub deleted_count: i64, +} + +/// Verbose results returned from a [`bulk_write`](crate::Client::bulk_write) operation. +#[cfg_attr(test, serde_with::skip_serializing_none)] +#[derive(Clone, Debug, Default)] +#[cfg_attr(test, derive(serde::Serialize))] +#[cfg_attr(test, serde(rename_all = "camelCase"))] +#[non_exhaustive] +pub struct VerboseBulkWriteResult { + /// The summary results. + #[cfg_attr(test, serde(flatten))] + pub summary: SummaryBulkWriteResult, + + /// The results of each insert operation that was successfully performed. + #[cfg_attr( + test, + serde(serialize_with = "crate::serde_util::serialize_indexed_map") + )] + pub insert_results: HashMap, + + /// The results of each update operation that was successfully performed. + #[cfg_attr( + test, + serde(serialize_with = "crate::serde_util::serialize_indexed_map") + )] + pub update_results: HashMap, + + /// The results of each delete operation that was successfully performed. + #[cfg_attr( + test, + serde(serialize_with = "crate::serde_util::serialize_indexed_map") + )] + pub delete_results: HashMap, +} + +mod result_trait { + use crate::{ + error::bulk_write::PartialBulkWriteResult, + results::{DeleteResult, InsertOneResult, UpdateResult}, + }; + + pub trait BulkWriteResult: Default + Send + Sync { + fn errors_only() -> bool; + + fn merge(&mut self, other: Self); + + fn into_partial_result(self) -> PartialBulkWriteResult; + + fn populate_summary_info( + &mut self, + n_inserted: i64, + n_matched: i64, + n_modified: i64, + n_upserted: i64, + n_deleted: i64, + ); + + fn add_insert_result(&mut self, _index: usize, _insert_result: InsertOneResult) {} + + fn add_update_result(&mut self, _index: usize, _update_result: UpdateResult) {} + + fn add_delete_result(&mut self, _index: usize, _delete_result: DeleteResult) {} + } +} + +pub(crate) use result_trait::BulkWriteResult; + +impl BulkWriteResult for SummaryBulkWriteResult { + fn errors_only() -> bool { + true + } + + fn merge(&mut self, other: Self) { + let SummaryBulkWriteResult { + inserted_count: other_inserted_count, + matched_count: other_matched_count, + modified_count: other_modified_count, + upserted_count: other_upserted_count, + deleted_count: other_deleted_count, + } = other; + + self.inserted_count += other_inserted_count; + self.matched_count += other_matched_count; + self.modified_count += other_modified_count; + self.upserted_count += other_upserted_count; + self.deleted_count += other_deleted_count; + } + + fn into_partial_result(self) -> PartialBulkWriteResult { + PartialBulkWriteResult::Summary(self) + } + + fn populate_summary_info( + &mut self, + n_inserted: i64, + n_matched: i64, + n_modified: i64, + n_upserted: i64, + n_deleted: i64, + ) { + self.inserted_count += n_inserted; + self.matched_count += n_matched; + self.modified_count += n_modified; + self.upserted_count += n_upserted; + self.deleted_count += n_deleted; + } +} + +impl BulkWriteResult for VerboseBulkWriteResult { + fn errors_only() -> bool { + false + } + + fn merge(&mut self, other: Self) { + let VerboseBulkWriteResult { + summary: other_summary, + insert_results: other_insert_results, + update_results: other_update_results, + delete_results: other_delete_results, + } = other; + + self.summary.merge(other_summary); + self.insert_results.extend(other_insert_results); + self.update_results.extend(other_update_results); + self.delete_results.extend(other_delete_results); + } + + fn into_partial_result(self) -> PartialBulkWriteResult { + PartialBulkWriteResult::Verbose(self) + } + + fn populate_summary_info( + &mut self, + n_inserted: i64, + n_matched: i64, + n_modified: i64, + n_upserted: i64, + n_deleted: i64, + ) { + self.summary + .populate_summary_info(n_inserted, n_matched, n_modified, n_upserted, n_deleted); + } + + fn add_insert_result(&mut self, index: usize, insert_result: InsertOneResult) { + self.insert_results.insert(index, insert_result); + } + + fn add_update_result(&mut self, index: usize, update_result: UpdateResult) { + self.update_results.insert(index, update_result); + } + + fn add_delete_result(&mut self, index: usize, delete_result: DeleteResult) { + self.delete_results.insert(index, delete_result); + } +} diff --git a/src/runtime.rs b/src/runtime.rs new file mode 100644 index 000000000..e46605bb2 --- /dev/null +++ b/src/runtime.rs @@ -0,0 +1,80 @@ +mod acknowledged_message; +#[cfg(any( + feature = "aws-auth", + feature = "azure-kms", + feature = "gcp-kms", + feature = "azure-oidc", + feature = "gcp-oidc" +))] +mod http; +mod join_handle; +#[cfg(feature = "cert-key-password")] +mod pem; +#[cfg(any(feature = "in-use-encryption", test))] +pub(crate) mod process; +#[cfg(feature = "dns-resolver")] +mod resolver; +pub(crate) mod stream; +mod sync_read_ext; +#[cfg(feature = "openssl-tls")] +mod tls_openssl; +#[cfg(feature = "rustls-tls")] +#[cfg_attr(feature = "openssl-tls", allow(unused))] +mod tls_rustls; +mod worker_handle; + +use std::{future::Future, net::SocketAddr, time::Duration}; + +#[cfg(feature = "dns-resolver")] +pub(crate) use self::resolver::AsyncResolver; +pub(crate) use self::{ + acknowledged_message::{AcknowledgedMessage, AcknowledgmentReceiver, AcknowledgmentSender}, + join_handle::AsyncJoinHandle, + stream::AsyncStream, + sync_read_ext::SyncLittleEndianRead, + worker_handle::{WorkerHandle, WorkerHandleListener}, +}; +use crate::{error::Result, options::ServerAddress}; +#[cfg(any( + feature = "aws-auth", + feature = "azure-kms", + feature = "gcp-kms", + feature = "azure-oidc", + feature = "gcp-oidc" +))] +pub(crate) use http::HttpClient; +#[cfg(feature = "openssl-tls")] +use tls_openssl as tls; +#[cfg(all(feature = "rustls-tls", not(feature = "openssl-tls")))] +use tls_rustls as tls; +#[cfg(not(any(feature = "rustls-tls", feature = "openssl-tls")))] +compile_error!("At least one of the features 'rustls-tls' or 'openssl-tls' must be enabled."); + +pub(crate) use tls::TlsConfig; + +/// Spawn a task in the background to run a future. +/// +/// This must be called from an async block +/// or function running on a runtime. +#[track_caller] +pub(crate) fn spawn(fut: F) -> AsyncJoinHandle +where + F: Future + Send + 'static, + O: Send + 'static, +{ + AsyncJoinHandle::spawn(fut) +} + +/// Await on a future for a maximum amount of time before returning an error. +pub(crate) async fn timeout(timeout: Duration, future: F) -> Result { + tokio::time::timeout(timeout, future) + .await + .map_err(|_| std::io::ErrorKind::TimedOut.into()) +} + +pub(crate) async fn resolve_address( + address: &ServerAddress, +) -> Result> { + let socket_addrs = tokio::net::lookup_host(format!("{}", address)).await?; + Ok(socket_addrs) +} diff --git a/src/runtime/acknowledged_message.rs b/src/runtime/acknowledged_message.rs index 24963bd16..9d0e5e06c 100644 --- a/src/runtime/acknowledged_message.rs +++ b/src/runtime/acknowledged_message.rs @@ -1,3 +1,7 @@ +use std::{pin::Pin, task::Poll}; + +use futures_core::Future; + /// A message type that includes an acknowledgement mechanism. /// When this is dropped or `acknowledge` is called, the sender will be notified. #[derive(Debug)] @@ -63,3 +67,14 @@ impl AcknowledgmentReceiver { self.receiver.await.ok() } } + +impl Future for AcknowledgmentReceiver { + type Output = Option; + + fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + match Pin::new(&mut self.get_mut().receiver).poll(cx) { + Poll::Ready(r) => Poll::Ready(r.ok()), + Poll::Pending => Poll::Pending, + } + } +} diff --git a/src/runtime/interval.rs b/src/runtime/interval.rs deleted file mode 100644 index 34b8543fc..000000000 --- a/src/runtime/interval.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::time::{Duration, Instant}; - -use crate::runtime; - -/// Interval implementation using async-std. -/// For tokio, we just use tokio::time::Interval. -pub(crate) struct Interval { - interval: Duration, - last_time: Instant, -} - -#[cfg(feature = "async-std-runtime")] -impl Interval { - pub(super) fn new(interval: Duration) -> Self { - Self { - interval, - last_time: Instant::now(), - } - } - - pub(crate) async fn tick(&mut self) -> Instant { - match self.interval.checked_sub(self.last_time.elapsed()) { - Some(duration) => { - runtime::delay_for(duration).await; - self.last_time = Instant::now(); - } - None => self.last_time = Instant::now(), - } - self.last_time - } -} diff --git a/src/runtime/join_handle.rs b/src/runtime/join_handle.rs index a0603cbf2..9bf413833 100644 --- a/src/runtime/join_handle.rs +++ b/src/runtime/join_handle.rs @@ -1,40 +1,35 @@ use std::{ future::Future, - ops::DerefMut, pin::Pin, task::{Context, Poll}, }; -/// A runtime-agnostic handle used for awaiting on tasks spawned in `AsyncRuntime::execute`. -/// Wraps either `tokio::task::JoinHandle` or `async_std::task::JoinHandle`. -/// -/// Note: the `Future::Output` of this handle is `Result`, not just `T`. +/// A handle used for awaiting on tasks spawned in `AsyncRuntime::execute`. #[derive(Debug)] -pub(crate) enum AsyncJoinHandle { - /// Wrapper around `tokio::task:JoinHandle`. - #[cfg(feature = "tokio-runtime")] - Tokio(tokio::task::JoinHandle), +pub(crate) struct AsyncJoinHandle(tokio::task::JoinHandle); - /// Wrapper around `tokio::task:JoinHandle`. - #[cfg(feature = "async-std-runtime")] - AsyncStd(async_std::task::JoinHandle), +impl AsyncJoinHandle { + #[track_caller] + pub(crate) fn spawn(fut: F) -> Self + where + F: Future + Send + 'static, + T: Send + 'static, + { + #[cfg(not(feature = "sync"))] + let handle = tokio::runtime::Handle::current(); + #[cfg(feature = "sync")] + let handle = tokio::runtime::Handle::try_current() + .unwrap_or_else(|_| crate::sync::TOKIO_RUNTIME.handle().clone()); + AsyncJoinHandle(handle.spawn(fut)) + } } impl Future for AsyncJoinHandle { - // tokio wraps the Output of its JoinHandle in a Result that contains an error if the task - // panicked, while async-std does not. - // - // Given that async-std will panic or abort the task in this scenario, there is not a - // lot of value in preserving the error in tokio. type Output = T; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.deref_mut() { - #[cfg(feature = "tokio-runtime")] - Self::Tokio(ref mut handle) => Pin::new(handle).poll(cx).map(|result| result.unwrap()), - - #[cfg(feature = "async-std-runtime")] - Self::AsyncStd(ref mut handle) => Pin::new(handle).poll(cx), - } + // Tokio wraps the task's return value with a `Result` that catches panics; in our case + // we want to propagate the panic, so for once `unwrap` is the right tool to use. + Pin::new(&mut self.0).poll(cx).map(|result| result.unwrap()) } } diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs deleted file mode 100644 index b625a5101..000000000 --- a/src/runtime/mod.rs +++ /dev/null @@ -1,177 +0,0 @@ -mod acknowledged_message; -#[cfg(feature = "reqwest")] -mod http; -#[cfg(feature = "async-std-runtime")] -mod interval; -mod join_handle; -#[cfg(any( - feature = "in-use-encryption-unstable", - all(test, not(feature = "sync"), not(feature = "tokio-sync")) -))] -pub(crate) mod process; -mod resolver; -pub(crate) mod stream; -mod sync_read_ext; -#[cfg(feature = "openssl-tls")] -mod tls_openssl; -#[cfg_attr(feature = "openssl-tls", allow(unused))] -mod tls_rustls; -mod worker_handle; - -use std::{future::Future, net::SocketAddr, time::Duration}; - -pub(crate) use self::{ - acknowledged_message::AcknowledgedMessage, - join_handle::AsyncJoinHandle, - resolver::AsyncResolver, - stream::AsyncStream, - sync_read_ext::SyncLittleEndianRead, - worker_handle::{WorkerHandle, WorkerHandleListener}, -}; -use crate::{error::Result, options::ServerAddress}; -#[cfg(feature = "reqwest")] -pub(crate) use http::HttpClient; -#[cfg(feature = "async-std-runtime")] -use interval::Interval; -#[cfg(feature = "openssl-tls")] -use tls_openssl as tls; -#[cfg(not(feature = "openssl-tls"))] -use tls_rustls as tls; -#[cfg(feature = "tokio-runtime")] -use tokio::time::Interval; - -pub(crate) use tls::TlsConfig; - -/// Spawn a task in the background to run a future. -/// -/// If the runtime is still running, this will return a handle to the background task. -/// Otherwise, it will return `None`. As a result, this must be called from an async block -/// or function running on a runtime. -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn spawn(fut: F) -> AsyncJoinHandle -where - F: Future + Send + 'static, - O: Send + 'static, -{ - #[cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] - { - let handle = tokio::runtime::Handle::current(); - AsyncJoinHandle::Tokio(handle.spawn(fut)) - } - - #[cfg(feature = "tokio-sync")] - { - let handle = crate::sync::TOKIO_RUNTIME.handle(); - AsyncJoinHandle::Tokio(handle.spawn(fut)) - } - - #[cfg(feature = "async-std-runtime")] - { - AsyncJoinHandle::AsyncStd(async_std::task::spawn(fut)) - } -} - -/// Spawn a task in the background to run a future. -/// -/// Note: this must only be called from an async block or function running on a runtime. -pub(crate) fn execute(fut: F) -where - F: Future + Send + 'static, - O: Send + 'static, -{ - spawn(fut); -} - -#[cfg(any(test, feature = "sync", feature = "tokio-sync"))] -pub(crate) fn block_on(fut: F) -> T -where - F: Future, -{ - #[cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] - { - tokio::task::block_in_place(|| futures::executor::block_on(fut)) - } - - #[cfg(feature = "tokio-sync")] - { - crate::sync::TOKIO_RUNTIME.block_on(fut) - } - - #[cfg(feature = "async-std-runtime")] - { - async_std::task::block_on(fut) - } -} - -/// Delay for the specified duration. -pub(crate) async fn delay_for(delay: Duration) { - #[cfg(feature = "tokio-runtime")] - { - tokio::time::sleep(delay).await - } - - #[cfg(feature = "async-std-runtime")] - { - // This avoids a panic in async-std when the provided duration is too large. - // See: https://github.com/async-rs/async-std/issues/1037. - if delay == Duration::MAX { - std::future::pending().await - } else { - async_std::task::sleep(delay).await - } - } -} - -/// Await on a future for a maximum amount of time before returning an error. -pub(crate) async fn timeout(timeout: Duration, future: F) -> Result { - #[cfg(feature = "tokio-runtime")] - { - tokio::time::timeout(timeout, future) - .await - .map_err(|_| std::io::ErrorKind::TimedOut.into()) - } - - #[cfg(feature = "async-std-runtime")] - { - // This avoids a panic on async-std when the provided duration is too large. - // See: https://github.com/async-rs/async-std/issues/1037. - if timeout == Duration::MAX { - Ok(future.await) - } else { - async_std::future::timeout(timeout, future) - .await - .map_err(|_| std::io::ErrorKind::TimedOut.into()) - } - } -} - -/// Create a new `Interval` that yields with interval of `duration`. -/// See: -pub(crate) fn interval(duration: Duration) -> Interval { - #[cfg(feature = "tokio-runtime")] - { - tokio::time::interval(duration) - } - - #[cfg(feature = "async-std-runtime")] - { - Interval::new(duration) - } -} - -pub(crate) async fn resolve_address( - address: &ServerAddress, -) -> Result> { - #[cfg(feature = "tokio-runtime")] - { - let socket_addrs = tokio::net::lookup_host(format!("{}", address)).await?; - Ok(socket_addrs) - } - - #[cfg(feature = "async-std-runtime")] - { - let host = (address.host(), address.port().unwrap_or(27017)); - let socket_addrs = async_std::net::ToSocketAddrs::to_socket_addrs(&host).await?; - Ok(socket_addrs) - } -} diff --git a/src/runtime/pem.rs b/src/runtime/pem.rs new file mode 100644 index 000000000..ef3c3109f --- /dev/null +++ b/src/runtime/pem.rs @@ -0,0 +1,30 @@ +use crate::error::{ErrorKind, Result}; + +pub(crate) fn decrypt_private_key(pem_data: &[u8], password: &[u8]) -> Result> { + let pems = pem::parse_many(pem_data).map_err(|error| ErrorKind::InvalidTlsConfig { + message: format!("Could not parse pemfile: {}", error), + })?; + let mut iter = pems + .into_iter() + .filter(|pem| pem.tag() == "ENCRYPTED PRIVATE KEY"); + let encrypted_bytes = match iter.next() { + Some(pem) => pem.into_contents(), + None => { + return Err(ErrorKind::InvalidTlsConfig { + message: "No encrypted private keys found".into(), + } + .into()) + } + }; + let encrypted_key = pkcs8::EncryptedPrivateKeyInfo::try_from(encrypted_bytes.as_slice()) + .map_err(|error| ErrorKind::InvalidTlsConfig { + message: format!("Invalid encrypted private key: {}", error), + })?; + let decrypted_key = + encrypted_key + .decrypt(password) + .map_err(|error| ErrorKind::InvalidTlsConfig { + message: format!("Failed to decrypt private key: {}", error), + })?; + Ok(decrypted_key.as_bytes().to_vec()) +} diff --git a/src/runtime/process.rs b/src/runtime/process.rs index 604b2e3d9..96dd13f63 100644 --- a/src/runtime/process.rs +++ b/src/runtime/process.rs @@ -3,9 +3,6 @@ use std::{ process::{ExitStatus, Stdio}, }; -#[cfg(feature = "async-std-runtime")] -use async_std::process::{Child, Command}; -#[cfg(feature = "tokio-runtime")] use tokio::process::{Child, Command}; use crate::error::Result; @@ -34,26 +31,17 @@ impl Process { /// Issue a kill signal to the child process and immediately return; to wait for the process to /// actually exit, use `wait`. pub(crate) fn kill(&mut self) -> Result<()> { - #[cfg(feature = "tokio-runtime")] - return Ok(self.child.start_kill()?); - #[cfg(feature = "async-std-runtime")] - return Ok(self.child.kill()?); + Ok(self.child.start_kill()?) } pub(crate) async fn wait(&mut self) -> Result { - #[cfg(feature = "tokio-runtime")] - return Ok(self.child.wait().await?); - #[cfg(feature = "async-std-runtime")] - return Ok(self.child.status().await?); + Ok(self.child.wait().await?) } } impl Drop for Process { fn drop(&mut self) { // Attempt to reap the process. - #[cfg(feature = "tokio-runtime")] let _ = self.child.try_wait(); - #[cfg(feature = "async-std-runtime")] - let _ = self.child.try_status(); } } diff --git a/src/runtime/resolver.rs b/src/runtime/resolver.rs index d8daacbca..94ba80123 100644 --- a/src/runtime/resolver.rs +++ b/src/runtime/resolver.rs @@ -1,40 +1,30 @@ -use trust_dns_resolver::{ +use crate::error::{Error, Result}; +use hickory_resolver::{ config::ResolverConfig, error::ResolveErrorKind, lookup::{SrvLookup, TxtLookup}, - IntoName, + Name, }; -use crate::error::{Error, Result}; +#[cfg(feature = "gssapi-auth")] +use hickory_resolver::{ + lookup::{Lookup, ReverseLookup}, + lookup_ip::LookupIp, + proto::rr::RecordType, +}; +#[cfg(feature = "gssapi-auth")] +use std::net::IpAddr; /// An async runtime agnostic DNS resolver. pub(crate) struct AsyncResolver { - #[cfg(feature = "tokio-runtime")] - resolver: trust_dns_resolver::TokioAsyncResolver, - - #[cfg(feature = "async-std-runtime")] - resolver: async_std_resolver::AsyncStdResolver, + resolver: hickory_resolver::TokioAsyncResolver, } impl AsyncResolver { pub(crate) async fn new(config: Option) -> Result { - #[cfg(feature = "tokio-runtime")] let resolver = match config { - Some(config) => { - trust_dns_resolver::TokioAsyncResolver::tokio(config, Default::default()) - .map_err(Error::from_resolve_error)? - } - None => trust_dns_resolver::TokioAsyncResolver::tokio_from_system_conf() - .map_err(Error::from_resolve_error)?, - }; - - #[cfg(feature = "async-std-runtime")] - let resolver = match config { - Some(config) => async_std_resolver::resolver(config, Default::default()) - .await - .map_err(Error::from_resolve_error)?, - None => async_std_resolver::resolver_from_system_conf() - .await + Some(config) => hickory_resolver::TokioAsyncResolver::tokio(config, Default::default()), + None => hickory_resolver::TokioAsyncResolver::tokio_from_system_conf() .map_err(Error::from_resolve_error)?, }; @@ -43,17 +33,51 @@ impl AsyncResolver { } impl AsyncResolver { - pub async fn srv_lookup(&self, query: N) -> Result { + #[cfg(feature = "gssapi-auth")] + pub async fn cname_lookup(&self, query: &str) -> Result { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; + let lookup = self + .resolver + .lookup(name, RecordType::CNAME) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + + #[cfg(feature = "gssapi-auth")] + pub async fn ip_lookup(&self, query: &str) -> Result { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; + let lookup = self + .resolver + .lookup_ip(name) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + + #[cfg(feature = "gssapi-auth")] + pub async fn reverse_lookup(&self, ip_addr: IpAddr) -> Result { + let lookup = self + .resolver + .reverse_lookup(ip_addr) + .await + .map_err(Error::from_resolve_error)?; + Ok(lookup) + } + + pub async fn srv_lookup(&self, query: &str) -> Result { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; let lookup = self .resolver - .srv_lookup(query) + .srv_lookup(name) .await .map_err(Error::from_resolve_error)?; Ok(lookup) } - pub async fn txt_lookup(&self, query: N) -> Result> { - let lookup_result = self.resolver.txt_lookup(query).await; + pub async fn txt_lookup(&self, query: &str) -> Result> { + let name = Name::from_str_relaxed(query).map_err(Error::from_resolve_proto_error)?; + let lookup_result = self.resolver.txt_lookup(name).await; match lookup_result { Ok(lookup) => Ok(Some(lookup)), Err(e) => match e.kind() { diff --git a/src/runtime/stream.rs b/src/runtime/stream.rs index 9ca6a0c55..8d9ef9762 100644 --- a/src/runtime/stream.rs +++ b/src/runtime/stream.rs @@ -6,30 +6,38 @@ use std::{ time::Duration, }; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::{io::AsyncWrite, net::TcpStream}; use crate::{ - error::{ErrorKind, Result}, + error::{Error, ErrorKind, Result}, options::ServerAddress, runtime, }; -use super::{tls::AsyncTlsStream, TlsConfig}; +use super::{ + tls::{tls_connect, TlsStream}, + TlsConfig, +}; pub(crate) const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); +#[cfg(not(target_os = "wasi"))] const KEEPALIVE_TIME: Duration = Duration::from_secs(120); -/// A runtime-agnostic async stream possibly using TLS. +/// An async stream possibly using TLS. #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub(crate) enum AsyncStream { Null, /// A basic TCP connection to the server. - Tcp(AsyncTcpStream), + Tcp(TcpStream), /// A TLS connection over TCP. - Tls(AsyncTlsStream), + Tls(TlsStream), + + /// A Unix domain socket connection. + #[cfg(unix)] + Unix(tokio::net::UnixStream), } impl AsyncStream { @@ -37,111 +45,122 @@ impl AsyncStream { address: ServerAddress, tls_cfg: Option<&TlsConfig>, ) -> Result { - let inner = AsyncTcpStream::connect(&address).await?; - - // If there are TLS options, wrap the inner stream in an AsyncTlsStream. - match tls_cfg { - Some(cfg) => { - let host = address.host(); - Ok(AsyncStream::Tls( - AsyncTlsStream::connect(host, inner, cfg).await?, - )) + match &address { + ServerAddress::Tcp { host, .. } => { + let resolved: Vec<_> = runtime::resolve_address(&address).await?.collect(); + if resolved.is_empty() { + return Err(ErrorKind::DnsResolve { + message: format!("No DNS results for domain {}", address), + } + .into()); + } + let inner = tcp_connect(resolved).await?; + + // If there are TLS options, wrap the inner stream in an AsyncTlsStream. + match tls_cfg { + Some(cfg) => Ok(AsyncStream::Tls(tls_connect(host, inner, cfg).await?)), + None => Ok(AsyncStream::Tcp(inner)), + } } - None => Ok(AsyncStream::Tcp(inner)), + #[cfg(unix)] + ServerAddress::Unix { path } => Ok(AsyncStream::Unix( + tokio::net::UnixStream::connect(path.as_path()).await?, + )), } } } -/// A runtime-agnostic async stream. -#[derive(Debug)] -pub(crate) enum AsyncTcpStream { - /// Wrapper around `tokio::net:TcpStream`. - #[cfg(feature = "tokio-runtime")] - Tokio(tokio::net::TcpStream), - - /// Wrapper around `async_std::net::TcpStream`. - #[cfg(feature = "async-std-runtime")] - AsyncStd(async_std::net::TcpStream), -} - -#[cfg(feature = "tokio-runtime")] -impl From for AsyncTcpStream { - fn from(stream: tokio::net::TcpStream) -> Self { - Self::Tokio(stream) - } -} - -#[cfg(feature = "async-std-runtime")] -impl From for AsyncTcpStream { - fn from(stream: async_std::net::TcpStream) -> Self { - Self::AsyncStd(stream) - } -} - -impl AsyncTcpStream { - #[cfg(feature = "tokio-runtime")] - async fn try_connect(address: &SocketAddr) -> Result { - use tokio::net::TcpStream; - - let stream = TcpStream::connect(address).await?; - stream.set_nodelay(true)?; +async fn tcp_try_connect(address: &SocketAddr) -> Result { + let stream = TcpStream::connect(address).await?; + stream.set_nodelay(true)?; - let socket = socket2::Socket::from(stream.into_std()?); + let socket = socket2::Socket::from(stream.into_std()?); + #[cfg(not(target_os = "wasi"))] + { let conf = socket2::TcpKeepalive::new().with_time(KEEPALIVE_TIME); socket.set_tcp_keepalive(&conf)?; - let std_stream = std::net::TcpStream::from(socket); - let stream = TcpStream::from_std(std_stream)?; - - Ok(stream.into()) } + let std_stream = std::net::TcpStream::from(socket); + Ok(TcpStream::from_std(std_stream)?) +} - #[cfg(feature = "async-std-runtime")] - async fn try_connect(address: &SocketAddr) -> Result { - use async_std::net::TcpStream; - - let stream = TcpStream::connect(address).await?; - stream.set_nodelay(true)?; - - let std_stream: std::net::TcpStream = stream.try_into()?; - let socket = socket2::Socket::from(std_stream); - let conf = socket2::TcpKeepalive::new().with_time(KEEPALIVE_TIME); - socket.set_tcp_keepalive(&conf)?; - let std_stream = std::net::TcpStream::from(socket); - let stream = TcpStream::from(std_stream); - - Ok(stream.into()) +pub(crate) async fn tcp_connect(resolved: Vec) -> Result { + // "Happy Eyeballs": try addresses in parallel, interleaving IPv6 and IPv4, preferring IPv6. + // Based on the implementation in https://codeberg.org/KMK/happy-eyeballs. + let (addrs_v6, addrs_v4): (Vec<_>, Vec<_>) = resolved + .into_iter() + .partition(|a| matches!(a, SocketAddr::V6(_))); + let socket_addrs = interleave(addrs_v6, addrs_v4); + + fn handle_join( + result: std::result::Result, tokio::task::JoinError>, + ) -> Result { + match result { + Ok(r) => r, + // JoinError indicates the task was cancelled or paniced, which should never happen + // here. + Err(e) => Err(Error::internal(format!("TCP connect task failure: {}", e))), + } } - pub(crate) async fn connect(address: &ServerAddress) -> Result { - let mut socket_addrs: Vec<_> = runtime::resolve_address(address).await?.collect(); - - if socket_addrs.is_empty() { - return Err(ErrorKind::DnsResolve { - message: format!("No DNS results for domain {}", address), + static CONNECTION_ATTEMPT_DELAY: Duration = Duration::from_millis(250); + + // Race connections + let mut attempts = tokio::task::JoinSet::new(); + let mut connect_error = None; + 'spawn: for a in socket_addrs { + attempts.spawn(async move { tcp_try_connect(&a).await }); + let sleep = tokio::time::sleep(CONNECTION_ATTEMPT_DELAY); + tokio::pin!(sleep); // required for select! + while !attempts.is_empty() { + tokio::select! { + biased; + connect_res = attempts.join_next() => { + match connect_res.map(handle_join) { + // The gating `while !attempts.is_empty()` should mean this never happens. + None => return Err(Error::internal("empty TCP connect task set")), + // A connection succeeded, return it. The JoinSet will cancel remaining tasks on drop. + Some(Ok(cnx)) => return Ok(cnx), + // A connection failed. Remember the error and wait for any other remaining attempts. + Some(Err(e)) => { + connect_error.get_or_insert(e); + }, + } + } + // CONNECTION_ATTEMPT_DELAY expired, spawn a new connection attempt. + _ = &mut sleep => continue 'spawn } - .into()); } + } - // After considering various approaches, we decided to do what other drivers do, namely try - // each of the addresses in sequence with a preference for IPv4. - socket_addrs.sort_by_key(|addr| if addr.is_ipv4() { 0 } else { 1 }); - - let mut connect_error = None; + // No more address to try. Drain the attempts until one succeeds. + while let Some(result) = attempts.join_next().await { + match handle_join(result) { + Ok(cnx) => return Ok(cnx), + Err(e) => { + connect_error.get_or_insert(e); + } + } + } - for address in &socket_addrs { - connect_error = match Self::try_connect(address).await { - Ok(stream) => return Ok(stream), - Err(err) => Some(err), - }; + // All attempts failed. Return the first error. + Err(connect_error.unwrap_or_else(|| { + ErrorKind::Internal { + message: "connecting to all DNS results failed but no error reported".to_string(), } + .into() + })) +} - Err(connect_error.unwrap_or_else(|| { - ErrorKind::Internal { - message: "connecting to all DNS results failed but no error reported".to_string(), - } - .into() - })) +fn interleave(left: Vec, right: Vec) -> Vec { + let mut out = Vec::with_capacity(left.len() + right.len()); + let (mut left, mut right) = (left.into_iter(), right.into_iter()); + while let Some(a) = left.next() { + out.push(a); + std::mem::swap(&mut left, &mut right); } + out.extend(right); + out } impl tokio::io::AsyncRead for AsyncStream { @@ -154,6 +173,8 @@ impl tokio::io::AsyncRead for AsyncStream { Self::Null => Poll::Ready(Ok(())), Self::Tcp(ref mut inner) => tokio::io::AsyncRead::poll_read(Pin::new(inner), cx, buf), Self::Tls(ref mut inner) => tokio::io::AsyncRead::poll_read(Pin::new(inner), cx, buf), + #[cfg(unix)] + Self::Unix(ref mut inner) => tokio::io::AsyncRead::poll_read(Pin::new(inner), cx, buf), } } } @@ -168,6 +189,8 @@ impl AsyncWrite for AsyncStream { Self::Null => Poll::Ready(Ok(0)), Self::Tcp(ref mut inner) => AsyncWrite::poll_write(Pin::new(inner), cx, buf), Self::Tls(ref mut inner) => Pin::new(inner).poll_write(cx, buf), + #[cfg(unix)] + Self::Unix(ref mut inner) => AsyncWrite::poll_write(Pin::new(inner), cx, buf), } } @@ -176,6 +199,8 @@ impl AsyncWrite for AsyncStream { Self::Null => Poll::Ready(Ok(())), Self::Tcp(ref mut inner) => AsyncWrite::poll_flush(Pin::new(inner), cx), Self::Tls(ref mut inner) => Pin::new(inner).poll_flush(cx), + #[cfg(unix)] + Self::Unix(ref mut inner) => AsyncWrite::poll_flush(Pin::new(inner), cx), } } @@ -184,74 +209,32 @@ impl AsyncWrite for AsyncStream { Self::Null => Poll::Ready(Ok(())), Self::Tcp(ref mut inner) => Pin::new(inner).poll_shutdown(cx), Self::Tls(ref mut inner) => Pin::new(inner).poll_shutdown(cx), + #[cfg(unix)] + Self::Unix(ref mut inner) => Pin::new(inner).poll_shutdown(cx), } } -} -impl AsyncRead for AsyncTcpStream { - fn poll_read( - mut self: Pin<&mut Self>, + fn poll_write_vectored( + self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut ReadBuf, - ) -> Poll> { - match self.deref_mut() { - #[cfg(feature = "tokio-runtime")] - Self::Tokio(ref mut inner) => Pin::new(inner).poll_read(cx, buf), - - #[cfg(feature = "async-std-runtime")] - Self::AsyncStd(ref mut inner) => { - use tokio_util::compat::FuturesAsyncReadCompatExt; - - Pin::new(&mut inner.compat()).poll_read(cx, buf) - } - } - } -} - -impl AsyncWrite for AsyncTcpStream { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match self.deref_mut() { - #[cfg(feature = "tokio-runtime")] - Self::Tokio(ref mut inner) => Pin::new(inner).poll_write(cx, buf), - - #[cfg(feature = "async-std-runtime")] - Self::AsyncStd(ref mut inner) => { - use tokio_util::compat::FuturesAsyncReadCompatExt; - - Pin::new(&mut inner.compat()).poll_write(cx, buf) - } - } - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - match self.deref_mut() { - #[cfg(feature = "tokio-runtime")] - Self::Tokio(ref mut inner) => Pin::new(inner).poll_flush(cx), - - #[cfg(feature = "async-std-runtime")] - Self::AsyncStd(ref mut inner) => { - use tokio_util::compat::FuturesAsyncReadCompatExt; - - Pin::new(&mut inner.compat()).poll_flush(cx) - } + bufs: &[futures_io::IoSlice<'_>], + ) -> Poll> { + match self.get_mut() { + Self::Null => Poll::Ready(Ok(0)), + Self::Tcp(ref mut inner) => Pin::new(inner).poll_write_vectored(cx, bufs), + Self::Tls(ref mut inner) => Pin::new(inner).poll_write_vectored(cx, bufs), + #[cfg(unix)] + Self::Unix(ref mut inner) => Pin::new(inner).poll_write_vectored(cx, bufs), } } - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - match self.deref_mut() { - #[cfg(feature = "tokio-runtime")] - Self::Tokio(ref mut inner) => Pin::new(inner).poll_shutdown(cx), - - #[cfg(feature = "async-std-runtime")] - Self::AsyncStd(ref mut inner) => { - use tokio_util::compat::FuturesAsyncReadCompatExt; - - Pin::new(&mut inner.compat()).poll_shutdown(cx) - } + fn is_write_vectored(&self) -> bool { + match self { + Self::Null => false, + Self::Tcp(ref inner) => inner.is_write_vectored(), + Self::Tls(ref inner) => inner.is_write_vectored(), + #[cfg(unix)] + Self::Unix(ref inner) => inner.is_write_vectored(), } } } diff --git a/src/runtime/tls_openssl.rs b/src/runtime/tls_openssl.rs index 761a047bb..6fc35ae63 100644 --- a/src/runtime/tls_openssl.rs +++ b/src/runtime/tls_openssl.rs @@ -1,14 +1,10 @@ -use std::{ - pin::Pin, - sync::Once, - task::{Context, Poll}, -}; +use std::pin::Pin; use openssl::{ error::ErrorStack, ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode}, }; -use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpStream; use tokio_openssl::SslStream; use crate::{ @@ -16,12 +12,7 @@ use crate::{ error::{Error, ErrorKind, Result}, }; -use super::stream::AsyncTcpStream; - -#[derive(Debug)] -pub(crate) struct AsyncTlsStream { - inner: SslStream, -} +pub(super) type TlsStream = SslStream; /// Configuration required to use TLS. Creating this is expensive, so its best to cache this value /// and reuse it for multiple connections. @@ -40,11 +31,7 @@ impl TlsConfig { None => true, }; - let connector = make_openssl_connector(options).map_err(|e| { - Error::from(ErrorKind::InvalidTlsConfig { - message: e.to_string(), - }) - })?; + let connector = make_openssl_connector(options)?; Ok(TlsConfig { connector, @@ -53,92 +40,91 @@ impl TlsConfig { } } -impl AsyncTlsStream { - pub(crate) async fn connect( - host: &str, - tcp_stream: AsyncTcpStream, - cfg: &TlsConfig, - ) -> Result { - init_trust(); - - let mut stream = make_ssl_stream(host, tcp_stream, cfg).map_err(|err| { - Error::from(ErrorKind::InvalidTlsConfig { - message: err.to_string(), - }) - })?; - Pin::new(&mut stream).connect().await.map_err(|err| { - use std::io; - match err.into_io_error() { - Ok(err) => err, - Err(err) => io::Error::new(io::ErrorKind::Other, err), - } - })?; - Ok(AsyncTlsStream { inner: stream }) - } -} - -impl AsyncRead for AsyncTlsStream { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.inner).poll_read(cx, buf) - } +pub(super) async fn tls_connect( + host: &str, + tcp_stream: TcpStream, + cfg: &TlsConfig, +) -> Result { + let mut stream = make_ssl_stream(host, tcp_stream, cfg).map_err(|err| { + Error::from(ErrorKind::InvalidTlsConfig { + message: err.to_string(), + }) + })?; + Pin::new(&mut stream).connect().await.map_err(|err| { + use std::io; + match err.into_io_error() { + Ok(err) => err, + Err(err) => io::Error::new(io::ErrorKind::Other, err), + } + })?; + Ok(stream) } -impl AsyncWrite for AsyncTlsStream { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.inner).poll_write(cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_flush(cx) - } +fn make_openssl_connector(cfg: TlsOptions) -> Result { + let openssl_err = |e: ErrorStack| { + Error::from(ErrorKind::InvalidTlsConfig { + message: e.to_string(), + }) + }; - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_shutdown(cx) - } -} + let mut builder = SslConnector::builder(SslMethod::tls_client()).map_err(openssl_err)?; -fn make_openssl_connector(cfg: TlsOptions) -> std::result::Result { - let mut builder = SslConnector::builder(SslMethod::tls_client())?; + let probe = openssl_probe::probe(); + builder + .load_verify_locations(probe.cert_file.as_deref(), probe.cert_dir.as_deref()) + .map_err(openssl_err)?; let TlsOptions { allow_invalid_certificates, ca_file_path, cert_key_file_path, allow_invalid_hostnames: _, + #[cfg(feature = "cert-key-password")] + tls_certificate_key_file_password, } = cfg; if let Some(true) = allow_invalid_certificates { builder.set_verify(SslVerifyMode::NONE); } if let Some(path) = ca_file_path { - builder.set_ca_file(path)?; + builder.set_ca_file(path).map_err(openssl_err)?; } if let Some(path) = cert_key_file_path { - builder.set_certificate_file(path.clone(), SslFiletype::PEM)?; - builder.set_private_key_file(path, SslFiletype::PEM)?; + builder + .set_certificate_file(path.clone(), SslFiletype::PEM) + .map_err(openssl_err)?; + // Inner fn so the cert-key-password path can early return + let handle_private_key = || -> Result<()> { + #[cfg(feature = "cert-key-password")] + if let Some(key_pw) = tls_certificate_key_file_password { + let contents = std::fs::read(&path)?; + + /* + let key_bytes = super::pem::decrypt_private_key(&contents, &key_pw)?; + let key = + openssl::pkey::PKey::private_key_from_der(&key_bytes).map_err(openssl_err)?; + */ + + let key = openssl::pkey::PKey::private_key_from_pem_passphrase(&contents, &key_pw) + .map_err(openssl_err)?; + builder.set_private_key(&key).map_err(openssl_err)?; + return Ok(()); + } + builder + .set_private_key_file(path, SslFiletype::PEM) + .map_err(openssl_err) + }; + handle_private_key()?; } Ok(builder.build()) } -fn init_trust() { - static ONCE: Once = Once::new(); - ONCE.call_once(openssl_probe::init_ssl_cert_env_vars); -} - fn make_ssl_stream( host: &str, - tcp_stream: AsyncTcpStream, + tcp_stream: TcpStream, cfg: &TlsConfig, -) -> std::result::Result, ErrorStack> { +) -> std::result::Result, ErrorStack> { let ssl = cfg .connector .configure()? diff --git a/src/runtime/tls_rustls.rs b/src/runtime/tls_rustls.rs index 5d730269d..519c0bc80 100644 --- a/src/runtime/tls_rustls.rs +++ b/src/runtime/tls_rustls.rs @@ -2,21 +2,17 @@ use std::{ convert::TryFrom, fs::File, io::{BufReader, Seek}, - pin::Pin, sync::Arc, - task::{Context, Poll}, - time::SystemTime, }; use rustls::{ - client::{ClientConfig, ServerCertVerified, ServerCertVerifier, ServerName}, - Certificate, + client::ClientConfig, + crypto::ring as provider, + pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer, ServerName}, Error as TlsError, - OwnedTrustAnchor, RootCertStore, }; -use rustls_pemfile::{certs, read_one, Item}; -use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpStream; use tokio_rustls::TlsConnector; use webpki_roots::TLS_SERVER_ROOTS; @@ -25,12 +21,7 @@ use crate::{ error::{ErrorKind, Result}, }; -use super::stream::AsyncTcpStream; - -#[derive(Debug)] -pub(crate) struct AsyncTlsStream { - inner: tokio_rustls::client::TlsStream, -} +pub(super) type TlsStream = tokio_rustls::client::TlsStream; /// Configuration required to use TLS. Creating this is expensive, so its best to cache this value /// and reuse it for multiple connections. @@ -51,112 +42,87 @@ impl TlsConfig { } } -impl AsyncTlsStream { - pub(crate) async fn connect( - host: &str, - tcp_stream: AsyncTcpStream, - cfg: &TlsConfig, - ) -> Result { - let name = ServerName::try_from(host).map_err(|e| ErrorKind::DnsResolve { +pub(super) async fn tls_connect( + host: &str, + tcp_stream: TcpStream, + cfg: &TlsConfig, +) -> Result { + let name = ServerName::try_from(host) + .map_err(|e| ErrorKind::DnsResolve { message: format!("could not resolve {:?}: {}", host, e), - })?; - - let conn = cfg - .connector - .connect_with(name, tcp_stream, |c| { - c.set_buffer_limit(None); - }) - .await?; - Ok(Self { inner: conn }) - } -} - -impl AsyncRead for AsyncTlsStream { - fn poll_read( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut tokio::io::ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.inner).poll_read(cx, buf) - } -} - -impl AsyncWrite for AsyncTlsStream { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.inner).poll_write(cx, buf) - } - - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_flush(cx) - } - - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_shutdown(cx) - } + })? + .to_owned(); + + let conn = cfg + .connector + .connect_with(name, tcp_stream, |c| { + c.set_buffer_limit(None); + }) + .await?; + Ok(conn) } /// Converts `TlsOptions` into a rustls::ClientConfig. fn make_rustls_config(cfg: TlsOptions) -> Result { let mut store = RootCertStore::empty(); if let Some(path) = cfg.ca_file_path { - let ders = certs(&mut BufReader::new(File::open(&path)?)).map_err(|_| { - ErrorKind::InvalidTlsConfig { + let ders = CertificateDer::pem_file_iter(&path) + .map_err(|err| ErrorKind::InvalidTlsConfig { message: format!( - "Unable to parse PEM-encoded root certificate from {}", + "Unable to parse PEM-encoded root certificate from {}: {err}", path.display() ), - } - })?; - store.add_parsable_certificates(&ders); + })? + .flatten(); + store.add_parsable_certificates(ders); } else { - let trust_anchors = TLS_SERVER_ROOTS.0.iter().map(|ta| { - OwnedTrustAnchor::from_subject_spki_name_constraints( - ta.subject, - ta.spki, - ta.name_constraints, - ) - }); - store.add_server_trust_anchors(trust_anchors); + store.extend(TLS_SERVER_ROOTS.iter().cloned()); } + let config_builder = ClientConfig::builder_with_provider(provider::default_provider().into()) + .with_safe_default_protocol_versions() + .map_err(|e| ErrorKind::InvalidTlsConfig { + message: format!( + "built-in provider should support default protocol versions: {}", + e + ), + })? + .with_root_certificates(store); + let mut config = if let Some(path) = cfg.cert_key_file_path { let mut file = BufReader::new(File::open(&path)?); - let certs = match certs(&mut file) { - Ok(certs) => certs.into_iter().map(Certificate).collect(), - Err(error) => { - return Err(ErrorKind::InvalidTlsConfig { - message: format!( - "Unable to parse PEM-encoded client certificate from {}: {}", - path.display(), - error, - ), - } - .into()) - } - }; + let mut certs = vec![]; + + for cert in CertificateDer::pem_reader_iter(&mut file) { + let cert = cert.map_err(|error| ErrorKind::InvalidTlsConfig { + message: format!( + "Unable to parse PEM-encoded client certificate from {}: {error}", + path.display(), + ), + })?; + certs.push(cert); + } file.rewind()?; - let key = loop { - match read_one(&mut file) { - Ok(Some(Item::PKCS8Key(bytes))) | Ok(Some(Item::RSAKey(bytes))) => { - break rustls::PrivateKey(bytes) - } - Ok(Some(_)) => continue, - Ok(None) => { - return Err(ErrorKind::InvalidTlsConfig { - message: format!("No PEM-encoded keys in {}", path.display()), - } - .into()) - } - Err(_) => { + let key = 'key: { + #[cfg(feature = "cert-key-password")] + if let Some(key_pw) = cfg.tls_certificate_key_file_password.as_deref() { + use rustls::pki_types::PrivatePkcs8KeyDer; + use std::io::Read; + let mut contents = vec![]; + file.read_to_end(&mut contents)?; + break 'key PrivatePkcs8KeyDer::from(super::pem::decrypt_private_key( + &contents, key_pw, + )?) + .into(); + } + match PrivateKeyDer::from_pem_reader(&mut file) { + Ok(key) => break 'key key, + Err(err) => { return Err(ErrorKind::InvalidTlsConfig { message: format!( - "Unable to parse PEM-encoded item from {}", - path.display() + "Unable to parse PEM-encoded item from {}: {err}", + path.display(), ), } .into()) @@ -164,41 +130,82 @@ fn make_rustls_config(cfg: TlsOptions) -> Result { } }; - ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(store) - .with_single_cert(certs, key) + config_builder + .with_client_auth_cert(certs, key) .map_err(|error| ErrorKind::InvalidTlsConfig { message: error.to_string(), })? } else { - ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(store) - .with_no_client_auth() + config_builder.with_no_client_auth() }; if let Some(true) = cfg.allow_invalid_certificates { - config + // nosemgrep: rustls-dangerous + config // mongodb rating: No Fix Needed .dangerous() - .set_certificate_verifier(Arc::new(NoCertVerifier {})); + .set_certificate_verifier(Arc::new(danger::NoCertVerifier( + provider::default_provider(), + ))); } Ok(config) } -struct NoCertVerifier {} - -impl ServerCertVerifier for NoCertVerifier { - fn verify_server_cert( - &self, - _: &Certificate, - _: &[Certificate], - _: &ServerName, - _: &mut dyn Iterator, - _: &[u8], - _: SystemTime, - ) -> std::result::Result { - Ok(ServerCertVerified::assertion()) +mod danger { + use super::*; + use rustls::{ + client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}, + crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}, + pki_types::UnixTime, + DigitallySignedStruct, + SignatureScheme, + }; + + #[derive(Debug)] + pub(super) struct NoCertVerifier(pub(super) CryptoProvider); + + impl ServerCertVerifier for NoCertVerifier { + fn verify_server_cert( + &self, + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], + _now: UnixTime, + ) -> std::result::Result { + Ok(ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> std::result::Result { + verify_tls12_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &DigitallySignedStruct, + ) -> std::result::Result { + verify_tls13_signature( + message, + cert, + dss, + &self.0.signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + self.0.signature_verification_algorithms.supported_schemes() + } } } diff --git a/src/sdam.rs b/src/sdam.rs new file mode 100644 index 000000000..75fac2f08 --- /dev/null +++ b/src/sdam.rs @@ -0,0 +1,29 @@ +mod description; +mod monitor; +pub mod public; +mod server; +mod srv_polling; +#[cfg(test)] +mod test; +mod topology; + +pub use self::public::{ServerInfo, ServerType, TopologyType}; + +pub(crate) use self::{ + description::{ + server::{ServerDescription, TopologyVersion}, + topology::{ + choose_n, + server_selection::{self, SelectedServer}, + verify_max_staleness, + TopologyDescription, + TransactionSupportStatus, + }, + }, + monitor::{Monitor, DEFAULT_HEARTBEAT_FREQUENCY, MIN_HEARTBEAT_FREQUENCY}, + server::Server, + topology::{BroadcastMessage, HandshakePhase, Topology, TopologyUpdater, TopologyWatcher}, +}; + +#[cfg(test)] +pub(crate) use topology::UpdateMessage; diff --git a/src/sdam/description/mod.rs b/src/sdam/description.rs similarity index 100% rename from src/sdam/description/mod.rs rename to src/sdam/description.rs diff --git a/src/sdam/description/server.rs b/src/sdam/description/server.rs index 014e775db..cc9fa00b8 100644 --- a/src/sdam/description/server.rs +++ b/src/sdam/description/server.rs @@ -1,24 +1,24 @@ use std::time::Duration; -use bson::{bson, Bson}; +use crate::bson::{bson, rawdoc, Bson, RawBson}; use serde::{Deserialize, Serialize}; use crate::{ bson::{oid::ObjectId, DateTime}, - bson_util, client::ClusterTime, error::{Error, ErrorKind, Result}, hello::{HelloCommandResponse, HelloReply}, options::ServerAddress, selection_criteria::TagSet, + serde_util, }; -const DRIVER_MIN_DB_VERSION: &str = "3.6"; -const DRIVER_MIN_WIRE_VERSION: i32 = 6; -const DRIVER_MAX_WIRE_VERSION: i32 = 21; +const DRIVER_MIN_DB_VERSION: &str = "4.0"; +const DRIVER_MIN_WIRE_VERSION: i32 = 7; +const DRIVER_MAX_WIRE_VERSION: i32 = 25; /// Enum representing the possible types of servers that the driver can connect to. -#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq, Serialize)] +#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq, Serialize, Default)] #[non_exhaustive] pub enum ServerType { /// A single, non-replica set mongod. @@ -53,6 +53,7 @@ pub enum ServerType { /// A server that the driver hasn't yet communicated with or can't connect to. #[serde(alias = "PossiblePrimary")] + #[default] Unknown, } @@ -77,12 +78,6 @@ impl ServerType { } } -impl Default for ServerType { - fn default() -> Self { - ServerType::Unknown - } -} - /// Struct modeling the `topologyVersion` field included in the server's hello and legacy hello /// responses. #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq)] @@ -107,6 +102,20 @@ impl From for Bson { } } +impl From for RawBson { + fn from(tv: TopologyVersion) -> Self { + RawBson::Document(rawdoc! { + "processId": tv.process_id, + "counter": tv.counter + }) + } +} + +#[cfg(feature = "bson-3")] +impl crate::bson::raw::BindRawBsonRef for TopologyVersion { + type Target = crate::bson::raw::BindValue; +} + /// A description of the most up-to-date information known about a server. #[derive(Debug, Clone, Serialize)] pub(crate) struct ServerDescription { @@ -133,12 +142,12 @@ pub(crate) struct ServerDescription { // allows us to ensure that only valid states are possible (e.g. preventing that both an error // and a reply are present) while still making it easy to define helper methods on // ServerDescription for information we need from the hello reply by propagating with `?`. - #[serde(serialize_with = "bson_util::serialize_result_error_as_string")] + #[serde(serialize_with = "serde_util::serialize_result_error_as_string")] pub(crate) reply: Result>, } // Server description equality has a specific notion of what fields in a hello command response -// should be compared (https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#serverdescription). +// should be compared (https://specifications.readthedocs.io/en/latest/server-discovery-and-monitoring/server-discovery-and-monitoring/#server-description-equality). fn hello_command_eq(a: &HelloCommandResponse, b: &HelloCommandResponse) -> bool { a.server_type() == b.server_type() && a.min_wire_version == b.min_wire_version @@ -188,11 +197,15 @@ impl PartialEq for ServerDescription { } impl ServerDescription { - pub(crate) fn new(address: ServerAddress) -> Self { + pub(crate) fn new(address: &ServerAddress) -> Self { Self { - address: ServerAddress::Tcp { - host: address.host().to_lowercase(), - port: address.port(), + address: match address { + ServerAddress::Tcp { host, port } => ServerAddress::Tcp { + host: host.to_lowercase(), + port: *port, + }, + #[cfg(unix)] + ServerAddress::Unix { path } => ServerAddress::Unix { path: path.clone() }, }, server_type: Default::default(), last_update_time: None, @@ -206,7 +219,7 @@ impl ServerDescription { mut reply: HelloReply, average_rtt: Duration, ) -> Self { - let mut description = Self::new(address); + let mut description = Self::new(&address); description.average_round_trip_time = Some(average_rtt); description.last_update_time = Some(DateTime::now()); @@ -251,7 +264,7 @@ impl ServerDescription { } pub(crate) fn new_from_error(address: ServerAddress, error: Error) -> Self { - let mut description = Self::new(address); + let mut description = Self::new(&address); description.last_update_time = Some(DateTime::now()); description.average_round_trip_time = None; description.reply = Err(error); @@ -302,7 +315,7 @@ impl ServerDescription { Ok(set_name) } - pub(crate) fn known_hosts(&self) -> Result> { + pub(crate) fn known_hosts(&self) -> Result> { let known_hosts = self .reply .as_ref() @@ -320,13 +333,17 @@ impl ServerDescription { .chain(arbiters.into_iter().flatten()) }); - Ok(known_hosts.into_iter().flatten()) + known_hosts + .into_iter() + .flatten() + .map(ServerAddress::parse) + .collect() } pub(crate) fn invalid_me(&self) -> Result { if let Some(ref reply) = self.reply.as_ref().map_err(Clone::clone)? { if let Some(ref me) = reply.command_response.me { - return Ok(&self.address.to_string() != me); + return Ok(self.address != ServerAddress::parse(me)?); } } diff --git a/src/sdam/description/topology.rs b/src/sdam/description/topology.rs new file mode 100644 index 000000000..2131893b9 --- /dev/null +++ b/src/sdam/description/topology.rs @@ -0,0 +1,805 @@ +pub(crate) mod server_selection; +#[cfg(test)] +pub(crate) mod test; + +use std::{ + collections::{HashMap, HashSet}, + time::Duration, +}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + bson::oid::ObjectId, + client::ClusterTime, + cmap::Command, + error::{Error, Result}, + options::{ClientOptions, ServerAddress}, + sdam::{ + description::server::{ServerDescription, ServerType}, + DEFAULT_HEARTBEAT_FREQUENCY, + }, + selection_criteria::{ReadPreference, SelectionCriteria}, +}; + +use self::server_selection::IDLE_WRITE_PERIOD; + +/// The possible types for a topology. +#[derive( + Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, Default, derive_more::Display, +)] +#[non_exhaustive] +pub enum TopologyType { + /// A single mongod server. + Single, + + /// A replica set with no primary. + ReplicaSetNoPrimary, + + /// A replica set with a primary. + ReplicaSetWithPrimary, + + /// A sharded topology. + Sharded, + + /// A load balanced topology. + LoadBalanced, + + /// A topology whose type is not known. + #[default] + Unknown, +} + +#[cfg(test)] +impl TopologyType { + fn as_str(&self) -> &'static str { + match self { + Self::Single => "Single", + Self::ReplicaSetNoPrimary => "ReplicaSetNoPrimary", + Self::ReplicaSetWithPrimary => "ReplicaSetWithPrimary", + Self::Sharded => "Sharded", + Self::LoadBalanced => "LoadBalanced", + Self::Unknown => "Unknown", + } + } +} + +/// A description of the most up-to-date information known about a topology. +#[derive(Debug, Clone, Serialize)] +#[non_exhaustive] +pub(crate) struct TopologyDescription { + /// Whether or not the topology was initialized with a single seed. + #[serde(skip)] + pub(crate) single_seed: bool, + + /// The current type of the topology. + pub(crate) topology_type: TopologyType, + + /// The replica set name of the topology. + pub(crate) set_name: Option, + + /// The highest replica set version the driver has seen by a member of the topology. + pub(crate) max_set_version: Option, + + /// The highest replica set election id the driver has seen by a member of the topology. + pub(crate) max_election_id: Option, + + /// Describes the compatibility issue between the driver and server with regards to the + /// respective supported wire versions. + pub(crate) compatibility_error: Option, + + /// The time that a session remains active after its most recent use. + pub(crate) logical_session_timeout: Option, + + /// Whether or not this topology supports transactions. + #[serde(skip)] + pub(crate) transaction_support_status: TransactionSupportStatus, + + /// The highest reported cluster time by any server in this topology. + #[serde(skip)] + pub(crate) cluster_time: Option, + + /// The amount of latency beyond that of the suitable server with the minimum latency that is + /// acceptable for a read operation. + #[serde(skip)] + pub(crate) local_threshold: Option, + + /// The maximum amount of time to wait before checking a given server by sending server check. + #[serde(skip)] + pub(crate) heartbeat_freq: Option, + + /// The server descriptions of each member of the topology. + pub(crate) servers: HashMap, + + /// The maximum number of hosts. + pub(crate) srv_max_hosts: Option, +} + +impl PartialEq for TopologyDescription { + fn eq(&self, other: &Self) -> bool { + // Since we only use TopologyDescription equality to determine whether to wake up server + // selection operations to try to select again, the only fields we care about are the ones + // checked by the server selection algorithm. + self.compatibility_error == other.compatibility_error + && self.servers == other.servers + && self.topology_type == other.topology_type + } +} + +impl Default for TopologyDescription { + fn default() -> Self { + Self { + single_seed: false, + topology_type: TopologyType::Unknown, + set_name: Default::default(), + max_set_version: Default::default(), + max_election_id: Default::default(), + compatibility_error: Default::default(), + logical_session_timeout: None, + transaction_support_status: TransactionSupportStatus::Undetermined, + cluster_time: Default::default(), + local_threshold: Default::default(), + heartbeat_freq: Default::default(), + servers: Default::default(), + srv_max_hosts: Default::default(), + } + } +} + +impl TopologyDescription { + pub(crate) fn initialize(&mut self, options: &ClientOptions) { + debug_assert!( + self.servers.is_empty() && self.topology_type == TopologyType::Unknown, + "new TopologyDescriptions should start empty" + ); + + self.topology_type = if let Some(true) = options.direct_connection { + TopologyType::Single + } else if options.repl_set_name.is_some() { + TopologyType::ReplicaSetNoPrimary + } else if options.load_balanced.unwrap_or(false) { + TopologyType::LoadBalanced + } else { + TopologyType::Unknown + }; + + self.transaction_support_status = if self.topology_type == TopologyType::LoadBalanced { + TransactionSupportStatus::Supported + } else { + TransactionSupportStatus::Undetermined + }; + + for address in options.hosts.iter() { + let description = ServerDescription::new(address); + self.servers.insert(address.to_owned(), description); + } + + self.single_seed = self.servers.len() == 1; + self.set_name.clone_from(&options.repl_set_name); + self.local_threshold = options.local_threshold; + self.heartbeat_freq = options.heartbeat_freq; + self.srv_max_hosts = options.srv_max_hosts; + } + + /// Gets the topology type of the cluster. + pub(crate) fn topology_type(&self) -> TopologyType { + self.topology_type + } + + pub(crate) fn server_addresses(&self) -> impl Iterator { + self.servers.keys() + } + + pub(crate) fn cluster_time(&self) -> Option<&ClusterTime> { + self.cluster_time.as_ref() + } + + pub(crate) fn get_server_description( + &self, + address: &ServerAddress, + ) -> Option<&ServerDescription> { + self.servers.get(address) + } + + pub(crate) fn update_command_with_read_pref( + &self, + address: &ServerAddress, + command: &mut Command, + criteria: &SelectionCriteria, + ) { + let server_type = self + .get_server_description(address) + .map(|sd| sd.server_type) + .unwrap_or(ServerType::Unknown); + + match (self.topology_type, server_type) { + (TopologyType::Sharded, ServerType::Mongos) + | (TopologyType::Single, ServerType::Mongos) + | (TopologyType::LoadBalanced, _) => { + self.update_command_read_pref_for_mongos(command, criteria) + } + (TopologyType::Single, ServerType::Standalone) => {} + (TopologyType::Single, _) => { + let specified_read_pref = criteria.as_read_pref().cloned(); + + let resolved_read_pref = match specified_read_pref { + Some(ReadPreference::Primary) | None => ReadPreference::PrimaryPreferred { + options: Default::default(), + }, + Some(other) => other, + }; + if resolved_read_pref != ReadPreference::Primary { + command.set_read_preference(resolved_read_pref) + } + } + _ => { + let read_pref = match criteria { + SelectionCriteria::ReadPreference(rp) => rp.clone(), + SelectionCriteria::Predicate(_) => ReadPreference::PrimaryPreferred { + options: Default::default(), + }, + }; + if read_pref != ReadPreference::Primary { + command.set_read_preference(read_pref) + } + } + } + } + + fn update_command_read_pref_for_mongos( + &self, + command: &mut Command, + criteria: &SelectionCriteria, + ) { + let read_preference = match criteria { + SelectionCriteria::ReadPreference(rp) => rp, + _ => return, + }; + match read_preference { + ReadPreference::Secondary { .. } + | ReadPreference::PrimaryPreferred { .. } + | ReadPreference::Nearest { .. } + | ReadPreference::SecondaryPreferred { .. } => { + command.set_read_preference(read_preference.clone()) + } + _ => {} + } + } + + /// Gets the heartbeat frequency. + fn heartbeat_frequency(&self) -> Duration { + self.heartbeat_freq.unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY) + } + + /// Check the cluster for a compatibility error, and record the error message if one is found. + fn check_compatibility(&mut self) { + self.compatibility_error = None; + + for server in self.servers.values() { + let error_message = server.compatibility_error_message(); + + if error_message.is_some() { + self.compatibility_error = error_message; + return; + } + } + } + + pub(crate) fn compatibility_error(&self) -> Option<&String> { + self.compatibility_error.as_ref() + } + + /// Updates the topology's logical session timeout value based on the server's value for it. + fn update_logical_session_timeout(&mut self, server_description: &ServerDescription) { + if !server_description.server_type.is_data_bearing() { + return; + } + match server_description.logical_session_timeout().ok().flatten() { + Some(new_timeout) => match self.logical_session_timeout { + Some(current_timeout) => { + self.logical_session_timeout = + Some(std::cmp::min(current_timeout, new_timeout)); + } + None => { + let min_timeout = self + .servers + .values() + .filter(|s| s.server_type.is_data_bearing()) + .map(|s| s.logical_session_timeout().ok().flatten()) + .min() + .flatten(); + self.logical_session_timeout = min_timeout; + } + }, + // If any data-bearing server does not have a value for logicalSessionTimeoutMinutes, + // the topology's value should be None. + None => self.logical_session_timeout = None, + } + } + + /// Updates the topology's transaction support status based on its session support status and + /// the server description's max wire version. + fn update_transaction_support_status(&mut self, server_description: &ServerDescription) { + if self.logical_session_timeout.is_none() { + self.transaction_support_status = TransactionSupportStatus::Unsupported; + } + if let Ok(Some(max_wire_version)) = server_description.max_wire_version() { + self.transaction_support_status = if max_wire_version < 7 + || (max_wire_version < 8 && self.topology_type == TopologyType::Sharded) + { + TransactionSupportStatus::Unsupported + } else { + TransactionSupportStatus::Supported + } + } + } + + /// Sets the topology's cluster time to the provided one if it is higher than the currently + /// recorded one. + pub(crate) fn advance_cluster_time(&mut self, cluster_time: &ClusterTime) { + if self.cluster_time.as_ref() >= Some(cluster_time) { + return; + } + self.cluster_time = Some(cluster_time.clone()); + } + + /// Returns the diff between this topology description and the provided one, or `None` if + /// they are equal. + /// + /// The returned `TopologyDescriptionDiff` refers to the changes reflected in the provided + /// description. For example, if the provided description has a server in it that this + /// description does not, it will be returned in the `added_addresses` field. + pub(crate) fn diff<'a>( + &'a self, + other: &'a TopologyDescription, + ) -> Option> { + if self == other { + return None; + } + + let addresses: HashSet<&ServerAddress> = self.server_addresses().collect(); + let other_addresses: HashSet<&ServerAddress> = other.server_addresses().collect(); + + let changed_servers = self + .servers + .iter() + .filter_map(|(address, description)| match other.servers.get(address) { + Some(other_description) if description != other_description => { + Some((address, (description, other_description))) + } + _ => None, + }); + + Some(TopologyDescriptionDiff { + removed_addresses: addresses.difference(&other_addresses).cloned().collect(), + added_addresses: other_addresses.difference(&addresses).cloned().collect(), + changed_servers: changed_servers.collect(), + }) + } + + /// Syncs the set of servers in the description to those in `hosts`. Servers in the set not + /// already present in the cluster will be added, and servers in the cluster not present in the + /// set will be removed. + pub(crate) fn sync_hosts(&mut self, hosts: HashSet) { + self.servers.retain(|host, _| hosts.contains(host)); + let mut new = vec![]; + for host in hosts { + if !self.servers.contains_key(&host) { + new.push((host.clone(), ServerDescription::new(&host))); + } + } + if let Some(max) = self.srv_max_hosts { + let max = max as usize; + if max > 0 && max < self.servers.len() + new.len() { + new = choose_n(&new, max.saturating_sub(self.servers.len())) + .cloned() + .collect(); + } + } + self.servers.extend(new); + } + + pub(crate) fn transaction_support_status(&self) -> TransactionSupportStatus { + self.transaction_support_status + } + + /// Update the topology based on the new information about the topology contained by the + /// ServerDescription. + pub(crate) fn update(&mut self, mut server_description: ServerDescription) -> Result<()> { + match self.servers.get(&server_description.address) { + None => return Ok(()), + Some(existing_sd) => { + // Ignore updates from outdated topology versions. + if let Some(existing_tv) = existing_sd.topology_version() { + if let Some(new_tv) = server_description.topology_version() { + if existing_tv.process_id == new_tv.process_id + && new_tv.counter < existing_tv.counter + { + return Ok(()); + } + } + } + } + } + + if let Some(expected_name) = &self.set_name { + if server_description.is_available() { + let got_name = server_description.set_name(); + if self.topology_type() == TopologyType::Single + && !matches!( + got_name.as_ref().map(|opt| opt.as_ref()), + Ok(Some(name)) if name == expected_name + ) + { + let got_display = match got_name { + Ok(Some(s)) => format!("{:?}", s), + Ok(None) => "".to_string(), + Err(s) => format!("", s), + }; + // Mark server as unknown. + server_description = ServerDescription::new_from_error( + server_description.address, + Error::invalid_argument(format!( + "Connection string replicaSet name {:?} does not match actual name {}", + expected_name, got_display, + )), + ); + } + } + } + + // Replace the old info about the server with the new info. + self.servers.insert( + server_description.address.clone(), + server_description.clone(), + ); + + if let TopologyType::LoadBalanced = self.topology_type { + // Load-balanced topologies don't have real server updates; attempting to update based + // on the synthesized one causes incorrect behavior. + return Ok(()); + } + + // Update the topology's min logicalSessionTimeout. + self.update_logical_session_timeout(&server_description); + + // Update the topology's transaction support status. + self.update_transaction_support_status(&server_description); + + // Update the topology's max reported $clusterTime. + if let Some(ref cluster_time) = server_description.cluster_time().ok().flatten() { + self.advance_cluster_time(cluster_time); + } + + // Update the topology description based on the current topology type. + match self.topology_type { + TopologyType::Single | TopologyType::LoadBalanced => {} + TopologyType::Unknown => self.update_unknown_topology(server_description)?, + TopologyType::Sharded => self.update_sharded_topology(server_description), + TopologyType::ReplicaSetNoPrimary => { + self.update_replica_set_no_primary_topology(server_description)? + } + TopologyType::ReplicaSetWithPrimary => { + self.update_replica_set_with_primary_topology(server_description)?; + } + } + + // Record any compatibility error. + self.check_compatibility(); + + Ok(()) + } + + /// Update the Unknown topology description based on the server description. + fn update_unknown_topology(&mut self, server_description: ServerDescription) -> Result<()> { + match server_description.server_type { + ServerType::Unknown | ServerType::RsGhost => {} + ServerType::Standalone => { + self.update_unknown_with_standalone_server(server_description) + } + ServerType::Mongos => self.topology_type = TopologyType::Sharded, + ServerType::RsPrimary => { + self.topology_type = TopologyType::ReplicaSetWithPrimary; + self.update_rs_from_primary_server(server_description)?; + } + ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { + self.topology_type = TopologyType::ReplicaSetNoPrimary; + self.update_rs_without_primary_server(server_description)?; + } + ServerType::LoadBalancer => { + return Err(Error::internal("cannot transition to a load balancer")) + } + } + + Ok(()) + } + + /// Update the Sharded topology description based on the server description. + fn update_sharded_topology(&mut self, server_description: ServerDescription) { + match server_description.server_type { + ServerType::Unknown | ServerType::Mongos => {} + _ => { + self.servers.remove(&server_description.address); + } + } + } + + /// Update the ReplicaSetNoPrimary topology description based on the server description. + fn update_replica_set_no_primary_topology( + &mut self, + server_description: ServerDescription, + ) -> Result<()> { + match server_description.server_type { + ServerType::Unknown | ServerType::RsGhost => {} + ServerType::Standalone | ServerType::Mongos => { + self.servers.remove(&server_description.address); + } + ServerType::RsPrimary => { + self.topology_type = TopologyType::ReplicaSetWithPrimary; + self.update_rs_from_primary_server(server_description)? + } + ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { + self.update_rs_without_primary_server(server_description)?; + } + ServerType::LoadBalancer => { + return Err(Error::internal("cannot transition to a load balancer")) + } + } + + Ok(()) + } + + /// Update the ReplicaSetWithPrimary topology description based on the server description. + fn update_replica_set_with_primary_topology( + &mut self, + server_description: ServerDescription, + ) -> Result<()> { + match server_description.server_type { + ServerType::Unknown | ServerType::RsGhost => { + self.record_primary_state(); + } + ServerType::Standalone | ServerType::Mongos => { + self.servers.remove(&server_description.address); + self.record_primary_state(); + } + ServerType::RsPrimary => self.update_rs_from_primary_server(server_description)?, + ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { + self.update_rs_with_primary_from_member(server_description)?; + } + ServerType::LoadBalancer => { + return Err(Error::internal("cannot transition to a load balancer")); + } + } + + Ok(()) + } + + /// Update the Unknown topology description based on the Standalone server description. + fn update_unknown_with_standalone_server(&mut self, server_description: ServerDescription) { + if self.single_seed { + self.topology_type = TopologyType::Single; + } else { + self.servers.remove(&server_description.address); + } + } + + /// Update the ReplicaSetNoPrimary topology description based on the non-primary server + /// description. + fn update_rs_without_primary_server( + &mut self, + server_description: ServerDescription, + ) -> Result<()> { + if self.set_name.is_none() { + self.set_name = server_description.set_name()?; + } else if self.set_name != server_description.set_name()? { + self.servers.remove(&server_description.address); + + return Ok(()); + } + + self.add_new_servers(server_description.known_hosts()?); + + if server_description.invalid_me()? { + self.servers.remove(&server_description.address); + } + + Ok(()) + } + + /// Update the ReplicaSetWithPrimary topology description based on the non-primary server + /// description. + fn update_rs_with_primary_from_member( + &mut self, + server_description: ServerDescription, + ) -> Result<()> { + if self.set_name != server_description.set_name()? { + self.servers.remove(&server_description.address); + self.record_primary_state(); + + return Ok(()); + } + + if server_description.invalid_me()? { + self.servers.remove(&server_description.address); + self.record_primary_state(); + + return Ok(()); + } + + Ok(()) + } + + /// Update the replica set topology description based on the RSPrimary server description. + fn update_rs_from_primary_server( + &mut self, + server_description: ServerDescription, + ) -> Result<()> { + if self.set_name.is_none() { + self.set_name = server_description.set_name()?; + } else if self.set_name != server_description.set_name()? { + self.servers.remove(&server_description.address); + self.record_primary_state(); + + return Ok(()); + } + + if let Some(server_set_version) = server_description.set_version()? { + if let Some(server_election_id) = server_description.election_id()? { + if let Some(topology_max_set_version) = self.max_set_version { + if let Some(ref topology_max_election_id) = self.max_election_id { + if topology_max_set_version > server_set_version + || (topology_max_set_version == server_set_version + && *topology_max_election_id > server_election_id) + { + // Stale primary. + self.servers.insert( + server_description.address.clone(), + ServerDescription::new_from_error( + server_description.address, + Error::invalid_response( + "primary marked stale due to electionId/setVersion \ + mismatch", + ), + ), + ); + self.record_primary_state(); + return Ok(()); + } + } + } + + self.max_election_id = Some(server_election_id); + } + } + + if let Some(server_set_version) = server_description.set_version()? { + if self + .max_set_version + .as_ref() + .map(|topology_max_set_version| server_set_version > *topology_max_set_version) + .unwrap_or(true) + { + self.max_set_version = Some(server_set_version); + } + } + + let addresses: Vec<_> = self.servers.keys().cloned().collect(); + + // If any other servers are RSPrimary, replace them with an unknown server decscription, + // which will cause them to be updated by a new server check. + for address in addresses.clone() { + if address == server_description.address { + continue; + } + + if let ServerType::RsPrimary = self.servers.get(&address).unwrap().server_type { + let description = ServerDescription::new_from_error( + address.clone(), + Error::invalid_response( + "primary marked stale due to discovery of newer primary", + ), + ); + self.servers.insert(address, description); + } + } + + let known_hosts = server_description.known_hosts()?; + self.add_new_servers(known_hosts.clone()); + + for address in addresses { + if !known_hosts.contains(&address) { + self.servers.remove(&address); + } + } + + self.record_primary_state(); + + Ok(()) + } + + /// Inspect the topology for a primary server, and update the topology type to + /// ReplicaSetNoPrimary if none is found. + /// + /// This should only be called on a replica set topology. + fn record_primary_state(&mut self) { + self.topology_type = if self + .servers + .values() + .any(|server| server.server_type == ServerType::RsPrimary) + { + TopologyType::ReplicaSetWithPrimary + } else { + TopologyType::ReplicaSetNoPrimary + }; + } + + /// Create a new ServerDescription for each address and add it to the topology. + fn add_new_servers(&mut self, addresses: impl IntoIterator) { + for address in addresses { + self.servers + .entry(address.clone()) + .or_insert_with(|| ServerDescription::new(&address)); + } + } +} + +pub(crate) fn choose_n(values: &[T], n: usize) -> impl Iterator { + use rand::{prelude::SliceRandom, SeedableRng}; + values.choose_multiple(&mut rand::rngs::SmallRng::from_entropy(), n) +} + +/// Enum representing whether transactions are supported by the topology. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum TransactionSupportStatus { + /// It is not known yet whether the topology supports transactions. This is possible if no + /// data-bearing servers have updated the `TopologyDescription` yet. + Undetermined, + + /// Transactions are not supported by this topology. + Unsupported, + + /// Transactions are supported by this topology. A topology supports transactions if it + /// supports sessions and its maxWireVersion >= 7. If the topology is sharded, maxWireVersion + /// must be >= 8 for transactions to be supported. + /// + /// Note that meeting these conditions does not guarantee that a deployment + /// supports transactions; any other missing qualification will be reported by the server. + Supported, +} + +impl Default for TransactionSupportStatus { + fn default() -> Self { + Self::Undetermined + } +} + +/// A struct representing the diff between two `TopologyDescription`s. +/// Returned from `TopologyDescription::diff`. +#[derive(Debug)] +pub(crate) struct TopologyDescriptionDiff<'a> { + pub(crate) removed_addresses: HashSet<&'a ServerAddress>, + pub(crate) added_addresses: HashSet<&'a ServerAddress>, + pub(crate) changed_servers: + HashMap<&'a ServerAddress, (&'a ServerDescription, &'a ServerDescription)>, +} + +pub(crate) fn verify_max_staleness( + max_staleness: Duration, + heartbeat_frequency: Duration, +) -> crate::error::Result<()> { + let smallest_max_staleness = std::cmp::max( + Duration::from_secs(90), + heartbeat_frequency + .checked_add(IDLE_WRITE_PERIOD) + .unwrap_or(Duration::MAX), + ); + + if max_staleness < smallest_max_staleness { + return Err(Error::invalid_argument(format!( + "invalid max_staleness value: must be at least {} seconds", + smallest_max_staleness.as_secs() + ))); + } + + Ok(()) +} diff --git a/src/sdam/description/topology/mod.rs b/src/sdam/description/topology/mod.rs deleted file mode 100644 index cefbf1823..000000000 --- a/src/sdam/description/topology/mod.rs +++ /dev/null @@ -1,784 +0,0 @@ -pub(crate) mod server_selection; -#[cfg(test)] -pub(crate) mod test; - -use std::{ - collections::{HashMap, HashSet}, - time::Duration, -}; - -use serde::{Deserialize, Serialize}; - -use crate::{ - bson::oid::ObjectId, - client::ClusterTime, - cmap::Command, - error::{Error, Result}, - options::{ClientOptions, ServerAddress}, - sdam::{ - description::server::{ServerDescription, ServerType}, - DEFAULT_HEARTBEAT_FREQUENCY, - }, - selection_criteria::{ReadPreference, SelectionCriteria}, -}; - -use self::server_selection::IDLE_WRITE_PERIOD; - -/// The possible types for a topology. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, derive_more::Display)] -#[non_exhaustive] -pub enum TopologyType { - /// A single mongod server. - Single, - - /// A replica set with no primary. - ReplicaSetNoPrimary, - - /// A replica set with a primary. - ReplicaSetWithPrimary, - - /// A sharded topology. - Sharded, - - /// A load balanced topology. - LoadBalanced, - - /// A topology whose type is not known. - Unknown, -} - -#[cfg(test)] -impl TopologyType { - fn as_str(&self) -> &'static str { - match self { - Self::Single => "Single", - Self::ReplicaSetNoPrimary => "ReplicaSetNoPrimary", - Self::ReplicaSetWithPrimary => "ReplicaSetWithPrimary", - Self::Sharded => "Sharded", - Self::LoadBalanced => "LoadBalanced", - Self::Unknown => "Unknown", - } - } -} - -impl Default for TopologyType { - fn default() -> Self { - TopologyType::Unknown - } -} - -/// A description of the most up-to-date information known about a topology. -#[derive(Debug, Clone, Serialize)] -#[non_exhaustive] -pub(crate) struct TopologyDescription { - /// Whether or not the topology was initialized with a single seed. - #[serde(skip)] - pub(crate) single_seed: bool, - - /// The current type of the topology. - pub(crate) topology_type: TopologyType, - - /// The replica set name of the topology. - pub(crate) set_name: Option, - - /// The highest replica set version the driver has seen by a member of the topology. - pub(crate) max_set_version: Option, - - /// The highest replica set election id the driver has seen by a member of the topology. - pub(crate) max_election_id: Option, - - /// Describes the compatibility issue between the driver and server with regards to the - /// respective supported wire versions. - pub(crate) compatibility_error: Option, - - /// The time that a session remains active after its most recent use. - pub(crate) logical_session_timeout: Option, - - /// Whether or not this topology supports transactions. - #[serde(skip)] - pub(crate) transaction_support_status: TransactionSupportStatus, - - /// The highest reported cluster time by any server in this topology. - #[serde(skip)] - pub(crate) cluster_time: Option, - - /// The amount of latency beyond that of the suitable server with the minimum latency that is - /// acceptable for a read operation. - #[serde(skip)] - pub(crate) local_threshold: Option, - - /// The maximum amount of time to wait before checking a given server by sending server check. - #[serde(skip)] - pub(crate) heartbeat_freq: Option, - - /// The server descriptions of each member of the topology. - pub(crate) servers: HashMap, -} - -impl PartialEq for TopologyDescription { - fn eq(&self, other: &Self) -> bool { - // Since we only use TopologyDescription equality to determine whether to wake up server - // selection operations to try to select again, the only fields we care about are the ones - // checked by the server selection algorithm. - self.compatibility_error == other.compatibility_error - && self.servers == other.servers - && self.topology_type == other.topology_type - } -} - -impl Default for TopologyDescription { - fn default() -> Self { - Self { - single_seed: false, - topology_type: TopologyType::Unknown, - set_name: Default::default(), - max_set_version: Default::default(), - max_election_id: Default::default(), - compatibility_error: Default::default(), - logical_session_timeout: None, - transaction_support_status: TransactionSupportStatus::Undetermined, - cluster_time: Default::default(), - local_threshold: Default::default(), - heartbeat_freq: Default::default(), - servers: Default::default(), - } - } -} - -impl TopologyDescription { - pub(crate) fn initialize(&mut self, options: &ClientOptions) { - debug_assert!( - self.servers.is_empty() && self.topology_type == TopologyType::Unknown, - "new TopologyDescriptions should start empty" - ); - - self.topology_type = if let Some(true) = options.direct_connection { - TopologyType::Single - } else if options.repl_set_name.is_some() { - TopologyType::ReplicaSetNoPrimary - } else if options.load_balanced.unwrap_or(false) { - TopologyType::LoadBalanced - } else { - TopologyType::Unknown - }; - - self.transaction_support_status = if self.topology_type == TopologyType::LoadBalanced { - TransactionSupportStatus::Supported - } else { - TransactionSupportStatus::Undetermined - }; - - for address in options.hosts.iter() { - let description = ServerDescription::new(address.clone()); - self.servers.insert(address.to_owned(), description); - } - - self.single_seed = self.servers.len() == 1; - self.set_name = options.repl_set_name.clone(); - self.local_threshold = options.local_threshold; - self.heartbeat_freq = options.heartbeat_freq; - } - - /// Gets the topology type of the cluster. - pub(crate) fn topology_type(&self) -> TopologyType { - self.topology_type - } - - pub(crate) fn server_addresses(&self) -> impl Iterator { - self.servers.keys() - } - - pub(crate) fn cluster_time(&self) -> Option<&ClusterTime> { - self.cluster_time.as_ref() - } - - pub(crate) fn get_server_description( - &self, - address: &ServerAddress, - ) -> Option<&ServerDescription> { - self.servers.get(address) - } - - pub(crate) fn update_command_with_read_pref( - &self, - address: &ServerAddress, - command: &mut Command, - criteria: Option<&SelectionCriteria>, - ) { - let server_type = self - .get_server_description(address) - .map(|sd| sd.server_type) - .unwrap_or(ServerType::Unknown); - - match (self.topology_type, server_type) { - (TopologyType::Sharded, ServerType::Mongos) - | (TopologyType::Single, ServerType::Mongos) - | (TopologyType::LoadBalanced, _) => { - self.update_command_read_pref_for_mongos(command, criteria) - } - (TopologyType::Single, ServerType::Standalone) => {} - (TopologyType::Single, _) => { - let specified_read_pref = criteria - .and_then(SelectionCriteria::as_read_pref) - .map(Clone::clone); - - let resolved_read_pref = match specified_read_pref { - Some(ReadPreference::Primary) | None => ReadPreference::PrimaryPreferred { - options: Default::default(), - }, - Some(other) => other, - }; - - command.set_read_preference(resolved_read_pref) - } - _ => { - let read_pref = match criteria { - Some(SelectionCriteria::ReadPreference(rp)) => rp.clone(), - Some(SelectionCriteria::Predicate(_)) => ReadPreference::PrimaryPreferred { - options: Default::default(), - }, - None => ReadPreference::Primary, - }; - command.set_read_preference(read_pref) - } - } - } - - fn update_command_read_pref_for_mongos( - &self, - command: &mut Command, - criteria: Option<&SelectionCriteria>, - ) { - let read_preference = match criteria { - Some(SelectionCriteria::ReadPreference(rp)) => rp, - _ => return, - }; - match read_preference { - ReadPreference::Secondary { .. } - | ReadPreference::PrimaryPreferred { .. } - | ReadPreference::Nearest { .. } - | ReadPreference::SecondaryPreferred { .. } => { - command.set_read_preference(read_preference.clone()) - } - _ => {} - } - } - - /// Gets the heartbeat frequency. - fn heartbeat_frequency(&self) -> Duration { - self.heartbeat_freq.unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY) - } - - /// Check the cluster for a compatibility error, and record the error message if one is found. - fn check_compatibility(&mut self) { - self.compatibility_error = None; - - for server in self.servers.values() { - let error_message = server.compatibility_error_message(); - - if error_message.is_some() { - self.compatibility_error = error_message; - return; - } - } - } - - pub(crate) fn compatibility_error(&self) -> Option<&String> { - self.compatibility_error.as_ref() - } - - /// Updates the topology's logical session timeout value based on the server's value for it. - fn update_logical_session_timeout(&mut self, server_description: &ServerDescription) { - if !server_description.server_type.is_data_bearing() { - return; - } - match server_description.logical_session_timeout().ok().flatten() { - Some(new_timeout) => match self.logical_session_timeout { - Some(current_timeout) => { - self.logical_session_timeout = - Some(std::cmp::min(current_timeout, new_timeout)); - } - None => { - let min_timeout = self - .servers - .values() - .filter(|s| s.server_type.is_data_bearing()) - .map(|s| s.logical_session_timeout().ok().flatten()) - .min() - .flatten(); - self.logical_session_timeout = min_timeout; - } - }, - // If any data-bearing server does not have a value for logicalSessionTimeoutMinutes, - // the topology's value should be None. - None => self.logical_session_timeout = None, - } - } - - /// Updates the topology's transaction support status based on its session support status and - /// the server description's max wire version. - fn update_transaction_support_status(&mut self, server_description: &ServerDescription) { - if self.logical_session_timeout.is_none() { - self.transaction_support_status = TransactionSupportStatus::Unsupported; - } - if let Ok(Some(max_wire_version)) = server_description.max_wire_version() { - self.transaction_support_status = if max_wire_version < 7 - || (max_wire_version < 8 && self.topology_type == TopologyType::Sharded) - { - TransactionSupportStatus::Unsupported - } else { - TransactionSupportStatus::Supported - } - } - } - - /// Sets the topology's cluster time to the provided one if it is higher than the currently - /// recorded one. - pub(crate) fn advance_cluster_time(&mut self, cluster_time: &ClusterTime) { - if self.cluster_time.as_ref() >= Some(cluster_time) { - return; - } - self.cluster_time = Some(cluster_time.clone()); - } - - /// Returns the diff between this topology description and the provided one, or `None` if - /// they are equal. - /// - /// The returned `TopologyDescriptionDiff` refers to the changes reflected in the provided - /// description. For example, if the provided description has a server in it that this - /// description does not, it will be returned in the `added_addresses` field. - pub(crate) fn diff<'a>( - &'a self, - other: &'a TopologyDescription, - ) -> Option { - if self == other { - return None; - } - - let addresses: HashSet<&ServerAddress> = self.server_addresses().collect(); - let other_addresses: HashSet<&ServerAddress> = other.server_addresses().collect(); - - let changed_servers = self - .servers - .iter() - .filter_map(|(address, description)| match other.servers.get(address) { - Some(other_description) if description != other_description => { - Some((address, (description, other_description))) - } - _ => None, - }); - - Some(TopologyDescriptionDiff { - removed_addresses: addresses.difference(&other_addresses).cloned().collect(), - added_addresses: other_addresses.difference(&addresses).cloned().collect(), - changed_servers: changed_servers.collect(), - }) - } - - /// Syncs the set of servers in the description to those in `hosts`. Servers in the set not - /// already present in the cluster will be added, and servers in the cluster not present in the - /// set will be removed. - pub(crate) fn sync_hosts(&mut self, hosts: &HashSet) { - self.add_new_servers_from_addresses(hosts.iter()); - self.servers.retain(|host, _| hosts.contains(host)); - } - - pub(crate) fn transaction_support_status(&self) -> TransactionSupportStatus { - self.transaction_support_status - } - - /// Update the topology based on the new information about the topology contained by the - /// ServerDescription. - pub(crate) fn update(&mut self, mut server_description: ServerDescription) -> Result<()> { - match self.servers.get(&server_description.address) { - None => return Ok(()), - Some(existing_sd) => { - // Ignore updates from outdated topology versions. - if let Some(existing_tv) = existing_sd.topology_version() { - if let Some(new_tv) = server_description.topology_version() { - if existing_tv.process_id == new_tv.process_id - && new_tv.counter < existing_tv.counter - { - return Ok(()); - } - } - } - } - } - - if let Some(expected_name) = &self.set_name { - if server_description.is_available() { - let got_name = server_description.set_name(); - if self.topology_type() == TopologyType::Single - && !matches!( - got_name.as_ref().map(|opt| opt.as_ref()), - Ok(Some(name)) if name == expected_name - ) - { - let got_display = match got_name { - Ok(Some(s)) => format!("{:?}", s), - Ok(None) => "".to_string(), - Err(s) => format!("", s), - }; - // Mark server as unknown. - server_description = ServerDescription::new_from_error( - server_description.address, - Error::invalid_argument(format!( - "Connection string replicaSet name {:?} does not match actual name {}", - expected_name, got_display, - )), - ); - } - } - } - - // Replace the old info about the server with the new info. - self.servers.insert( - server_description.address.clone(), - server_description.clone(), - ); - - if let TopologyType::LoadBalanced = self.topology_type { - // Load-balanced topologies don't have real server updates; attempting to update based - // on the synthesized one causes incorrect behavior. - return Ok(()); - } - - // Update the topology's min logicalSessionTimeout. - self.update_logical_session_timeout(&server_description); - - // Update the topology's transaction support status. - self.update_transaction_support_status(&server_description); - - // Update the topology's max reported $clusterTime. - if let Some(ref cluster_time) = server_description.cluster_time().ok().flatten() { - self.advance_cluster_time(cluster_time); - } - - // Update the topology description based on the current topology type. - match self.topology_type { - TopologyType::Single | TopologyType::LoadBalanced => {} - TopologyType::Unknown => self.update_unknown_topology(server_description)?, - TopologyType::Sharded => self.update_sharded_topology(server_description), - TopologyType::ReplicaSetNoPrimary => { - self.update_replica_set_no_primary_topology(server_description)? - } - TopologyType::ReplicaSetWithPrimary => { - self.update_replica_set_with_primary_topology(server_description)?; - } - } - - // Record any compatibility error. - self.check_compatibility(); - - Ok(()) - } - - /// Update the Unknown topology description based on the server description. - fn update_unknown_topology(&mut self, server_description: ServerDescription) -> Result<()> { - match server_description.server_type { - ServerType::Unknown | ServerType::RsGhost => {} - ServerType::Standalone => { - self.update_unknown_with_standalone_server(server_description) - } - ServerType::Mongos => self.topology_type = TopologyType::Sharded, - ServerType::RsPrimary => { - self.topology_type = TopologyType::ReplicaSetWithPrimary; - self.update_rs_from_primary_server(server_description)?; - } - ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { - self.topology_type = TopologyType::ReplicaSetNoPrimary; - self.update_rs_without_primary_server(server_description)?; - } - ServerType::LoadBalancer => { - return Err(Error::internal("cannot transition to a load balancer")) - } - } - - Ok(()) - } - - /// Update the Sharded topology description based on the server description. - fn update_sharded_topology(&mut self, server_description: ServerDescription) { - match server_description.server_type { - ServerType::Unknown | ServerType::Mongos => {} - _ => { - self.servers.remove(&server_description.address); - } - } - } - - /// Update the ReplicaSetNoPrimary topology description based on the server description. - fn update_replica_set_no_primary_topology( - &mut self, - server_description: ServerDescription, - ) -> Result<()> { - match server_description.server_type { - ServerType::Unknown | ServerType::RsGhost => {} - ServerType::Standalone | ServerType::Mongos => { - self.servers.remove(&server_description.address); - } - ServerType::RsPrimary => { - self.topology_type = TopologyType::ReplicaSetWithPrimary; - self.update_rs_from_primary_server(server_description)? - } - ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { - self.update_rs_without_primary_server(server_description)?; - } - ServerType::LoadBalancer => { - return Err(Error::internal("cannot transition to a load balancer")) - } - } - - Ok(()) - } - - /// Update the ReplicaSetWithPrimary topology description based on the server description. - fn update_replica_set_with_primary_topology( - &mut self, - server_description: ServerDescription, - ) -> Result<()> { - match server_description.server_type { - ServerType::Unknown | ServerType::RsGhost => { - self.record_primary_state(); - } - ServerType::Standalone | ServerType::Mongos => { - self.servers.remove(&server_description.address); - self.record_primary_state(); - } - ServerType::RsPrimary => self.update_rs_from_primary_server(server_description)?, - ServerType::RsSecondary | ServerType::RsArbiter | ServerType::RsOther => { - self.update_rs_with_primary_from_member(server_description)?; - } - ServerType::LoadBalancer => { - return Err(Error::internal("cannot transition to a load balancer")); - } - } - - Ok(()) - } - - /// Update the Unknown topology description based on the Standalone server description. - fn update_unknown_with_standalone_server(&mut self, server_description: ServerDescription) { - if self.single_seed { - self.topology_type = TopologyType::Single; - } else { - self.servers.remove(&server_description.address); - } - } - - /// Update the ReplicaSetNoPrimary topology description based on the non-primary server - /// description. - fn update_rs_without_primary_server( - &mut self, - server_description: ServerDescription, - ) -> Result<()> { - if self.set_name.is_none() { - self.set_name = server_description.set_name()?; - } else if self.set_name != server_description.set_name()? { - self.servers.remove(&server_description.address); - - return Ok(()); - } - - self.add_new_servers(server_description.known_hosts()?)?; - - if server_description.invalid_me()? { - self.servers.remove(&server_description.address); - } - - Ok(()) - } - - /// Update the ReplicaSetWithPrimary topology description based on the non-primary server - /// description. - fn update_rs_with_primary_from_member( - &mut self, - server_description: ServerDescription, - ) -> Result<()> { - if self.set_name != server_description.set_name()? { - self.servers.remove(&server_description.address); - self.record_primary_state(); - - return Ok(()); - } - - if server_description.invalid_me()? { - self.servers.remove(&server_description.address); - self.record_primary_state(); - - return Ok(()); - } - - Ok(()) - } - - /// Update the replica set topology description based on the RSPrimary server description. - fn update_rs_from_primary_server( - &mut self, - server_description: ServerDescription, - ) -> Result<()> { - if self.set_name.is_none() { - self.set_name = server_description.set_name()?; - } else if self.set_name != server_description.set_name()? { - self.servers.remove(&server_description.address); - self.record_primary_state(); - - return Ok(()); - } - - if let Some(server_set_version) = server_description.set_version()? { - if let Some(server_election_id) = server_description.election_id()? { - if let Some(topology_max_set_version) = self.max_set_version { - if let Some(ref topology_max_election_id) = self.max_election_id { - if topology_max_set_version > server_set_version - || (topology_max_set_version == server_set_version - && *topology_max_election_id > server_election_id) - { - self.servers.insert( - server_description.address.clone(), - ServerDescription::new(server_description.address), - ); - self.record_primary_state(); - return Ok(()); - } - } - } - - self.max_election_id = Some(server_election_id); - } - } - - if let Some(server_set_version) = server_description.set_version()? { - if self - .max_set_version - .as_ref() - .map(|topology_max_set_version| server_set_version > *topology_max_set_version) - .unwrap_or(true) - { - self.max_set_version = Some(server_set_version); - } - } - - let addresses: Vec<_> = self.servers.keys().cloned().collect(); - - // If any other servers are RSPrimary, replace them with an unknown server decscription, - // which will cause them to be updated by a new server check. - for address in addresses.clone() { - if address == server_description.address { - continue; - } - - if let ServerType::RsPrimary = self.servers.get(&address).unwrap().server_type { - self.servers - .insert(address.clone(), ServerDescription::new(address)); - } - } - - self.add_new_servers(server_description.known_hosts()?)?; - let known_hosts: HashSet<_> = server_description.known_hosts()?.collect(); - - for address in addresses { - if !known_hosts.contains(&address.to_string()) { - self.servers.remove(&address); - } - } - - self.record_primary_state(); - - Ok(()) - } - - /// Inspect the topology for a primary server, and update the topology type to - /// ReplicaSetNoPrimary if none is found. - /// - /// This should only be called on a replica set topology. - fn record_primary_state(&mut self) { - self.topology_type = if self - .servers - .values() - .any(|server| server.server_type == ServerType::RsPrimary) - { - TopologyType::ReplicaSetWithPrimary - } else { - TopologyType::ReplicaSetNoPrimary - }; - } - - /// Create a new ServerDescription for each address and add it to the topology. - fn add_new_servers<'a>(&mut self, servers: impl Iterator) -> Result<()> { - let servers: Result> = servers.map(ServerAddress::parse).collect(); - - self.add_new_servers_from_addresses(servers?.iter()); - Ok(()) - } - - /// Create a new ServerDescription for each address and add it to the topology. - fn add_new_servers_from_addresses<'a>( - &mut self, - servers: impl Iterator, - ) { - for server in servers { - if !self.servers.contains_key(server) { - self.servers - .insert(server.clone(), ServerDescription::new(server.clone())); - } - } - } -} - -/// Enum representing whether transactions are supported by the topology. -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum TransactionSupportStatus { - /// It is not known yet whether the topology supports transactions. This is possible if no - /// data-bearing servers have updated the `TopologyDescription` yet. - Undetermined, - - /// Transactions are not supported by this topology. - Unsupported, - - /// Transactions are supported by this topology. A topology supports transactions if it - /// supports sessions and its maxWireVersion >= 7. If the topology is sharded, maxWireVersion - /// must be >= 8 for transactions to be supported. - /// - /// Note that meeting these conditions does not guarantee that a deployment - /// supports transactions; any other missing qualification will be reported by the server. - Supported, -} - -impl Default for TransactionSupportStatus { - fn default() -> Self { - Self::Undetermined - } -} - -/// A struct representing the diff between two `TopologyDescription`s. -/// Returned from `TopologyDescription::diff`. -#[derive(Debug)] -pub(crate) struct TopologyDescriptionDiff<'a> { - pub(crate) removed_addresses: HashSet<&'a ServerAddress>, - pub(crate) added_addresses: HashSet<&'a ServerAddress>, - pub(crate) changed_servers: - HashMap<&'a ServerAddress, (&'a ServerDescription, &'a ServerDescription)>, -} - -pub(crate) fn verify_max_staleness( - max_staleness: Duration, - heartbeat_frequency: Duration, -) -> crate::error::Result<()> { - let smallest_max_staleness = std::cmp::max( - Duration::from_secs(90), - heartbeat_frequency - .checked_add(IDLE_WRITE_PERIOD) - .unwrap_or(Duration::MAX), - ); - - if max_staleness < smallest_max_staleness { - return Err(Error::invalid_argument(format!( - "invalid max_staleness value: must be at least {} seconds", - smallest_max_staleness.as_secs() - ))); - } - - Ok(()) -} diff --git a/src/sdam/description/topology/server_selection.rs b/src/sdam/description/topology/server_selection.rs new file mode 100644 index 000000000..e282c8a77 --- /dev/null +++ b/src/sdam/description/topology/server_selection.rs @@ -0,0 +1,439 @@ +#[cfg(test)] +mod test; + +use std::{collections::HashMap, fmt, ops::Deref, sync::Arc, time::Duration}; + +use super::TopologyDescription; +use crate::{ + error::{ErrorKind, Result}, + options::ServerAddress, + sdam::{ + description::{ + server::{ServerDescription, ServerType}, + topology::TopologyType, + }, + Server, + ServerInfo, + }, + selection_criteria::{ReadPreference, SelectionCriteria, TagSet}, +}; + +const DEFAULT_LOCAL_THRESHOLD: Duration = Duration::from_millis(15); +pub(crate) const IDLE_WRITE_PERIOD: Duration = Duration::from_secs(10); + +/// Struct encapsulating a selected server that handles the opcount accounting. +#[derive(Debug)] +pub(crate) struct SelectedServer { + server: Arc, +} + +impl SelectedServer { + fn new(server: Arc) -> Self { + server.increment_operation_count(); + Self { server } + } + + #[cfg(feature = "tracing-unstable")] + pub(crate) fn address(&self) -> &ServerAddress { + &self.server.address + } +} + +impl Deref for SelectedServer { + type Target = Server; + + fn deref(&self) -> &Server { + self.server.deref() + } +} + +impl Drop for SelectedServer { + fn drop(&mut self) { + self.server.decrement_operation_count(); + } +} + +/// Attempt to select a server, returning None if no server could be selected +/// that matched the provided criteria. +pub(crate) fn attempt_to_select_server<'a>( + criteria: &'a SelectionCriteria, + topology_description: &'a TopologyDescription, + servers: &'a HashMap>, + deprioritized: Option<&ServerAddress>, +) -> Result> { + let mut in_window = topology_description.suitable_servers_in_latency_window(criteria)?; + if let Some(addr) = deprioritized { + if in_window.len() > 1 { + in_window.retain(|d| &d.address != addr); + } + } + let in_window_servers = in_window + .into_iter() + .flat_map(|desc| servers.get(&desc.address)) + .collect(); + let selected = select_server_in_latency_window(in_window_servers); + Ok(selected.map(SelectedServer::new)) +} + +/// Choose a server from several suitable choices within the latency window according to +/// the algorithm laid out in the server selection specification. +fn select_server_in_latency_window(in_window: Vec<&Arc>) -> Option> { + if in_window.is_empty() { + return None; + } else if in_window.len() == 1 { + return Some(in_window[0].clone()); + } + + super::choose_n(&in_window, 2) + .min_by_key(|s| s.operation_count()) + .map(|server| (*server).clone()) +} + +impl TopologyDescription { + pub(crate) fn server_selection_timeout_error_message( + &self, + criteria: &SelectionCriteria, + ) -> String { + if self.has_available_servers() { + format!( + "Server selection timeout: None of the available servers suitable for criteria \ + {:?}. Topology: {}", + criteria, self + ) + } else { + format!( + "Server selection timeout: No available servers. Topology: {}", + self + ) + } + } + + pub(crate) fn suitable_servers_in_latency_window<'a>( + &'a self, + criteria: &'a SelectionCriteria, + ) -> Result> { + if let Some(message) = self.compatibility_error() { + return Err(ErrorKind::ServerSelection { + message: message.to_string(), + } + .into()); + } + + let mut suitable_servers = match criteria { + SelectionCriteria::ReadPreference(ref read_pref) => self.suitable_servers(read_pref)?, + SelectionCriteria::Predicate(ref filter) => self + .servers + .values() + .filter(|s| { + // If we're direct-connected or connected to a standalone, ignore whether the + // single server in the topology is data-bearing. + (self.topology_type == TopologyType::Single || s.server_type.is_data_bearing()) + && filter(&ServerInfo::new_borrowed(s)) + }) + .collect(), + }; + + self.retain_servers_within_latency_window(&mut suitable_servers); + + Ok(suitable_servers) + } + + pub(crate) fn has_available_servers(&self) -> bool { + self.servers.values().any(|server| server.is_available()) + } + + fn suitable_servers( + &self, + read_preference: &ReadPreference, + ) -> Result> { + let servers = match self.topology_type { + TopologyType::Unknown => Vec::new(), + TopologyType::Single | TopologyType::LoadBalanced => self.servers.values().collect(), + TopologyType::Sharded => self.servers_with_type(&[ServerType::Mongos]).collect(), + TopologyType::ReplicaSetWithPrimary | TopologyType::ReplicaSetNoPrimary => { + self.suitable_servers_in_replica_set(read_preference)? + } + }; + + Ok(servers) + } + + fn retain_servers_within_latency_window(&self, suitable_servers: &mut Vec<&ServerDescription>) { + let shortest_average_rtt = suitable_servers + .iter() + .filter_map(|server_desc| server_desc.average_round_trip_time) + .fold(Option::::None, |min, curr| match min { + Some(prev) => Some(prev.min(curr)), + None => Some(curr), + }); + + let local_threshold = self.local_threshold.unwrap_or(DEFAULT_LOCAL_THRESHOLD); + + let max_rtt_within_window = shortest_average_rtt + .map(|rtt| rtt.checked_add(local_threshold).unwrap_or(Duration::MAX)); + + suitable_servers.retain(move |server_desc| { + if let Some(server_rtt) = server_desc.average_round_trip_time { + // unwrap() is safe here because this server's avg rtt being Some indicates that + // there exists a max rtt as well. + server_rtt <= max_rtt_within_window.unwrap() + } else { + // SDAM isn't performed with a load balanced topology, so the load balancer won't + // have an RTT. Instead, we just select it. + matches!(server_desc.server_type, ServerType::LoadBalancer) + } + }); + } + + pub(crate) fn servers_with_type<'a>( + &'a self, + types: &'a [ServerType], + ) -> impl Iterator { + self.servers + .values() + .filter(move |server| types.contains(&server.server_type)) + } + + #[cfg(any(test, feature = "in-use-encryption"))] + pub(crate) fn primary(&self) -> Option<&ServerDescription> { + self.servers_with_type(&[ServerType::RsPrimary]).next() + } + + fn suitable_servers_in_replica_set( + &self, + read_preference: &ReadPreference, + ) -> Result> { + let tag_sets = read_preference.tag_sets(); + let max_staleness = read_preference.max_staleness(); + + let servers = match read_preference { + ReadPreference::Primary => self.servers_with_type(&[ServerType::RsPrimary]).collect(), + ReadPreference::Secondary { .. } => self.suitable_servers_for_read_preference( + &[ServerType::RsSecondary], + tag_sets, + max_staleness, + )?, + ReadPreference::PrimaryPreferred { .. } => { + match self.servers_with_type(&[ServerType::RsPrimary]).next() { + Some(primary) => vec![primary], + None => self.suitable_servers_for_read_preference( + &[ServerType::RsSecondary], + tag_sets, + max_staleness, + )?, + } + } + ReadPreference::SecondaryPreferred { .. } => { + let suitable_servers = self.suitable_servers_for_read_preference( + &[ServerType::RsSecondary], + tag_sets, + max_staleness, + )?; + + if suitable_servers.is_empty() { + self.servers_with_type(&[ServerType::RsPrimary]).collect() + } else { + suitable_servers + } + } + ReadPreference::Nearest { .. } => self.suitable_servers_for_read_preference( + &[ServerType::RsPrimary, ServerType::RsSecondary], + tag_sets, + max_staleness, + )?, + }; + + Ok(servers) + } + + fn suitable_servers_for_read_preference( + &self, + types: &'static [ServerType], + tag_sets: Option<&Vec>, + max_staleness: Option, + ) -> Result> { + if let Some(max_staleness) = max_staleness { + super::verify_max_staleness(max_staleness, self.heartbeat_frequency())?; + } + + let mut servers = self.servers_with_type(types).collect(); + + // We don't need to check for the Client's default max_staleness because it would be passed + // in as part of the Client's default ReadPreference if none is specified for the operation. + if let Some(max_staleness) = max_staleness { + // According to the spec, max staleness <= 0 is the same as no max staleness. + if max_staleness > Duration::from_secs(0) { + self.filter_servers_by_max_staleness(&mut servers, max_staleness); + } + } + + if let Some(tag_sets) = tag_sets { + filter_servers_by_tag_sets(&mut servers, tag_sets); + } + + Ok(servers) + } + + fn filter_servers_by_max_staleness( + &self, + servers: &mut Vec<&ServerDescription>, + max_staleness: Duration, + ) { + let primary = self + .servers + .values() + .find(|server| server.server_type == ServerType::RsPrimary); + + match primary { + Some(primary) => { + self.filter_servers_by_max_staleness_with_primary(servers, primary, max_staleness) + } + None => self.filter_servers_by_max_staleness_without_primary(servers, max_staleness), + }; + } + + fn filter_servers_by_max_staleness_with_primary( + &self, + servers: &mut Vec<&ServerDescription>, + primary: &ServerDescription, + max_staleness: Duration, + ) { + let max_staleness_ms = max_staleness.as_millis().try_into().unwrap_or(i64::MAX); + + servers.retain(|server| { + let server_staleness = self.calculate_secondary_staleness_with_primary(server, primary); + + server_staleness + .map(|staleness| staleness <= max_staleness_ms) + .unwrap_or(false) + }) + } + + fn filter_servers_by_max_staleness_without_primary( + &self, + servers: &mut Vec<&ServerDescription>, + max_staleness: Duration, + ) { + let max_staleness = max_staleness.as_millis().try_into().unwrap_or(i64::MAX); + let max_write_date = self + .servers + .values() + .filter(|server| server.server_type == ServerType::RsSecondary) + .filter_map(|server| { + server + .last_write_date() + .ok() + .and_then(std::convert::identity) + }) + .map(|last_write_date| last_write_date.timestamp_millis()) + .max(); + + let secondary_max_write_date = match max_write_date { + Some(max_write_date) => max_write_date, + None => return, + }; + + servers.retain(|server| { + let server_staleness = self + .calculate_secondary_staleness_without_primary(server, secondary_max_write_date); + + server_staleness + .map(|staleness| staleness <= max_staleness) + .unwrap_or(false) + }) + } + + fn calculate_secondary_staleness_with_primary( + &self, + secondary: &ServerDescription, + primary: &ServerDescription, + ) -> Option { + let primary_last_update = primary.last_update_time?.timestamp_millis(); + let primary_last_write = primary.last_write_date().ok()??.timestamp_millis(); + + let secondary_last_update = secondary.last_update_time?.timestamp_millis(); + let secondary_last_write = secondary.last_write_date().ok()??.timestamp_millis(); + + let heartbeat_frequency = self + .heartbeat_frequency() + .as_millis() + .try_into() + .unwrap_or(i64::MAX); + + let staleness = (secondary_last_update - secondary_last_write) + - (primary_last_update - primary_last_write) + + heartbeat_frequency; + + Some(staleness) + } + + fn calculate_secondary_staleness_without_primary( + &self, + secondary: &ServerDescription, + max_last_write_date: i64, + ) -> Option { + let secondary_last_write = secondary.last_write_date().ok()??.timestamp_millis(); + let heartbeat_frequency = self + .heartbeat_frequency() + .as_millis() + .try_into() + .unwrap_or(i64::MAX); + + let staleness = max_last_write_date - secondary_last_write + heartbeat_frequency; + Some(staleness) + } +} + +impl fmt::Display for TopologyDescription { + fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { + write!(f, "{{ Type: {}", self.topology_type)?; + + if let Some(ref set_name) = self.set_name { + write!(f, ", Set Name: {}", set_name)?; + } + + if let Some(max_set_version) = self.max_set_version { + write!(f, ", Max Set Version: {}", max_set_version)?; + } + + if let Some(max_election_id) = self.max_election_id { + write!(f, ", Max Election ID: {}", max_election_id)?; + } + + if let Some(ref compatibility_error) = self.compatibility_error { + write!(f, ", Compatibility Error: {}", compatibility_error)?; + } + + if !self.servers.is_empty() { + write!(f, ", Servers: [ ")?; + let mut iter = self.servers.values(); + if let Some(server) = iter.next() { + write!(f, "{}", ServerInfo::new_borrowed(server))?; + } + for server in iter { + write!(f, ", {}", ServerInfo::new_borrowed(server))?; + } + write!(f, " ]")?; + } + + write!(f, " }}") + } +} + +fn filter_servers_by_tag_sets(servers: &mut Vec<&ServerDescription>, tag_sets: &[TagSet]) { + if tag_sets.is_empty() { + return; + } + + for tag_set in tag_sets { + let matches_tag_set = |server: &&ServerDescription| server.matches_tag_set(tag_set); + + if servers.iter().any(matches_tag_set) { + servers.retain(matches_tag_set); + + return; + } + } + + servers.clear(); +} diff --git a/src/sdam/description/topology/server_selection/mod.rs b/src/sdam/description/topology/server_selection/mod.rs deleted file mode 100644 index b63da884d..000000000 --- a/src/sdam/description/topology/server_selection/mod.rs +++ /dev/null @@ -1,421 +0,0 @@ -#[cfg(test)] -mod test; - -use std::{collections::HashMap, fmt, ops::Deref, sync::Arc, time::Duration}; - -use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng}; - -use super::TopologyDescription; -use crate::{ - error::{ErrorKind, Result}, - options::ServerAddress, - sdam::{ - description::{ - server::{ServerDescription, ServerType}, - topology::TopologyType, - }, - Server, - ServerInfo, - }, - selection_criteria::{ReadPreference, SelectionCriteria, TagSet}, -}; - -const DEFAULT_LOCAL_THRESHOLD: Duration = Duration::from_millis(15); -pub(crate) const IDLE_WRITE_PERIOD: Duration = Duration::from_secs(10); - -/// Struct encapsulating a selected server that handles the opcount accounting. -#[derive(Debug)] -pub(crate) struct SelectedServer { - server: Arc, -} - -impl SelectedServer { - fn new(server: Arc) -> Self { - server.increment_operation_count(); - Self { server } - } - - #[cfg(feature = "tracing-unstable")] - pub(crate) fn address(&self) -> &ServerAddress { - &self.server.address - } -} - -impl Deref for SelectedServer { - type Target = Server; - - fn deref(&self) -> &Server { - self.server.deref() - } -} - -impl Drop for SelectedServer { - fn drop(&mut self) { - self.server.decrement_operation_count(); - } -} - -/// Attempt to select a server, returning None if no server could be selected -/// that matched the provided criteria. -pub(crate) fn attempt_to_select_server<'a>( - criteria: &'a SelectionCriteria, - topology_description: &'a TopologyDescription, - servers: &'a HashMap>, -) -> Result> { - let in_window = topology_description.suitable_servers_in_latency_window(criteria)?; - let in_window_servers = in_window - .into_iter() - .flat_map(|desc| servers.get(&desc.address)) - .collect(); - Ok(select_server_in_latency_window(in_window_servers).map(SelectedServer::new)) -} - -/// Choose a server from several suitable choices within the latency window according to -/// the algorithm laid out in the server selection specification. -fn select_server_in_latency_window(in_window: Vec<&Arc>) -> Option> { - if in_window.is_empty() { - return None; - } else if in_window.len() == 1 { - return Some(in_window[0].clone()); - } - - let mut rng = SmallRng::from_entropy(); - in_window - .choose_multiple(&mut rng, 2) - .min_by_key(|s| s.operation_count()) - .map(|server| (*server).clone()) -} - -impl TopologyDescription { - pub(crate) fn server_selection_timeout_error_message( - &self, - criteria: &SelectionCriteria, - ) -> String { - if self.has_available_servers() { - format!( - "Server selection timeout: None of the available servers suitable for criteria \ - {:?}. Topology: {}", - criteria, self - ) - } else { - format!( - "Server selection timeout: No available servers. Topology: {}", - self - ) - } - } - - pub(crate) fn suitable_servers_in_latency_window<'a>( - &'a self, - criteria: &'a SelectionCriteria, - ) -> Result> { - if let Some(message) = self.compatibility_error() { - return Err(ErrorKind::ServerSelection { - message: message.to_string(), - } - .into()); - } - - let mut suitable_servers = match criteria { - SelectionCriteria::ReadPreference(ref read_pref) => self.suitable_servers(read_pref)?, - SelectionCriteria::Predicate(ref filter) => self - .servers - .values() - .filter(|s| s.server_type.is_data_bearing() && filter(&ServerInfo::new_borrowed(s))) - .collect(), - }; - - self.retain_servers_within_latency_window(&mut suitable_servers); - - Ok(suitable_servers) - } - - pub(crate) fn has_available_servers(&self) -> bool { - self.servers.values().any(|server| server.is_available()) - } - - fn suitable_servers( - &self, - read_preference: &ReadPreference, - ) -> Result> { - let servers = match self.topology_type { - TopologyType::Unknown => Vec::new(), - TopologyType::Single | TopologyType::LoadBalanced => self.servers.values().collect(), - TopologyType::Sharded => self.servers_with_type(&[ServerType::Mongos]).collect(), - TopologyType::ReplicaSetWithPrimary | TopologyType::ReplicaSetNoPrimary => { - self.suitable_servers_in_replica_set(read_preference)? - } - }; - - Ok(servers) - } - - fn retain_servers_within_latency_window(&self, suitable_servers: &mut Vec<&ServerDescription>) { - let shortest_average_rtt = suitable_servers - .iter() - .filter_map(|server_desc| server_desc.average_round_trip_time) - .fold(Option::::None, |min, curr| match min { - Some(prev) => Some(prev.min(curr)), - None => Some(curr), - }); - - let local_threshold = self.local_threshold.unwrap_or(DEFAULT_LOCAL_THRESHOLD); - - let max_rtt_within_window = shortest_average_rtt - .map(|rtt| rtt.checked_add(local_threshold).unwrap_or(Duration::MAX)); - - suitable_servers.retain(move |server_desc| { - if let Some(server_rtt) = server_desc.average_round_trip_time { - // unwrap() is safe here because this server's avg rtt being Some indicates that - // there exists a max rtt as well. - server_rtt <= max_rtt_within_window.unwrap() - } else { - // SDAM isn't performed with a load balanced topology, so the load balancer won't - // have an RTT. Instead, we just select it. - matches!(server_desc.server_type, ServerType::LoadBalancer) - } - }); - } - - pub(crate) fn servers_with_type<'a>( - &'a self, - types: &'a [ServerType], - ) -> impl Iterator { - self.servers - .values() - .filter(move |server| types.contains(&server.server_type)) - } - - #[cfg(any(test, feature = "in-use-encryption-unstable"))] - pub(crate) fn primary(&self) -> Option<&ServerDescription> { - self.servers_with_type(&[ServerType::RsPrimary]).next() - } - - fn suitable_servers_in_replica_set( - &self, - read_preference: &ReadPreference, - ) -> Result> { - let servers = match read_preference { - ReadPreference::Primary => self.servers_with_type(&[ServerType::RsPrimary]).collect(), - ReadPreference::Secondary { ref options } => self - .suitable_servers_for_read_preference( - &[ServerType::RsSecondary], - options.tag_sets.as_ref(), - options.max_staleness, - )?, - ReadPreference::PrimaryPreferred { ref options } => { - match self.servers_with_type(&[ServerType::RsPrimary]).next() { - Some(primary) => vec![primary], - None => self.suitable_servers_for_read_preference( - &[ServerType::RsSecondary], - options.tag_sets.as_ref(), - options.max_staleness, - )?, - } - } - ReadPreference::SecondaryPreferred { ref options } => { - let suitable_servers = self.suitable_servers_for_read_preference( - &[ServerType::RsSecondary], - options.tag_sets.as_ref(), - options.max_staleness, - )?; - - if suitable_servers.is_empty() { - self.servers_with_type(&[ServerType::RsPrimary]).collect() - } else { - suitable_servers - } - } - ReadPreference::Nearest { ref options } => self.suitable_servers_for_read_preference( - &[ServerType::RsPrimary, ServerType::RsSecondary], - options.tag_sets.as_ref(), - options.max_staleness, - )?, - }; - - Ok(servers) - } - - fn suitable_servers_for_read_preference( - &self, - types: &'static [ServerType], - tag_sets: Option<&Vec>, - max_staleness: Option, - ) -> Result> { - if let Some(max_staleness) = max_staleness { - super::verify_max_staleness(max_staleness, self.heartbeat_frequency())?; - } - - let mut servers = self.servers_with_type(types).collect(); - - // We don't need to check for the Client's default max_staleness because it would be passed - // in as part of the Client's default ReadPreference if none is specified for the operation. - if let Some(max_staleness) = max_staleness { - // According to the spec, max staleness <= 0 is the same as no max staleness. - if max_staleness > Duration::from_secs(0) { - self.filter_servers_by_max_staleness(&mut servers, max_staleness); - } - } - - if let Some(tag_sets) = tag_sets { - filter_servers_by_tag_sets(&mut servers, tag_sets); - } - - Ok(servers) - } - - fn filter_servers_by_max_staleness( - &self, - servers: &mut Vec<&ServerDescription>, - max_staleness: Duration, - ) { - let primary = self - .servers - .values() - .find(|server| server.server_type == ServerType::RsPrimary); - - match primary { - Some(primary) => { - self.filter_servers_by_max_staleness_with_primary(servers, primary, max_staleness) - } - None => self.filter_servers_by_max_staleness_without_primary(servers, max_staleness), - }; - } - - fn filter_servers_by_max_staleness_with_primary( - &self, - servers: &mut Vec<&ServerDescription>, - primary: &ServerDescription, - max_staleness: Duration, - ) { - let max_staleness_ms = max_staleness.as_millis() as i64; - - servers.retain(|server| { - let server_staleness = self.calculate_secondary_staleness_with_primary(server, primary); - - server_staleness - .map(|staleness| staleness <= max_staleness_ms) - .unwrap_or(false) - }) - } - - fn filter_servers_by_max_staleness_without_primary( - &self, - servers: &mut Vec<&ServerDescription>, - max_staleness: Duration, - ) { - let max_staleness = max_staleness.as_millis() as i64; - let max_write_date = self - .servers - .values() - .filter(|server| server.server_type == ServerType::RsSecondary) - .filter_map(|server| { - server - .last_write_date() - .ok() - .and_then(std::convert::identity) - }) - .map(|last_write_date| last_write_date.timestamp_millis()) - .max(); - - let secondary_max_write_date = match max_write_date { - Some(max_write_date) => max_write_date, - None => return, - }; - - servers.retain(|server| { - let server_staleness = self - .calculate_secondary_staleness_without_primary(server, secondary_max_write_date); - - server_staleness - .map(|staleness| staleness <= max_staleness) - .unwrap_or(false) - }) - } - - fn calculate_secondary_staleness_with_primary( - &self, - secondary: &ServerDescription, - primary: &ServerDescription, - ) -> Option { - let primary_last_update = primary.last_update_time?.timestamp_millis(); - let primary_last_write = primary.last_write_date().ok()??.timestamp_millis(); - - let secondary_last_update = secondary.last_update_time?.timestamp_millis(); - let secondary_last_write = secondary.last_write_date().ok()??.timestamp_millis(); - - let heartbeat_frequency = self.heartbeat_frequency().as_millis() as i64; - - let staleness = (secondary_last_update - secondary_last_write) - - (primary_last_update - primary_last_write) - + heartbeat_frequency; - - Some(staleness) - } - - fn calculate_secondary_staleness_without_primary( - &self, - secondary: &ServerDescription, - max_last_write_date: i64, - ) -> Option { - let secondary_last_write = secondary.last_write_date().ok()??.timestamp_millis(); - let heartbeat_frequency = self.heartbeat_frequency().as_millis() as i64; - - let staleness = max_last_write_date - secondary_last_write + heartbeat_frequency; - Some(staleness) - } -} - -impl fmt::Display for TopologyDescription { - fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { - write!(f, "{{ Type: {}", self.topology_type)?; - - if let Some(ref set_name) = self.set_name { - write!(f, ", Set Name: {}", set_name)?; - } - - if let Some(max_set_version) = self.max_set_version { - write!(f, ", Max Set Version: {}", max_set_version)?; - } - - if let Some(max_election_id) = self.max_election_id { - write!(f, ", Max Election ID: {}", max_election_id)?; - } - - if let Some(ref compatibility_error) = self.compatibility_error { - write!(f, ", Compatibility Error: {}", compatibility_error)?; - } - - if !self.servers.is_empty() { - write!(f, ", Servers: [ ")?; - let mut iter = self.servers.values(); - if let Some(server) = iter.next() { - write!(f, "{}", ServerInfo::new_borrowed(server))?; - } - for server in iter { - write!(f, ", {}", ServerInfo::new_borrowed(server))?; - } - write!(f, " ]")?; - } - - write!(f, " }}") - } -} - -fn filter_servers_by_tag_sets(servers: &mut Vec<&ServerDescription>, tag_sets: &[TagSet]) { - if tag_sets.is_empty() { - return; - } - - for tag_set in tag_sets { - let matches_tag_set = |server: &&ServerDescription| server.matches_tag_set(tag_set); - - if servers.iter().any(matches_tag_set) { - servers.retain(matches_tag_set); - - return; - } - } - - servers.clear(); -} diff --git a/src/sdam/description/topology/server_selection/test.rs b/src/sdam/description/topology/server_selection/test.rs new file mode 100644 index 000000000..0ecc74655 --- /dev/null +++ b/src/sdam/description/topology/server_selection/test.rs @@ -0,0 +1,251 @@ +use std::{collections::HashMap, sync::Arc, time::Duration}; + +use serde::Deserialize; + +use crate::{ + bson::{doc, DateTime}, + hello::{HelloCommandResponse, HelloReply, LastWrite}, + options::ServerAddress, + sdam::{ + description::topology::{test::f64_ms_as_duration, TopologyType}, + ServerDescription, + ServerType, + TopologyDescription, + }, + selection_criteria::{SelectionCriteria, TagSet}, +}; + +mod in_window; +mod logic; + +#[derive(Debug, Deserialize)] +struct TestTopologyDescription { + #[serde(rename = "type")] + topology_type: TopologyType, + servers: Vec, +} + +impl TestTopologyDescription { + fn into_topology_description( + self, + heartbeat_frequency: Option, + ) -> TopologyDescription { + let servers: HashMap = self + .servers + .into_iter() + .filter_map(|sd| { + sd.into_server_description() + .map(|sd| (sd.address.clone(), sd)) + }) + .collect(); + + TopologyDescription { + single_seed: servers.len() == 1, + topology_type: self.topology_type, + set_name: None, + max_set_version: None, + max_election_id: None, + compatibility_error: None, + logical_session_timeout: None, + transaction_support_status: Default::default(), + cluster_time: None, + local_threshold: None, + heartbeat_freq: heartbeat_frequency, + servers, + srv_max_hosts: None, + } + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct TestServerDescription { + address: String, + #[serde(rename = "avg_rtt_ms")] + avg_rtt_ms: Option, + #[serde(rename = "type")] + server_type: TestServerType, + tags: Option, + last_update_time: Option, + last_write: Option, + // We don't need to use this field, but it needs to be included during deserialization so that + // we can use the deny_unknown_fields tag. + _max_wire_version: Option, +} + +impl TestServerDescription { + fn into_server_description(self) -> Option { + let server_type = self.server_type.into_server_type()?; + + let server_address = ServerAddress::parse(self.address).ok()?; + let tags = self.tags; + let last_write = self.last_write; + let avg_rtt_ms = self.avg_rtt_ms; + let reply = hello_response_from_server_type(server_type).map(|mut command_response| { + command_response.tags = tags; + command_response.last_write = last_write.map(|last_write| LastWrite { + last_write_date: DateTime::from_millis(last_write.last_write_date), + }); + HelloReply { + server_address: server_address.clone(), + command_response, + cluster_time: None, + raw_command_response: Default::default(), + } + }); + + let mut server_desc = match reply { + Some(reply) => ServerDescription::new_from_hello_reply( + server_address, + reply, + avg_rtt_ms.map(f64_ms_as_duration).unwrap(), + ), + None => ServerDescription::new(&server_address), + }; + server_desc.last_update_time = self + .last_update_time + .map(|i| DateTime::from_millis(i.into())); + + Some(server_desc) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct LastWriteDate { + last_write_date: i64, +} + +#[derive(Clone, Copy, Debug, Deserialize)] +enum TestServerType { + Standalone, + Mongos, + #[serde(rename = "RSPrimary")] + RsPrimary, + #[serde(rename = "RSSecondary")] + RsSecondary, + #[serde(rename = "RSArbiter")] + RsArbiter, + #[serde(rename = "RSOther")] + RsOther, + #[serde(rename = "RSGhost")] + RsGhost, + LoadBalancer, + Unknown, + PossiblePrimary, +} + +impl TestServerType { + fn into_server_type(self) -> Option { + match self { + TestServerType::Standalone => Some(ServerType::Standalone), + TestServerType::Mongos => Some(ServerType::Mongos), + TestServerType::RsPrimary => Some(ServerType::RsPrimary), + TestServerType::RsSecondary => Some(ServerType::RsSecondary), + TestServerType::RsArbiter => Some(ServerType::RsArbiter), + TestServerType::RsOther => Some(ServerType::RsOther), + TestServerType::RsGhost => Some(ServerType::RsGhost), + TestServerType::LoadBalancer => Some(ServerType::LoadBalancer), + TestServerType::Unknown => Some(ServerType::Unknown), + TestServerType::PossiblePrimary => None, + } + } +} + +fn hello_response_from_server_type(server_type: ServerType) -> Option { + let mut response = HelloCommandResponse::default(); + + match server_type { + ServerType::Unknown => { + return None; + } + ServerType::Mongos => { + response.msg = Some("isdbgrid".into()); + } + ServerType::RsPrimary => { + response.set_name = Some("foo".into()); + response.is_writable_primary = Some(true); + } + ServerType::RsOther => { + response.set_name = Some("foo".into()); + response.hidden = Some(true); + } + ServerType::RsSecondary => { + response.set_name = Some("foo".into()); + response.secondary = Some(true); + } + ServerType::RsArbiter => { + response.set_name = Some("foo".into()); + response.arbiter_only = Some(true); + } + ServerType::RsGhost => { + response.is_replica_set = Some(true); + } + ServerType::Standalone | ServerType::LoadBalancer => {} + }; + + Some(response) +} + +#[test] +fn predicate_omits_unavailable() { + let criteria = SelectionCriteria::Predicate(Arc::new(|si| { + !matches!(si.server_type(), ServerType::RsPrimary) + })); + + let desc = TestTopologyDescription { + topology_type: TopologyType::ReplicaSetWithPrimary, + servers: vec![ + TestServerDescription { + address: "localhost:27017".to_string(), + avg_rtt_ms: Some(12.0), + server_type: TestServerType::RsPrimary, + tags: None, + last_update_time: None, + last_write: None, + _max_wire_version: None, + }, + TestServerDescription { + address: "localhost:27018".to_string(), + avg_rtt_ms: Some(12.0), + server_type: TestServerType::Unknown, + tags: None, + last_update_time: None, + last_write: None, + _max_wire_version: None, + }, + TestServerDescription { + address: "localhost:27019".to_string(), + avg_rtt_ms: Some(12.0), + server_type: TestServerType::RsArbiter, + tags: None, + last_update_time: None, + last_write: None, + _max_wire_version: None, + }, + TestServerDescription { + address: "localhost:27020".to_string(), + avg_rtt_ms: Some(12.0), + server_type: TestServerType::RsGhost, + tags: None, + last_update_time: None, + last_write: None, + _max_wire_version: None, + }, + TestServerDescription { + address: "localhost:27021".to_string(), + avg_rtt_ms: Some(12.0), + server_type: TestServerType::RsOther, + tags: None, + last_update_time: None, + last_write: None, + _max_wire_version: None, + }, + ], + } + .into_topology_description(None); + pretty_assertions::assert_eq!( + desc.suitable_servers_in_latency_window(&criteria).unwrap(), + Vec::<&ServerDescription>::new() + ); +} diff --git a/src/sdam/description/topology/server_selection/test/in_window.rs b/src/sdam/description/topology/server_selection/test/in_window.rs index aefb50aab..af7dc0ac1 100644 --- a/src/sdam/description/topology/server_selection/test/in_window.rs +++ b/src/sdam/description/topology/server_selection/test/in_window.rs @@ -1,33 +1,29 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; +use crate::bson::{doc, Document}; use approx::abs_diff_eq; -use bson::{doc, Document}; -use semver::VersionReq; use serde::Deserialize; -use tokio::sync::RwLockWriteGuard; use crate::{ - coll::options::FindOptions, + cmap::DEFAULT_MAX_POOL_SIZE, error::Result, event::cmap::CmapEvent, options::ServerAddress, - runtime, - runtime::AsyncJoinHandle, + runtime::{self, AsyncJoinHandle}, sdam::{description::topology::server_selection, Server}, selection_criteria::{ReadPreference, SelectionCriteria}, test::{ + auth_enabled, + block_connection_supported, + get_client_options, log_uncaptured, run_spec_test, + topology_is_sharded, + util::fail_point::{FailPoint, FailPointMode}, Event, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, + EventClient, }, - ServerInfo, + Client, }; use super::TestTopologyDescription; @@ -79,10 +75,14 @@ async fn run_test(test_file: TestFile) { .into(); for _ in 0..test_file.iterations { - let selection = - server_selection::attempt_to_select_server(&read_pref, &topology_description, &servers) - .expect("selection should not fail") - .expect("a server should have been selected"); + let selection = server_selection::attempt_to_select_server( + &read_pref, + &topology_description, + &servers, + None, + ) + .expect("selection should not fail") + .expect("a server should have been selected"); *tallies.entry(selection.address.clone()).or_insert(0) += 1; } @@ -107,70 +107,61 @@ async fn run_test(test_file: TestFile) { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn select_in_window() { run_spec_test(&["server-selection", "in_window"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn load_balancing_test() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let mut setup_client_options = CLIENT_OPTIONS.get().await.clone(); - - if setup_client_options.load_balanced.unwrap_or(false) { - log_uncaptured("skipping load_balancing_test test due to load-balanced topology"); + if !topology_is_sharded().await { + log_uncaptured("skipping load_balancing_test test due to topology not being sharded"); return; } - - if setup_client_options.credential.is_some() { + if get_client_options().await.hosts.len() != 2 { + log_uncaptured("skipping load_balancing_test test due to topology not having 2 mongoses"); + return; + } + if auth_enabled().await { log_uncaptured("skipping load_balancing_test test due to auth being enabled"); return; } - - setup_client_options.hosts.drain(1..); - setup_client_options.direct_connection = Some(true); - let setup_client = TestClient::with_options(Some(setup_client_options)).await; - - let version = VersionReq::parse(">= 4.2.9").unwrap(); - // blockConnection failpoint option only supported in 4.2.9+. - if !version.matches(&setup_client.server_version) { + if !block_connection_supported().await { log_uncaptured( "skipping load_balancing_test test due to server not supporting blockConnection option", ); return; } - if !setup_client.is_sharded() { - log_uncaptured("skipping load_balancing_test test due to topology not being sharded"); - return; - } + let mut setup_client_options = get_client_options().await.clone(); - if CLIENT_OPTIONS.get().await.hosts.len() != 2 { - log_uncaptured("skipping load_balancing_test test due to topology not having 2 mongoses"); - return; - } + setup_client_options.hosts.drain(1..); + setup_client_options.direct_connection = Some(true); + let setup_client = Client::for_test().options(setup_client_options).await; + + // clear the collection so subsequent test runs don't increase linearly in time + setup_client + .database("load_balancing_test") + .collection::("load_balancing_test") + .drop() + .await + .unwrap(); // seed the collection with a document so the find commands do some work setup_client .database("load_balancing_test") .collection("load_balancing_test") - .insert_one(doc! {}, None) + .insert_one(doc! {}) .await .unwrap(); /// min_share is the lower bound for the % of times the the less selected server /// was selected. max_share is the upper bound. - async fn do_test( - client: &TestClient, - handler: &mut EventHandler, - min_share: f64, - max_share: f64, - iterations: usize, - ) { - handler.clear_cached_events(); + async fn do_test(client: &EventClient, min_share: f64, max_share: f64, iterations: usize) { + { + let mut events = client.events.clone(); + events.clear_cached_events(); + } let mut handles: Vec>> = Vec::new(); for _ in 0..10 { @@ -179,7 +170,7 @@ async fn load_balancing_test() { .collection::("load_balancing_test"); handles.push(runtime::spawn(async move { for _ in 0..iterations { - collection.find_one(None, None).await?; + collection.find_one(doc! {}).await?; } Ok(()) })) @@ -190,7 +181,7 @@ async fn load_balancing_test() { } let mut tallies: HashMap = HashMap::new(); - for event in handler.get_command_started_events(&["find"]) { + for event in client.events.get_command_started_events(&["find"]) { *tallies.entry(event.connection.address.clone()).or_insert(0) += 1; } @@ -199,78 +190,62 @@ async fn load_balancing_test() { counts.sort(); let share_of_selections = (*counts[0] as f64) / ((*counts[0] + *counts[1]) as f64); - assert!( - share_of_selections <= max_share, - "expected no more than {}% of selections, instead got {}%", - (max_share * 100.0) as u32, - (share_of_selections * 100.0) as u32 - ); - assert!( - share_of_selections >= min_share, - "expected at least {}% of selections, instead got {}%", - (min_share * 100.0) as u32, - (share_of_selections * 100.0) as u32 - ); + #[allow(clippy::cast_possible_truncation)] + { + assert!( + share_of_selections <= max_share, + "expected no more than {}% of selections, instead got {}%", + (max_share * 100.0) as u32, + (share_of_selections * 100.0) as u32 + ); + assert!( + share_of_selections >= min_share, + "expected at least {}% of selections, instead got {}%", + (min_share * 100.0) as u32, + (share_of_selections * 100.0) as u32 + ); + } } - let mut handler = EventHandler::new(); - let mut subscriber = handler.subscribe(); - let mut options = CLIENT_OPTIONS.get().await.clone(); - let max_pool_size = 10; - let hosts = options.hosts.clone(); + let mut options = get_client_options().await.clone(); + let max_pool_size = DEFAULT_MAX_POOL_SIZE; options.local_threshold = Duration::from_secs(30).into(); - options.max_pool_size = Some(max_pool_size); options.min_pool_size = Some(max_pool_size); - let client = TestClient::with_handler(Some(Arc::new(handler.clone())), options).await; + let client = Client::for_test() + .options(options) + .monitor_events() + .retain_startup_events() + .await; + + let mut subscriber = client.events.stream_all(); // wait for both servers pools to be saturated. - for address in hosts { - let selector = Arc::new(move |sd: &ServerInfo| sd.address() == &address); - for _ in 0..max_pool_size { - let client = client.clone(); - let selector = selector.clone(); - runtime::execute(async move { - let options = FindOptions::builder() - .selection_criteria(SelectionCriteria::Predicate(selector)) - .build(); - client - .database("load_balancing_test") - .collection::("load_balancing_test") - .find(doc! { "$where": "sleep(500) && true" }, options) - .await - .unwrap(); - }); - } - } + client.warm_connection_pool().await; let mut conns = 0; while conns < max_pool_size * 2 { subscriber - .wait_for_event(Duration::from_secs(30), |event| { + .next_match(Duration::from_secs(30), |event| { matches!(event, Event::Cmap(CmapEvent::ConnectionReady(_))) }) .await .expect("timed out waiting for both pools to be saturated"); conns += 1; } - drop(subscriber); // enable a failpoint on one of the mongoses to slow it down - let options = FailCommandOptions::builder() + let slow_host = get_client_options().await.hosts[0].clone(); + let slow_host_criteria = + SelectionCriteria::Predicate(Arc::new(move |si| si.address() == &slow_host)); + let fail_point = FailPoint::fail_command(&["find"], FailPointMode::AlwaysOn) .block_connection(Duration::from_millis(500)) - .build(); - let failpoint = FailPoint::fail_command(&["find"], FailPointMode::AlwaysOn, options); - - let slow_host = CLIENT_OPTIONS.get().await.hosts[0].clone(); - let criteria = SelectionCriteria::Predicate(Arc::new(move |si| si.address() == &slow_host)); - let fp_guard = setup_client - .enable_failpoint(failpoint, criteria) - .await - .expect("enabling failpoint should succeed"); + .selection_criteria(slow_host_criteria); + let guard = setup_client.enable_fail_point(fail_point).await.unwrap(); // verify that the lesser picked server (slower one) was picked less than 25% of the time. - do_test(&client, &mut handler, 0.05, 0.25, 10).await; + const FLUFF: f64 = 0.02; // See RUST-2044. + do_test(&client, 0.05, 0.25 + FLUFF, 10).await; // disable failpoint and rerun, should be back to even split - drop(fp_guard); - do_test(&client, &mut handler, 0.40, 0.50, 100).await; + drop(guard); + do_test(&client, 0.40, 0.50, 100).await; } diff --git a/src/sdam/description/topology/server_selection/test/logic.rs b/src/sdam/description/topology/server_selection/test/logic.rs index 36e3e4485..cd364681f 100644 --- a/src/sdam/description/topology/server_selection/test/logic.rs +++ b/src/sdam/description/topology/server_selection/test/logic.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ error::{Error, Result}, - selection_criteria::{ReadPreference, ReadPreferenceOptions, TagSet}, + options::{ReadPreference, ReadPreferenceOptions, TagSet}, test::run_spec_test, }; @@ -28,12 +28,13 @@ struct TestFile { _operation: Option, } +// Deserialize into a helper struct to avoid deserialization errors for invalid read preferences. #[derive(Debug, Deserialize, Serialize)] -pub struct TestReadPreference { - pub mode: Option, - pub tag_sets: Option>, +struct TestReadPreference { + mode: Option, + tag_sets: Option>, #[serde(rename = "maxStalenessSeconds")] - pub max_staleness_seconds: Option, + max_staleness_seconds: Option, } impl TryFrom for ReadPreference { @@ -57,10 +58,18 @@ impl TryFrom for ReadPreference { } ReadPreference::Primary } - Some("Secondary") => ReadPreference::Secondary { options }, - Some("PrimaryPreferred") => ReadPreference::PrimaryPreferred { options }, - Some("SecondaryPreferred") => ReadPreference::SecondaryPreferred { options }, - Some("Nearest") => ReadPreference::Nearest { options }, + Some("Secondary") => ReadPreference::Secondary { + options: Some(options), + }, + Some("PrimaryPreferred") => ReadPreference::PrimaryPreferred { + options: Some(options), + }, + Some("SecondaryPreferred") => ReadPreference::SecondaryPreferred { + options: Some(options), + }, + Some("Nearest") => ReadPreference::Nearest { + options: Some(options), + }, Some(m) => { return Err(Error::invalid_argument( format!("invalid read preference mode: {}", m).as_str(), @@ -103,60 +112,54 @@ async fn run_test(test_file: TestFile) { ); } } else if test_file.error == Some(true) { - // skip on sync to avoid compilation conflicts with the sync version of - // ClientOptions::parse. - #[cfg(not(any(feature = "sync", feature = "tokio-sync")))] - { - use crate::{ - client::options::ClientOptions, - selection_criteria::SelectionCriteria, - Client, - }; - - let mut options = Vec::new(); - if let Some(ref mode) = test_file.read_preference.mode { - options.push(format!("readPreference={}", mode)); - } - if let Some(max_staleness_seconds) = test_file.read_preference.max_staleness_seconds { - options.push(format!("maxStalenessSeconds={}", max_staleness_seconds)); - } - if let Some(heartbeat_freq) = test_file.heartbeat_frequency_ms { - options.push(format!("heartbeatFrequencyMS={}", heartbeat_freq)); - } + use crate::{ + client::options::ClientOptions, + selection_criteria::SelectionCriteria, + Client, + }; - let uri_str = format!("mongodb://localhost:27017/?{}", options.join("&")); - ClientOptions::parse(uri_str) - .await - .err() - .unwrap_or_else(|| { - panic!( - "expected client construction to fail with read preference {:#?}", - test_file.read_preference - ) - }); + let mut uri_options = Vec::new(); + if let Some(ref mode) = test_file.read_preference.mode { + uri_options.push(format!("readPreference={}", mode)); + } + if let Some(max_staleness_seconds) = test_file.read_preference.max_staleness_seconds { + uri_options.push(format!("maxStalenessSeconds={}", max_staleness_seconds)); + } + if let Some(heartbeat_freq) = test_file.heartbeat_frequency_ms { + uri_options.push(format!("heartbeatFrequencyMS={}", heartbeat_freq)); + } - // if the options contain a read preference that is supported by the type system, ensure - // that it still can't be used to construct a client. - if let Ok(rp) = test_file.read_preference.try_into() { - let mut opts = ClientOptions::builder() - .selection_criteria(SelectionCriteria::ReadPreference(rp)) - .build(); - if let Some(heartbeat_freq) = test_file.heartbeat_frequency_ms { - opts.heartbeat_freq = Some(Duration::from_secs(heartbeat_freq)); - } - Client::with_options(opts.clone()).err().unwrap_or_else(|| { - panic!( - "expected client construction to fail with options: {:#?}", - opts - ) - }); + let uri_str = format!("mongodb://localhost:27017/?{}", uri_options.join("&")); + ClientOptions::parse(&uri_str) + .await + .err() + .unwrap_or_else(|| { + panic!( + "expected client construction to fail with read preference {:#?}", + test_file.read_preference + ) + }); + + // if the options contain a read preference that is supported by the type system, ensure + // that it still can't be used to construct a client. + if let Ok(rp) = test_file.read_preference.try_into() { + let mut opts = ClientOptions::builder() + .selection_criteria(SelectionCriteria::ReadPreference(rp)) + .build(); + if let Some(heartbeat_freq) = test_file.heartbeat_frequency_ms { + opts.heartbeat_freq = Some(Duration::from_secs(heartbeat_freq)); } + Client::with_options(opts.clone()).err().unwrap_or_else(|| { + panic!( + "expected client construction to fail with options: {:#?}", + opts + ) + }); } } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_replica_set_no_primary() { run_spec_test( &[ @@ -170,8 +173,7 @@ async fn server_selection_replica_set_no_primary() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_replica_set_with_primary() { run_spec_test( &[ @@ -185,8 +187,7 @@ async fn server_selection_replica_set_with_primary() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_sharded() { run_spec_test( &["server-selection", "server_selection", "Sharded", "read"], @@ -195,8 +196,7 @@ async fn server_selection_sharded() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_single() { run_spec_test( &["server-selection", "server_selection", "Single", "read"], @@ -205,8 +205,7 @@ async fn server_selection_single() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_unknown() { run_spec_test( &["server-selection", "server_selection", "Unknown", "read"], @@ -215,8 +214,7 @@ async fn server_selection_unknown() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_load_balanced() { run_spec_test( &[ @@ -230,32 +228,27 @@ async fn server_selection_load_balanced() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn max_staleness_replica_set_no_primary() { run_spec_test(&["max-staleness", "ReplicaSetNoPrimary"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn max_staleness_replica_set_with_primary() { run_spec_test(&["max-staleness", "ReplicaSetWithPrimary"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn max_staleness_sharded() { run_spec_test(&["max-staleness", "Sharded"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn max_staleness_single() { run_spec_test(&["max-staleness", "Single"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn max_staleness_unknown() { run_spec_test(&["max-staleness", "Unknown"], run_test).await; } diff --git a/src/sdam/description/topology/server_selection/test/mod.rs b/src/sdam/description/topology/server_selection/test/mod.rs deleted file mode 100644 index 0d8785ef6..000000000 --- a/src/sdam/description/topology/server_selection/test/mod.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::{collections::HashMap, sync::Arc, time::Duration}; - -use serde::Deserialize; - -use crate::{ - bson::{doc, DateTime}, - hello::{HelloCommandResponse, HelloReply, LastWrite}, - options::ServerAddress, - sdam::{ - description::topology::{test::f64_ms_as_duration, TopologyType}, - ServerDescription, - ServerType, - TopologyDescription, - }, - selection_criteria::{SelectionCriteria, TagSet}, -}; - -mod in_window; -mod logic; - -#[derive(Debug, Deserialize)] -struct TestTopologyDescription { - #[serde(rename = "type")] - topology_type: TopologyType, - servers: Vec, -} - -impl TestTopologyDescription { - fn into_topology_description( - self, - heartbeat_frequency: Option, - ) -> TopologyDescription { - let servers: HashMap = self - .servers - .into_iter() - .filter_map(|sd| { - sd.into_server_description() - .map(|sd| (sd.address.clone(), sd)) - }) - .collect(); - - TopologyDescription { - single_seed: servers.len() == 1, - topology_type: self.topology_type, - set_name: None, - max_set_version: None, - max_election_id: None, - compatibility_error: None, - logical_session_timeout: None, - transaction_support_status: Default::default(), - cluster_time: None, - local_threshold: None, - heartbeat_freq: heartbeat_frequency, - servers, - } - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -struct TestServerDescription { - address: String, - #[serde(rename = "avg_rtt_ms")] - avg_rtt_ms: Option, - #[serde(rename = "type")] - server_type: TestServerType, - tags: Option, - last_update_time: Option, - last_write: Option, - // We don't need to use this field, but it needs to be included during deserialization so that - // we can use the deny_unknown_fields tag. - _max_wire_version: Option, -} - -impl TestServerDescription { - fn into_server_description(self) -> Option { - let server_type = match self.server_type.into_server_type() { - Some(server_type) => server_type, - None => return None, - }; - - let server_address = ServerAddress::parse(self.address).ok()?; - let tags = self.tags; - let last_write = self.last_write; - let avg_rtt_ms = self.avg_rtt_ms; - let reply = hello_response_from_server_type(server_type).map(|mut command_response| { - command_response.tags = tags; - command_response.last_write = last_write.map(|last_write| LastWrite { - last_write_date: DateTime::from_millis(last_write.last_write_date), - }); - HelloReply { - server_address: server_address.clone(), - command_response, - cluster_time: None, - raw_command_response: Default::default(), - } - }); - - let mut server_desc = match reply { - Some(reply) => ServerDescription::new_from_hello_reply( - server_address, - reply, - avg_rtt_ms.map(f64_ms_as_duration).unwrap(), - ), - None => ServerDescription::new(server_address), - }; - server_desc.last_update_time = self - .last_update_time - .map(|i| DateTime::from_millis(i.into())); - - Some(server_desc) - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct LastWriteDate { - last_write_date: i64, -} - -#[derive(Clone, Copy, Debug, Deserialize)] -enum TestServerType { - Standalone, - Mongos, - #[serde(rename = "RSPrimary")] - RsPrimary, - #[serde(rename = "RSSecondary")] - RsSecondary, - #[serde(rename = "RSArbiter")] - RsArbiter, - #[serde(rename = "RSOther")] - RsOther, - #[serde(rename = "RSGhost")] - RsGhost, - LoadBalancer, - Unknown, - PossiblePrimary, -} - -impl TestServerType { - fn into_server_type(self) -> Option { - match self { - TestServerType::Standalone => Some(ServerType::Standalone), - TestServerType::Mongos => Some(ServerType::Mongos), - TestServerType::RsPrimary => Some(ServerType::RsPrimary), - TestServerType::RsSecondary => Some(ServerType::RsSecondary), - TestServerType::RsArbiter => Some(ServerType::RsArbiter), - TestServerType::RsOther => Some(ServerType::RsOther), - TestServerType::RsGhost => Some(ServerType::RsGhost), - TestServerType::LoadBalancer => Some(ServerType::LoadBalancer), - TestServerType::Unknown => Some(ServerType::Unknown), - TestServerType::PossiblePrimary => None, - } - } -} - -fn hello_response_from_server_type(server_type: ServerType) -> Option { - let mut response = HelloCommandResponse::default(); - - match server_type { - ServerType::Unknown => { - return None; - } - ServerType::Mongos => { - response.msg = Some("isdbgrid".into()); - } - ServerType::RsPrimary => { - response.set_name = Some("foo".into()); - response.is_writable_primary = Some(true); - } - ServerType::RsOther => { - response.set_name = Some("foo".into()); - response.hidden = Some(true); - } - ServerType::RsSecondary => { - response.set_name = Some("foo".into()); - response.secondary = Some(true); - } - ServerType::RsArbiter => { - response.set_name = Some("foo".into()); - response.arbiter_only = Some(true); - } - ServerType::RsGhost => { - response.is_replica_set = Some(true); - } - ServerType::Standalone | ServerType::LoadBalancer => {} - }; - - Some(response) -} - -#[test] -fn predicate_omits_unavailable() { - let criteria = SelectionCriteria::Predicate(Arc::new(|si| { - !matches!(si.server_type(), ServerType::RsPrimary) - })); - - let desc = TestTopologyDescription { - topology_type: TopologyType::ReplicaSetWithPrimary, - servers: vec![ - TestServerDescription { - address: "localhost:27017".to_string(), - avg_rtt_ms: Some(12.0), - server_type: TestServerType::RsPrimary, - tags: None, - last_update_time: None, - last_write: None, - _max_wire_version: None, - }, - TestServerDescription { - address: "localhost:27018".to_string(), - avg_rtt_ms: Some(12.0), - server_type: TestServerType::Unknown, - tags: None, - last_update_time: None, - last_write: None, - _max_wire_version: None, - }, - TestServerDescription { - address: "localhost:27019".to_string(), - avg_rtt_ms: Some(12.0), - server_type: TestServerType::RsArbiter, - tags: None, - last_update_time: None, - last_write: None, - _max_wire_version: None, - }, - TestServerDescription { - address: "localhost:27020".to_string(), - avg_rtt_ms: Some(12.0), - server_type: TestServerType::RsGhost, - tags: None, - last_update_time: None, - last_write: None, - _max_wire_version: None, - }, - TestServerDescription { - address: "localhost:27021".to_string(), - avg_rtt_ms: Some(12.0), - server_type: TestServerType::RsOther, - tags: None, - last_update_time: None, - last_write: None, - _max_wire_version: None, - }, - ], - } - .into_topology_description(None); - pretty_assertions::assert_eq!( - desc.suitable_servers_in_latency_window(&criteria).unwrap(), - Vec::<&ServerDescription>::new() - ); -} diff --git a/src/sdam/description/topology/test.rs b/src/sdam/description/topology/test.rs new file mode 100644 index 000000000..9a4109177 --- /dev/null +++ b/src/sdam/description/topology/test.rs @@ -0,0 +1,12 @@ +mod event; +mod rtt; +mod sdam; + +use std::time::Duration; + +pub use event::TestSdamEvent; + +#[allow(clippy::cast_possible_truncation)] +pub(crate) fn f64_ms_as_duration(f: f64) -> Duration { + Duration::from_micros((f * 1000.0) as u64) +} diff --git a/src/sdam/description/topology/test/event.rs b/src/sdam/description/topology/test/event.rs index 8ade3494a..7f87ceda2 100644 --- a/src/sdam/description/topology/test/event.rs +++ b/src/sdam/description/topology/test/event.rs @@ -3,6 +3,7 @@ use serde::Deserialize; use crate::{ client::options::ServerAddress, event::sdam::{ + SdamEvent, ServerClosedEvent, ServerDescriptionChangedEvent, ServerOpeningEvent, @@ -11,7 +12,6 @@ use crate::{ TopologyOpeningEvent, }, sdam::{ServerDescription, ServerType, TopologyDescription}, - test::SdamEvent, }; #[derive(Debug, Deserialize)] diff --git a/src/sdam/description/topology/test/mod.rs b/src/sdam/description/topology/test/mod.rs deleted file mode 100644 index 235b79841..000000000 --- a/src/sdam/description/topology/test/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod event; -mod rtt; -mod sdam; - -use std::time::Duration; - -pub use event::TestSdamEvent; - -pub(crate) fn f64_ms_as_duration(f: f64) -> Duration { - Duration::from_micros((f * 1000.0) as u64) -} diff --git a/src/sdam/description/topology/test/rtt.rs b/src/sdam/description/topology/test/rtt.rs index 37e354d39..f23397917 100644 --- a/src/sdam/description/topology/test/rtt.rs +++ b/src/sdam/description/topology/test/rtt.rs @@ -38,8 +38,7 @@ async fn run_test(test_file: TestFile) { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_rtt() { run_spec_test(&["server-selection", "rtt"], run_test).await; } diff --git a/src/sdam/description/topology/test/sdam.rs b/src/sdam/description/topology/test/sdam.rs index 12e59c538..5154e24e2 100644 --- a/src/sdam/description/topology/test/sdam.rs +++ b/src/sdam/description/topology/test/sdam.rs @@ -1,8 +1,7 @@ -use std::{borrow::Borrow, collections::HashMap, sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use bson::Document; +use crate::bson::Document; use serde::Deserialize; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use super::TestSdamEvent; @@ -10,7 +9,8 @@ use crate::{ bson::{doc, oid::ObjectId}, client::Client, cmap::{conn::ConnectionGeneration, PoolGeneration}, - error::{BulkWriteFailure, CommandError, Error, ErrorKind}, + error::{CommandError, Error, ErrorKind, InsertManyError}, + event::sdam::SdamEvent, hello::{HelloCommandResponse, HelloReply, LastWrite, LEGACY_HELLO_COMMAND_NAME}, options::{ClientOptions, ReadPreference, SelectionCriteria, ServerAddress}, sdam::{ @@ -25,18 +25,17 @@ use crate::{ }, selection_criteria::TagSet, test::{ + fail_command_appname_initial_handshake_supported, + get_client_options, log_uncaptured, run_spec_test, + topology_is_load_balanced, + topology_is_replica_set, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, Event, - EventClient, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - SdamEvent, - TestClient, - CLIENT_OPTIONS, - LOCK, }, }; @@ -178,14 +177,14 @@ pub enum ErrorType { #[serde(untagged)] pub enum ServerError { CommandError(CommandError), - WriteError(BulkWriteFailure), + WriteError(InsertManyError), } impl From for Error { fn from(server_error: ServerError) -> Self { match server_error { ServerError::CommandError(command_error) => ErrorKind::Command(command_error).into(), - ServerError::WriteError(bwf) => ErrorKind::BulkWrite(bwf).into(), + ServerError::WriteError(bwf) => ErrorKind::InsertMany(bwf).into(), } } } @@ -272,15 +271,15 @@ async fn run_test(test_file: TestFile) { log_uncaptured(format!("Executing {}", test_description)); - let mut options = ClientOptions::parse_uri(&test_file.uri, None) + let mut options = ClientOptions::parse(&test_file.uri) .await .expect(test_description); - let handler = Arc::new(EventHandler::new()); - options.sdam_event_handler = Some(handler.clone()); + let buffer = EventBuffer::new(); + options.sdam_event_handler = Some(buffer.handler()); options.test_options_mut().disable_monitoring_threads = true; - let mut event_subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); let mut topology = Topology::new(options.clone()).unwrap(); for (i, phase) in test_file.phases.into_iter().enumerate() { @@ -377,8 +376,8 @@ async fn run_test(test_file: TestFile) { ); } Outcome::Events(EventsOutcome { events: expected }) => { - let actual = event_subscriber - .collect_events(Duration::from_millis(500), |e| matches!(e, Event::Sdam(_))) + let actual = event_stream + .collect(Duration::from_millis(500), |e| matches!(e, Event::Sdam(_))) .await .into_iter() .map(|e| e.unwrap_sdam_event()); @@ -555,38 +554,32 @@ fn verify_description_outcome( } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn single() { run_spec_test(&["server-discovery-and-monitoring", "single"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn rs() { run_spec_test(&["server-discovery-and-monitoring", "rs"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn sharded() { run_spec_test(&["server-discovery-and-monitoring", "sharded"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn errors() { run_spec_test(&["server-discovery-and-monitoring", "errors"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn monitoring() { run_spec_test(&["server-discovery-and-monitoring", "monitoring"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn load_balanced() { run_spec_test( &["server-discovery-and-monitoring", "load-balanced"], @@ -595,32 +588,28 @@ async fn load_balanced() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn topology_closed_event_last() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let event_handler = EventHandler::new(); - let mut subscriber = event_handler.subscribe(); - let client = EventClient::with_additional_options( - None, - Some(Duration::from_millis(50)), - None, - event_handler.clone(), - ) - .await; + let client = Client::for_test() + .use_single_mongos() + .min_heartbeat_freq(Duration::from_millis(50)) + .monitor_events() + .await; + let events = client.events.clone(); + + let mut subscriber = events.stream_all(); client .database(function_name!()) .collection(function_name!()) - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); drop(client); subscriber - .wait_for_event(Duration::from_millis(1000), |event| { + .next_match(Duration::from_millis(1000), |event| { matches!(event, Event::Sdam(SdamEvent::TopologyClosed(_))) }) .await @@ -628,7 +617,7 @@ async fn topology_closed_event_last() { // no further SDAM events should be emitted after the TopologyClosedEvent let event = subscriber - .wait_for_event(Duration::from_millis(1000), |event| { + .next_match(Duration::from_millis(1000), |event| { matches!(event, Event::Sdam(_)) }) .await; @@ -639,88 +628,75 @@ async fn topology_closed_event_last() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn heartbeat_events() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; + if topology_is_load_balanced().await { + log_uncaptured("skipping heartbeat_events tests due to load-balanced topology"); + return; + } - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.hosts.drain(1..); options.heartbeat_freq = Some(Duration::from_millis(50)); options.app_name = "heartbeat_events".to_string().into(); - let event_handler = EventHandler::new(); - let mut subscriber = event_handler.subscribe(); + let client = Client::for_test() + .options(options.clone()) + .use_single_mongos() + .min_heartbeat_freq(Duration::from_millis(50)) + .monitor_events() + .await; - let client = EventClient::with_additional_options( - Some(options.clone()), - Some(Duration::from_millis(50)), - None, - event_handler.clone(), - ) - .await; - - if client.is_load_balanced() { - log_uncaptured("skipping heartbeat_events tests due to load-balanced topology"); - return; - } + let mut subscriber = client.events.stream_all(); subscriber - .wait_for_event(Duration::from_millis(500), |event| { + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Sdam(SdamEvent::ServerHeartbeatStarted(_))) }) .await .expect("should see server heartbeat started event"); subscriber - .wait_for_event(Duration::from_millis(500), |event| { + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(_))) }) .await .expect("should see server heartbeat succeeded event"); - if !client.supports_fail_command_appname_initial_handshake() { + if !fail_command_appname_initial_handshake_supported().await { return; } options.app_name = None; options.heartbeat_freq = None; - let fp_client = TestClient::with_options(Some(options)).await; + let fp_client = Client::for_test().options(options).await; - let fp_options = FailCommandOptions::builder() - .error_code(1234) - .app_name("heartbeat_events".to_string()) - .build(); - let failpoint = FailPoint::fail_command( + let fail_point = FailPoint::fail_command( &[LEGACY_HELLO_COMMAND_NAME, "hello"], FailPointMode::AlwaysOn, - fp_options, - ); - let _fp_guard = fp_client - .enable_failpoint(failpoint, None) - .await - .expect("enabling failpoint should succeed"); + ) + .app_name("heartbeat_events") + .error_code(1234); + let _guard = fp_client.enable_fail_point(fail_point).await.unwrap(); subscriber - .wait_for_event(Duration::from_millis(500), |event| { + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Sdam(SdamEvent::ServerHeartbeatFailed(_))) }) .await .expect("should see server heartbeat failed event"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn direct_connection() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let test_client = TestClient::new().await; - if !test_client.is_replica_set() { + if !topology_is_replica_set().await { log_uncaptured("Skipping direct_connection test due to non-replica set topology"); return; } + let test_client = Client::for_test().await; + let criteria = SelectionCriteria::ReadPreference(ReadPreference::Secondary { options: Default::default(), }); @@ -729,7 +705,7 @@ async fn direct_connection() { .await .expect("failed to select secondary"); - let mut secondary_options = CLIENT_OPTIONS.get().await.clone(); + let mut secondary_options = get_client_options().await.clone(); secondary_options.hosts = vec![secondary_address]; let mut direct_false_options = secondary_options.clone(); @@ -739,7 +715,7 @@ async fn direct_connection() { direct_false_client .database(function_name!()) .collection(function_name!()) - .insert_one(doc! {}, None) + .insert_one(doc! {}) .await .expect("write should succeed with directConnection=false on secondary"); @@ -750,7 +726,7 @@ async fn direct_connection() { let error = direct_true_client .database(function_name!()) .collection(function_name!()) - .insert_one(doc! {}, None) + .insert_one(doc! {}) .await .expect_err("write should fail with directConnection=true on secondary"); assert!(error.is_notwritableprimary()); @@ -760,13 +736,12 @@ async fn direct_connection() { client .database(function_name!()) .collection(function_name!()) - .insert_one(doc! {}, None) + .insert_one(doc! {}) .await .expect("write should succeed with directConnection unspecified"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn pool_cleared_error_does_not_mark_unknown() { let address = ServerAddress::parse("a:1234").unwrap(); let mut options = ClientOptions::builder() @@ -779,16 +754,17 @@ async fn pool_cleared_error_does_not_mark_unknown() { // get the one server in the topology let server = topology.servers().into_values().next().unwrap(); - let heartbeat_response: HelloCommandResponse = bson::from_document(doc! { - "ok": 1, - "isWritablePrimary": true, - "minWireVersion": 0, - "maxWireVersion": 6, - "maxBsonObjectSize": 16_000, - "maxWriteBatchSize": 10_000, - "maxMessageSizeBytes": 48_000_000, - }) - .unwrap(); + let heartbeat_response: HelloCommandResponse = + crate::bson_compat::deserialize_from_document(doc! { + "ok": 1, + "isWritablePrimary": true, + "minWireVersion": 0, + "maxWireVersion": 6, + "maxBsonObjectSize": 16_000, + "maxWriteBatchSize": 10_000, + "maxMessageSizeBytes": 48_000_000, + }) + .unwrap(); // discover the node topology @@ -807,7 +783,6 @@ async fn pool_cleared_error_does_not_mark_unknown() { assert_eq!( topology .watch() - .borrow() .server_description(&address) .unwrap() .server_type, diff --git a/src/sdam/mod.rs b/src/sdam/mod.rs deleted file mode 100644 index 17231c0ae..000000000 --- a/src/sdam/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -mod description; -mod monitor; -pub mod public; -mod server; -mod srv_polling; -#[cfg(test)] -mod test; -mod topology; - -pub use self::public::{ServerInfo, ServerType, TopologyType}; - -pub(crate) use self::{ - description::{ - server::{ServerDescription, TopologyVersion}, - topology::{ - server_selection::{self, SelectedServer}, - verify_max_staleness, - TopologyDescription, - TransactionSupportStatus, - }, - }, - monitor::{Monitor, DEFAULT_HEARTBEAT_FREQUENCY, MIN_HEARTBEAT_FREQUENCY}, - server::Server, - topology::{HandshakePhase, Topology, TopologyUpdater, TopologyWatcher}, -}; - -#[cfg(test)] -pub(crate) use topology::UpdateMessage; diff --git a/src/sdam/monitor.rs b/src/sdam/monitor.rs index 69357f4b7..360fcafc7 100644 --- a/src/sdam/monitor.rs +++ b/src/sdam/monitor.rs @@ -1,9 +1,12 @@ use std::{ - sync::Arc, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, + }, time::{Duration, Instant}, }; -use bson::doc; +use crate::bson::doc; use tokio::sync::watch; use super::{ @@ -13,6 +16,7 @@ use super::{ TopologyWatcher, }; use crate::{ + client::options::ServerMonitoringMode, cmap::{establish::ConnectionEstablisher, Connection}, error::{Error, Result}, event::sdam::{ @@ -26,6 +30,12 @@ use crate::{ runtime::{self, stream::DEFAULT_CONNECT_TIMEOUT, WorkerHandle, WorkerHandleListener}, }; +fn next_monitoring_connection_id() -> u32 { + static MONITORING_CONNECTION_ID: AtomicU32 = AtomicU32::new(0); + + MONITORING_CONNECTION_ID.fetch_add(1, Ordering::SeqCst) +} + pub(crate) const DEFAULT_HEARTBEAT_FREQUENCY: Duration = Duration::from_secs(10); pub(crate) const MIN_HEARTBEAT_FREQUENCY: Duration = Duration::from_millis(500); @@ -39,11 +49,15 @@ pub(crate) struct Monitor { sdam_event_emitter: Option, client_options: ClientOptions, + /// Whether this monitor is allowed to use the streaming protocol. + allow_streaming: bool, + /// The most recent topology version returned by the server in a hello response. - /// If some, indicates that this monitor should use the streaming protocol. If none, it should - /// use the polling protocol. topology_version: Option, + /// The RTT monitor; once it's started this is None. + pending_rtt_monitor: Option, + /// Handle to the RTT monitor, used to get the latest known round trip time for a given server /// and to reset the RTT when the monitor disconnects from the server. rtt_monitor_handle: RttMonitorHandle, @@ -70,6 +84,15 @@ impl Monitor { connection_establisher.clone(), client_options.clone(), ); + let allow_streaming = match client_options + .server_monitoring_mode + .clone() + .unwrap_or(ServerMonitoringMode::Auto) + { + ServerMonitoringMode::Stream => true, + ServerMonitoringMode::Poll => false, + ServerMonitoringMode::Auto => !crate::cmap::is_faas(), + }; let monitor = Self { address, client_options, @@ -77,14 +100,15 @@ impl Monitor { topology_updater, topology_watcher, sdam_event_emitter, + pending_rtt_monitor: Some(rtt_monitor), rtt_monitor_handle, request_receiver: manager_receiver, connection: None, + allow_streaming, topology_version: None, }; - runtime::execute(monitor.execute()); - runtime::execute(rtt_monitor.execute()); + runtime::spawn(monitor.execute()); } async fn execute(mut self) { @@ -93,13 +117,19 @@ impl Monitor { while self.is_alive() { let check_succeeded = self.check_server().await; + if self.topology_version.is_some() && self.allow_streaming { + if let Some(rtt_monitor) = self.pending_rtt_monitor.take() { + runtime::spawn(rtt_monitor.execute()); + } + } + // In the streaming protocol, we read from the socket continuously // rather than polling at specific intervals, unless the most recent check // failed. // // We only go to sleep when using the polling protocol (i.e. server never returned a // topologyVersion) or when the most recent check failed. - if self.topology_version.is_none() || !check_succeeded { + if self.topology_version.is_none() || !check_succeeded || !self.allow_streaming { self.request_receiver .wait_for_check_request( self.client_options.min_heartbeat_frequency(), @@ -162,10 +192,18 @@ impl Monitor { } async fn perform_hello(&mut self) -> HelloResult { + let driver_connection_id = self + .connection + .as_ref() + .map(|c| c.id) + .unwrap_or(next_monitoring_connection_id()); + self.emit_event(|| { SdamEvent::ServerHeartbeatStarted(ServerHeartbeatStartedEvent { server_address: self.address.clone(), - awaited: self.topology_version.is_some(), + awaited: self.topology_version.is_some() && self.allow_streaming, + driver_connection_id, + server_connection_id: self.connection.as_ref().and_then(|c| c.server_id), }) }); @@ -196,10 +234,14 @@ impl Monitor { } else { // If the initial handshake returned a topology version, send it back to the // server to begin streaming responses. - let opts = self.topology_version.map(|tv| AwaitableHelloOptions { - topology_version: tv, - max_await_time: heartbeat_frequency, - }); + let opts = if self.allow_streaming { + self.topology_version.map(|tv| AwaitableHelloOptions { + topology_version: tv, + max_await_time: heartbeat_frequency, + }) + } else { + None + }; let command = hello_command( self.client_options.server_api.as_ref(), @@ -208,14 +250,14 @@ impl Monitor { opts, ); - run_hello(conn, command).await + run_hello(conn, command, None).await } } None => { let start = Instant::now(); let res = self .connection_establisher - .establish_monitoring_connection(self.address.clone()) + .establish_monitoring_connection(self.address.clone(), driver_connection_id) .await; match res { Ok((conn, hello_reply)) => { @@ -233,7 +275,11 @@ impl Monitor { let start = Instant::now(); let result = tokio::select! { result = execute_hello => match result { - Ok(reply) => HelloResult::Ok(reply), + Ok(mut reply) => { + // Do not propagate server reported cluster time for monitoring hello responses. + reply.cluster_time = None; + HelloResult::Ok(reply) + }, Err(e) => HelloResult::Err(e) }, r = self.request_receiver.wait_for_cancellation() => { @@ -243,14 +289,18 @@ impl Monitor { }; HelloResult::Cancelled { reason: reason_error } } - _ = runtime::delay_for(timeout) => { + _ = tokio::time::sleep(timeout) => { HelloResult::Err(Error::network_timeout()) } }; let duration = start.elapsed(); + let awaited = self.topology_version.is_some() && self.allow_streaming; match result { HelloResult::Ok(ref r) => { + if !awaited { + self.rtt_monitor_handle.add_sample(duration); + } self.emit_event(|| { let mut reply = r .raw_command_response @@ -263,7 +313,9 @@ impl Monitor { duration, reply, server_address: self.address.clone(), - awaited: self.topology_version.is_some(), + awaited, + driver_connection_id, + server_connection_id: self.connection.as_ref().and_then(|c| c.server_id), }) }); @@ -272,18 +324,21 @@ impl Monitor { self.topology_version = r.command_response.topology_version; } HelloResult::Err(ref e) | HelloResult::Cancelled { reason: ref e } => { - // Per the spec, cancelled requests and errors both require the monitoring - // connection to be closed. - self.connection = None; - self.rtt_monitor_handle.reset_average_rtt(); self.emit_event(|| { SdamEvent::ServerHeartbeatFailed(ServerHeartbeatFailedEvent { duration, failure: e.clone(), server_address: self.address.clone(), - awaited: self.topology_version.is_some(), + awaited, + driver_connection_id, + server_connection_id: self.connection.as_ref().and_then(|c| c.server_id), }) }); + + // Per the spec, cancelled requests and errors both require the monitoring + // connection to be closed. + self.connection = None; + self.rtt_monitor_handle.reset_average_rtt(); self.topology_version.take(); } } @@ -397,12 +452,15 @@ impl RttMonitor { Some(conn.stream_description()?.hello_ok), None, ); - conn.send_command(command, None).await?; + conn.send_message(command).await?; } None => { let connection = self .connection_establisher - .establish_monitoring_connection(self.address.clone()) + .establish_monitoring_connection( + self.address.clone(), + next_monitoring_connection_id(), + ) .await? .0; self.connection = Some(connection); @@ -414,7 +472,7 @@ impl RttMonitor { let start = Instant::now(); let check_succeded = tokio::select! { r = perform_check => r.is_ok(), - _ = runtime::delay_for(timeout) => { + _ = tokio::time::sleep(timeout) => { false } }; @@ -431,7 +489,7 @@ impl RttMonitor { // responsible for resetting the average RTT." } - runtime::delay_for( + tokio::time::sleep( self.client_options .heartbeat_freq .unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY), @@ -592,26 +650,18 @@ impl MonitorRequestReceiver { async fn wait_for_check_request(&mut self, delay: Duration, timeout: Duration) { let _ = runtime::timeout(timeout, async { let wait_for_check_request = async { - runtime::delay_for(delay).await; + tokio::time::sleep(delay).await; self.topology_check_request_receiver .wait_for_check_request() .await; }; tokio::pin!(wait_for_check_request); - loop { - tokio::select! { - _ = self.individual_check_request_receiver.changed() => { - break; - } - _ = &mut wait_for_check_request => { - break; - } - _ = self.handle_listener.wait_for_all_handle_drops() => { - // Don't continue waiting after server has been removed from the topology. - break; - } - } + tokio::select! { + _ = self.individual_check_request_receiver.changed() => (), + _ = &mut wait_for_check_request => (), + // Don't continue waiting after server has been removed from the topology. + _ = self.handle_listener.wait_for_all_handle_drops() => (), } }) .await; diff --git a/src/sdam/public.rs b/src/sdam/public.rs index d25f0008f..8f03f9d4d 100644 --- a/src/sdam/public.rs +++ b/src/sdam/public.rs @@ -13,13 +13,13 @@ use crate::{ }; /// A description of the most up-to-date information known about a server. Further details can be -/// found in the [Server Discovery and Monitoring specification](https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst). +/// found in the [Server Discovery and Monitoring specification](https://specifications.readthedocs.io/en/latest/server-discovery-and-monitoring/server-discovery-and-monitoring/). #[derive(Clone)] pub struct ServerInfo<'a> { pub(crate) description: Cow<'a, ServerDescription>, } -impl<'a> Serialize for ServerInfo<'a> { +impl Serialize for ServerInfo<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -112,7 +112,7 @@ impl<'a> ServerInfo<'a> { } } -impl<'a> fmt::Debug for ServerInfo<'a> { +impl fmt::Debug for ServerInfo<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { match self.description.reply { Ok(_) => f @@ -141,7 +141,7 @@ impl<'a> fmt::Debug for ServerInfo<'a> { } } -impl<'a> fmt::Display for ServerInfo<'a> { +impl fmt::Display for ServerInfo<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> { write!( f, diff --git a/src/sdam/server.rs b/src/sdam/server.rs index ba6315a2c..a956716b7 100644 --- a/src/sdam/server.rs +++ b/src/sdam/server.rs @@ -37,7 +37,7 @@ impl Server { options: ClientOptions, connection_establisher: ConnectionEstablisher, topology_updater: TopologyUpdater, - topology_id: bson::oid::ObjectId, + topology_id: crate::bson::oid::ObjectId, ) -> Arc { Arc::new(Self { pool: ConnectionPool::new( diff --git a/src/sdam/srv_polling.rs b/src/sdam/srv_polling.rs new file mode 100644 index 000000000..8ad686f07 --- /dev/null +++ b/src/sdam/srv_polling.rs @@ -0,0 +1,156 @@ +#[cfg(test)] +mod test; + +use std::time::Duration; + +use super::{ + description::topology::TopologyType, + monitor::DEFAULT_HEARTBEAT_FREQUENCY, + TopologyUpdater, + TopologyWatcher, +}; +use crate::{ + error::{Error, Result}, + options::ClientOptions, + runtime, + srv::{LookupHosts, SrvResolver}, +}; + +const MIN_RESCAN_SRV_INTERVAL: Duration = Duration::from_secs(60); + +pub(crate) struct SrvPollingMonitor { + initial_hostname: String, + resolver: Option, + topology_updater: TopologyUpdater, + topology_watcher: TopologyWatcher, + rescan_interval: Duration, + client_options: ClientOptions, +} + +impl SrvPollingMonitor { + pub(crate) fn new( + topology_updater: TopologyUpdater, + topology_watcher: TopologyWatcher, + mut client_options: ClientOptions, + ) -> Option { + let initial_info = client_options.original_srv_info.take()?; + + Some(Self { + initial_hostname: initial_info.hostname, + resolver: None, + topology_updater, + topology_watcher, + rescan_interval: initial_info.min_ttl, + client_options, + }) + } + + /// Starts a monitoring task that periodically performs SRV record lookups to determine if the + /// set of mongos in the cluster have changed. A weak reference is used to ensure that the + /// monitoring task doesn't keep the topology alive after the client has been dropped. + pub(super) fn start( + topology: TopologyUpdater, + topology_watcher: TopologyWatcher, + client_options: ClientOptions, + ) { + if let Some(monitor) = Self::new(topology, topology_watcher, client_options) { + runtime::spawn(monitor.execute()); + } + } + + fn rescan_interval(&self) -> Duration { + if cfg!(test) { + self.rescan_interval + } else { + std::cmp::max(self.rescan_interval, MIN_RESCAN_SRV_INTERVAL) + } + } + + async fn execute(mut self) { + fn should_poll(tt: TopologyType) -> bool { + matches!(tt, TopologyType::Sharded | TopologyType::Unknown) + } + + while self.topology_watcher.is_alive() { + tokio::time::sleep(self.rescan_interval()).await; + + if should_poll(self.topology_watcher.topology_type()) { + let hosts = self.lookup_hosts().await; + + // verify we should still update before updating in case the topology changed + // while the srv lookup was happening. + if should_poll(self.topology_watcher.topology_type()) { + self.update_hosts(hosts).await; + } + } + } + } + + async fn update_hosts(&mut self, lookup: Result) { + let lookup = match lookup { + Ok(LookupHosts { hosts, .. }) if hosts.is_empty() => { + self.no_valid_hosts(None); + + return; + } + Ok(lookup) => lookup, + Err(err) => { + self.no_valid_hosts(Some(err)); + + return; + } + }; + + self.rescan_interval = lookup.min_ttl; + + // TODO: RUST-230 Log error with host that was returned. + self.topology_updater + .sync_hosts(lookup.hosts.into_iter().collect()) + .await; + } + + async fn lookup_hosts(&mut self) -> Result { + #[cfg(test)] + if let Some(mock) = self + .client_options + .test_options + .as_ref() + .and_then(|to| to.mock_lookup_hosts.as_ref()) + { + return mock.clone(); + } + let initial_hostname = self.initial_hostname.clone(); + let resolver = self.get_or_create_srv_resolver().await?; + resolver + .get_srv_hosts(initial_hostname.as_str(), crate::srv::DomainMismatch::Skip) + .await + } + + async fn get_or_create_srv_resolver(&mut self) -> Result<&SrvResolver> { + if let Some(ref resolver) = self.resolver { + return Ok(resolver); + } + + let resolver = SrvResolver::new( + self.client_options.resolver_config().cloned(), + self.client_options.srv_service_name.clone(), + ) + .await?; + + // Since the connection was not `Some` above, this will always insert the new connection and + // return a reference to it. + Ok(self.resolver.get_or_insert(resolver)) + } + + fn no_valid_hosts(&mut self, _error: Option) { + // TODO RUST-230: Log error/lack of valid results. + + self.rescan_interval = self.heartbeat_freq(); + } + + fn heartbeat_freq(&self) -> Duration { + self.client_options + .heartbeat_freq + .unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY) + } +} diff --git a/src/sdam/srv_polling/mod.rs b/src/sdam/srv_polling/mod.rs deleted file mode 100644 index 7aec87509..000000000 --- a/src/sdam/srv_polling/mod.rs +++ /dev/null @@ -1,150 +0,0 @@ -#[cfg(test)] -mod test; - -use std::time::Duration; - -use super::{ - description::topology::TopologyType, - monitor::DEFAULT_HEARTBEAT_FREQUENCY, - TopologyUpdater, - TopologyWatcher, -}; -use crate::{ - error::{Error, Result}, - options::ClientOptions, - runtime, - srv::{LookupHosts, SrvResolver}, -}; - -const MIN_RESCAN_SRV_INTERVAL: Duration = Duration::from_secs(60); - -pub(crate) struct SrvPollingMonitor { - initial_hostname: String, - resolver: Option, - topology_updater: TopologyUpdater, - topology_watcher: TopologyWatcher, - rescan_interval: Duration, - client_options: ClientOptions, -} - -impl SrvPollingMonitor { - pub(crate) fn new( - topology_updater: TopologyUpdater, - topology_watcher: TopologyWatcher, - mut client_options: ClientOptions, - ) -> Option { - let initial_info = match client_options.original_srv_info.take() { - Some(info) => info, - None => return None, - }; - - Some(Self { - initial_hostname: initial_info.hostname, - resolver: None, - topology_updater, - topology_watcher, - rescan_interval: initial_info.min_ttl, - client_options, - }) - } - - /// Starts a monitoring task that periodically performs SRV record lookups to determine if the - /// set of mongos in the cluster have changed. A weak reference is used to ensure that the - /// monitoring task doesn't keep the topology alive after the client has been dropped. - pub(super) fn start( - topology: TopologyUpdater, - topology_watcher: TopologyWatcher, - client_options: ClientOptions, - ) { - if let Some(monitor) = Self::new(topology, topology_watcher, client_options) { - runtime::execute(monitor.execute()); - } - } - - fn rescan_interval(&self) -> Duration { - std::cmp::max(self.rescan_interval, MIN_RESCAN_SRV_INTERVAL) - } - - async fn execute(mut self) { - fn should_poll(tt: TopologyType) -> bool { - matches!(tt, TopologyType::Sharded | TopologyType::Unknown) - } - - while self.topology_watcher.is_alive() { - runtime::delay_for(self.rescan_interval()).await; - - if should_poll(self.topology_watcher.topology_type()) { - let hosts = self.lookup_hosts().await; - - // verify we should still update before updating in case the topology changed - // while the srv lookup was happening. - if should_poll(self.topology_watcher.topology_type()) { - self.update_hosts(hosts).await; - } - } - } - } - - async fn update_hosts(&mut self, lookup: Result) { - let lookup = match lookup { - Ok(LookupHosts { hosts, .. }) if hosts.is_empty() => { - self.no_valid_hosts(None); - - return; - } - Ok(lookup) => lookup, - Err(err) => { - self.no_valid_hosts(Some(err)); - - return; - } - }; - - self.rescan_interval = lookup.min_ttl; - - // TODO: RUST-230 Log error with host that was returned. - self.topology_updater - .sync_hosts(lookup.hosts.into_iter().filter_map(Result::ok).collect()) - .await; - } - - async fn lookup_hosts(&mut self) -> Result { - #[cfg(test)] - if let Some(mock) = self - .client_options - .test_options - .as_ref() - .and_then(|to| to.mock_lookup_hosts.as_ref()) - { - return mock.clone(); - } - let initial_hostname = self.initial_hostname.clone(); - let resolver = self.get_or_create_srv_resolver().await?; - resolver.get_srv_hosts(initial_hostname.as_str()).await - } - - async fn get_or_create_srv_resolver(&mut self) -> Result<&SrvResolver> { - if let Some(ref resolver) = self.resolver { - return Ok(resolver); - } - - let resolver = - SrvResolver::new(self.client_options.resolver_config.clone().map(|c| c.inner)).await?; - - // Since the connection was not `Some` above, this will always insert the new connection and - // return a reference to it. - Ok(self.resolver.get_or_insert(resolver)) - } - - fn no_valid_hosts(&mut self, _error: Option) { - // TODO RUST-230: Log error/lack of valid results. - - self.rescan_interval = self.heartbeat_freq(); - } - - fn heartbeat_freq(&self) -> Duration { - self.client_options - .heartbeat_freq - .unwrap_or(DEFAULT_HEARTBEAT_FREQUENCY) - } -} diff --git a/src/sdam/srv_polling/test.rs b/src/sdam/srv_polling/test.rs index 1c2079b82..646a458b9 100644 --- a/src/sdam/srv_polling/test.rs +++ b/src/sdam/srv_polling/test.rs @@ -1,15 +1,14 @@ use std::{collections::HashSet, time::Duration}; +use once_cell::sync::Lazy; use pretty_assertions::assert_eq; -use tokio::sync::RwLockReadGuard; use super::{LookupHosts, SrvPollingMonitor}; use crate::{ error::Result, options::{ClientOptions, ServerAddress}, - runtime, sdam::Topology, - test::{log_uncaptured, CLIENT_OPTIONS, LOCK}, + test::{get_client_options, log_uncaptured}, }; fn localhost_test_build_10gen(port: u16) -> ServerAddress { @@ -19,19 +18,34 @@ fn localhost_test_build_10gen(port: u16) -> ServerAddress { } } -lazy_static::lazy_static! { - static ref DEFAULT_HOSTS: Vec = vec![ +static DEFAULT_HOSTS: Lazy> = Lazy::new(|| { + vec![ localhost_test_build_10gen(27017), localhost_test_build_10gen(27108), - ]; -} + ] +}); async fn run_test(new_hosts: Result>, expected_hosts: HashSet) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; + run_test_srv(None, new_hosts, expected_hosts).await +} + +async fn run_test_srv( + max_hosts: Option, + new_hosts: Result>, + expected_hosts: HashSet, +) { + let actual = run_test_extra(max_hosts, new_hosts).await; + assert_eq!(expected_hosts, actual); +} +async fn run_test_extra( + max_hosts: Option, + new_hosts: Result>, +) -> HashSet { let mut options = ClientOptions::new_srv(); - options.hosts = DEFAULT_HOSTS.clone(); + options.hosts.clone_from(&DEFAULT_HOSTS); options.test_options_mut().disable_monitoring_threads = true; + options.srv_max_hosts = max_hosts; let mut topology = Topology::new(options.clone()).unwrap(); topology.watch().wait_until_initialized().await; let mut monitor = @@ -41,19 +55,18 @@ async fn run_test(new_hosts: Result>, expected_hosts: HashSet .update_hosts(new_hosts.and_then(make_lookup_hosts)) .await; - assert_eq!(expected_hosts, topology.server_addresses()); + topology.server_addresses() } fn make_lookup_hosts(hosts: Vec) -> Result { Ok(LookupHosts { - hosts: hosts.into_iter().map(Result::Ok).collect(), + hosts, min_ttl: Duration::from_secs(60), }) } // If a new DNS record is returned, it should be reflected in the topology. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn add_new_dns_record() { let hosts = vec![ localhost_test_build_10gen(27017), @@ -65,8 +78,7 @@ async fn add_new_dns_record() { } // If a DNS record is no longer returned, it should be reflected in the topology. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn remove_dns_record() { let hosts = vec![localhost_test_build_10gen(27017)]; @@ -74,8 +86,7 @@ async fn remove_dns_record() { } // If a single DNS record is replaced, it should be reflected in the topology. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn replace_single_dns_record() { let hosts = vec![ localhost_test_build_10gen(27017), @@ -86,8 +97,7 @@ async fn replace_single_dns_record() { } // If all DNS records are replaced, it should be reflected in the topology. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn replace_all_dns_records() { let hosts = vec![localhost_test_build_10gen(27019)]; @@ -95,8 +105,7 @@ async fn replace_all_dns_records() { } // If a timeout error occurs, the topology should be unchanged. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn timeout_error() { run_test( Err(std::io::ErrorKind::TimedOut.into()), @@ -106,20 +115,16 @@ async fn timeout_error() { } // If no results are returned, the topology should be unchanged. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn no_results() { run_test(Ok(Vec::new()), DEFAULT_HOSTS.iter().cloned().collect()).await; } // SRV polling is not done for load-balanced clusters (as per spec at -// https://github.com/mongodb/specifications/blob/master/source/polling-srv-records-for-mongos-discovery/tests/README.rst#test-that-srv-polling-is-not-done-for-load-balalanced-clusters). -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +// https://github.com/mongodb/specifications/tree/master/source/polling-srv-records-for-mongos-discovery/tests#9-test-that-srv-polling-is-not-done-for-load-balalanced-clusters). +#[tokio::test] async fn load_balanced_no_srv_polling() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - if CLIENT_OPTIONS.get().await.load_balanced != Some(true) { + if get_client_options().await.load_balanced != Some(true) { log_uncaptured("skipping load_balanced_no_srv_polling due to not load balanced topology"); return; } @@ -127,7 +132,7 @@ async fn load_balanced_no_srv_polling() { let hosts = vec![localhost_test_build_10gen(27017)]; let mut options = ClientOptions::new_srv(); let rescan_interval = options.original_srv_info.as_ref().cloned().unwrap().min_ttl; - options.hosts = hosts.clone(); + options.hosts.clone_from(&hosts); options.load_balanced = Some(true); options.test_options_mut().mock_lookup_hosts = Some(make_lookup_hosts(vec![ localhost_test_build_10gen(27017), @@ -135,9 +140,73 @@ async fn load_balanced_no_srv_polling() { ])); let mut topology = Topology::new(options).unwrap(); topology.watch().wait_until_initialized().await; - runtime::delay_for(rescan_interval * 2).await; + tokio::time::sleep(rescan_interval * 2).await; assert_eq!( hosts.into_iter().collect::>(), topology.server_addresses() ); } + +// SRV polling with srvMaxHosts MongoClient option: All DNS records are selected (srvMaxHosts = 0) +#[tokio::test] +async fn srv_max_hosts_zero() { + let hosts = vec![ + localhost_test_build_10gen(27017), + localhost_test_build_10gen(27019), + localhost_test_build_10gen(27020), + ]; + + run_test_srv(None, Ok(hosts.clone()), hosts.clone().into_iter().collect()).await; + run_test_srv(Some(0), Ok(hosts.clone()), hosts.into_iter().collect()).await; +} + +// SRV polling with srvMaxHosts MongoClient option: All DNS records are selected (srvMaxHosts >= +// records) +#[tokio::test] +async fn srv_max_hosts_gt_actual() { + let hosts = vec![ + localhost_test_build_10gen(27019), + localhost_test_build_10gen(27020), + ]; + + run_test_srv(Some(2), Ok(hosts.clone()), hosts.into_iter().collect()).await; +} + +// SRV polling with srvMaxHosts MongoClient option: New DNS records are randomly selected +// (srvMaxHosts > 0) +#[tokio::test] +async fn srv_max_hosts_random() { + let hosts = vec![ + localhost_test_build_10gen(27017), + localhost_test_build_10gen(27019), + localhost_test_build_10gen(27020), + ]; + + let actual = run_test_extra(Some(2), Ok(hosts)).await; + assert_eq!(2, actual.len()); + assert!(actual.contains(&localhost_test_build_10gen(27017))); +} + +#[tokio::test] +async fn srv_service_name() { + let rescan_interval = Duration::from_secs(1); + let new_hosts = vec![ + ServerAddress::Tcp { + host: "localhost.test.build.10gen.cc".to_string(), + port: Some(27019), + }, + ServerAddress::Tcp { + host: "localhost.test.build.10gen.cc".to_string(), + port: Some(27020), + }, + ]; + let uri = "mongodb+srv://test22.test.build.10gen.cc/?srvServiceName=customname"; + let mut options = ClientOptions::parse(uri).await.unwrap(); + // override the min_ttl to speed up lookup interval + options.original_srv_info.as_mut().unwrap().min_ttl = rescan_interval; + options.test_options_mut().mock_lookup_hosts = Some(make_lookup_hosts(new_hosts.clone())); + let mut topology = Topology::new(options).unwrap(); + topology.watch().wait_until_initialized().await; + tokio::time::sleep(rescan_interval * 2).await; + assert_eq!(topology.server_addresses(), new_hosts.into_iter().collect()); +} diff --git a/src/sdam/test.rs b/src/sdam/test.rs index 874648453..8bfe7cedf 100644 --- a/src/sdam/test.rs +++ b/src/sdam/test.rs @@ -1,52 +1,40 @@ use std::{ collections::HashSet, - sync::Arc, time::{Duration, Instant}, }; -use bson::doc; -use semver::VersionReq; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; +use crate::bson::doc; use crate::{ client::options::{ClientOptions, ServerAddress}, cmap::RawCommandResponse, error::{Error, ErrorKind}, - event::{cmap::CmapEvent, sdam::SdamEventHandler}, + event::{cmap::CmapEvent, sdam::SdamEvent}, hello::{LEGACY_HELLO_COMMAND_NAME, LEGACY_HELLO_COMMAND_NAME_LOWERCASE}, sdam::{ServerDescription, Topology}, test::{ + fail_command_appname_initial_handshake_supported, + get_client_options, log_uncaptured, + server_version_matches, + topology_is_load_balanced, + topology_is_replica_set, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, Event, - EventClient, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - SdamEvent, - TestClient, - CLIENT_OPTIONS, - LOCK, }, Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn min_heartbeat_frequency() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let mut setup_client_options = CLIENT_OPTIONS.get().await.clone(); - if setup_client_options.load_balanced.unwrap_or(false) { + if topology_is_load_balanced().await { log_uncaptured("skipping min_heartbeat_frequency test due to load-balanced topology"); return; } - setup_client_options.hosts.drain(1..); - setup_client_options.direct_connection = Some(true); - - let setup_client = TestClient::with_options(Some(setup_client_options.clone())).await; - - if !setup_client.supports_fail_command_appname_initial_handshake() { + if !fail_command_appname_initial_handshake_supported().await { log_uncaptured( "skipping min_heartbeat_frequency test due to server not supporting failcommand \ appname", @@ -54,20 +42,25 @@ async fn min_heartbeat_frequency() { return; } - let fp_options = FailCommandOptions::builder() - .app_name("SDAMMinHeartbeatFrequencyTest".to_string()) - .error_code(1234) - .build(); - let failpoint = FailPoint::fail_command( - &[LEGACY_HELLO_COMMAND_NAME, "hello"], - FailPointMode::Times(5), - fp_options, - ); + let mut setup_client_options = get_client_options().await.clone(); + setup_client_options.hosts.drain(1..); + setup_client_options.direct_connection = Some(true); + + let setup_client = Client::for_test() + .options(setup_client_options.clone()) + .await; - let _fp_guard = setup_client - .enable_failpoint(failpoint, None) + let _guard = setup_client + .enable_fail_point( + FailPoint::fail_command( + &[LEGACY_HELLO_COMMAND_NAME, "hello"], + FailPointMode::Times(5), + ) + .app_name("SDAMMinHeartbeatFrequencyTest") + .error_code(1234), + ) .await - .expect("enabling failpoint should succeed"); + .unwrap(); let mut options = setup_client_options; options.app_name = Some("SDAMMinHeartbeatFrequencyTest".to_string()); @@ -77,7 +70,7 @@ async fn min_heartbeat_frequency() { let start = Instant::now(); client .database("admin") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await .expect("ping should eventually succeed"); @@ -94,12 +87,16 @@ async fn min_heartbeat_frequency() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn sdam_pool_management() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; + if !server_version_matches(">= 4.2.9").await { + log_uncaptured( + "skipping sdam_pool_management test due to server not supporting appName failCommand", + ); + return; + } - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); if options.load_balanced.unwrap_or(false) { log_uncaptured("skipping sdam_pool_management test due to load-balanced topology"); return; @@ -109,61 +106,44 @@ async fn sdam_pool_management() { options.app_name = Some("SDAMPoolManagementTest".to_string()); options.heartbeat_freq = Some(Duration::from_millis(50)); - let event_handler = EventHandler::new(); - let mut subscriber = event_handler.subscribe(); - - let client = EventClient::with_additional_options( - Some(options), - Some(Duration::from_millis(50)), - None, - event_handler.clone(), - ) - .await; + let client = Client::for_test() + .options(options) + .use_single_mongos() + .min_heartbeat_freq(Duration::from_millis(50)) + .monitor_events() + .await; - if !VersionReq::parse(">= 4.2.9") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured( - "skipping sdam_pool_management test due to server not supporting appName failCommand", - ); - return; - } + let mut subscriber = client.events.stream_all(); subscriber - .wait_for_event(Duration::from_millis(500), |event| { + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Cmap(CmapEvent::PoolReady(_))) }) .await .expect("should see pool ready event"); subscriber - .wait_for_event(Duration::from_millis(500), |event| { + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(_))) }) .await .expect("should see server heartbeat succeeded event"); - let fp_options = FailCommandOptions::builder() - .app_name("SDAMPoolManagementTest".to_string()) - .error_code(1234) - .build(); - let failpoint = FailPoint::fail_command( - &[LEGACY_HELLO_COMMAND_NAME, "hello"], - FailPointMode::Times(4), - fp_options, - ); - - let _fp_guard = client - .enable_failpoint(failpoint, None) + let _guard = client + .enable_fail_point( + FailPoint::fail_command( + &[LEGACY_HELLO_COMMAND_NAME, "hello"], + FailPointMode::Times(4), + ) + .app_name("SDAMPoolManagementTest") + .error_code(1234), + ) .await - .expect("enabling failpoint should succeed"); + .unwrap(); // Since there is no deterministic ordering, simply collect all the events and check for their // presence. - let events = subscriber - .collect_events(Duration::from_secs(1), |_| true) - .await; + let events = subscriber.collect(Duration::from_secs(1), |_| true).await; assert!(events .iter() .any(|e| matches!(e, Event::Sdam(SdamEvent::ServerHeartbeatFailed(_))))); @@ -178,12 +158,14 @@ async fn sdam_pool_management() { .any(|e| matches!(e, Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(_))))); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn hello_ok_true() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; + if !server_version_matches(">= 4.4.5").await { + log_uncaptured("skipping hello_ok_true test due to server not supporting hello"); + return; + } - let mut setup_client_options = CLIENT_OPTIONS.get().await.clone(); + let mut setup_client_options = get_client_options().await.clone(); setup_client_options.hosts.drain(1..); if setup_client_options.server_api.is_some() { @@ -196,29 +178,21 @@ async fn hello_ok_true() { return; } - let setup_client = TestClient::with_options(Some(setup_client_options.clone())).await; - if !VersionReq::parse(">= 4.4.5") - .unwrap() - .matches(&setup_client.server_version) - { - log_uncaptured("skipping hello_ok_true test due to server not supporting hello"); - return; - } + let buffer = EventBuffer::new(); - let handler = Arc::new(EventHandler::new()); - let mut subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); let mut options = setup_client_options.clone(); - options.sdam_event_handler = Some(handler.clone()); + options.sdam_event_handler = Some(buffer.handler()); options.direct_connection = Some(true); options.heartbeat_freq = Some(Duration::from_millis(500)); let _client = Client::with_options(options).expect("client creation should succeed"); // first heartbeat should be legacy hello but contain helloOk - subscriber - .wait_for_event(Duration::from_millis(2000), |event| { + event_stream + .next_match(Duration::from_millis(2000), |event| { if let Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(e)) = event { - assert_eq!(e.reply.get_bool("helloOk"), Ok(true)); + assert!(e.reply.get_bool("helloOk").unwrap()); assert!(e.reply.get(LEGACY_HELLO_COMMAND_NAME_LOWERCASE).is_some()); assert!(e.reply.get("isWritablePrimary").is_none()); return true; @@ -230,8 +204,8 @@ async fn hello_ok_true() { // subsequent heartbeats should just be hello for _ in 0..3 { - subscriber - .wait_for_event(Duration::from_millis(2000), |event| { + event_stream + .next_match(Duration::from_millis(2000), |event| { if let Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(e)) = event { assert!(e.reply.get("isWritablePrimary").is_some()); assert!(e.reply.get(LEGACY_HELLO_COMMAND_NAME_LOWERCASE).is_none()); @@ -244,24 +218,20 @@ async fn hello_ok_true() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn repl_set_name_mismatch() -> crate::error::Result<()> { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - if !client.is_replica_set() { + if !topology_is_replica_set().await { log_uncaptured("skipping repl_set_name_mismatch due to non-replica set topology"); return Ok(()); } - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.hosts.drain(1..); options.direct_connection = Some(true); options.repl_set_name = Some("invalid".to_string()); options.server_selection_timeout = Some(Duration::from_secs(5)); let client = Client::with_options(options)?; - let result = client.list_database_names(None, None).await; + let result = client.list_database_names().await; assert!( match result { Err(Error { ref kind, .. }) => matches!(**kind, ErrorKind::ServerSelection { .. }), @@ -276,12 +246,9 @@ async fn repl_set_name_mismatch() -> crate::error::Result<()> { /// Test verifying that a server's monitor stops after the server has been removed from the /// topology. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn removed_server_monitor_stops() -> crate::error::Result<()> { - let _guard = LOCK.run_concurrently().await; - - let handler = Arc::new(EventHandler::new()); + let buffer = EventBuffer::new(); let options = ClientOptions::builder() .hosts(vec![ ServerAddress::parse("localhost:49152")?, @@ -289,20 +256,20 @@ async fn removed_server_monitor_stops() -> crate::error::Result<()> { ServerAddress::parse("localhost:49154")?, ]) .heartbeat_freq(Duration::from_millis(50)) - .sdam_event_handler(handler.clone() as Arc) + .sdam_event_handler(buffer.handler()) .repl_set_name("foo".to_string()) .build(); let hosts = options.hosts.clone(); let set_name = options.repl_set_name.clone().unwrap(); - let mut subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); let topology = Topology::new(options)?; // Wait until all three monitors have started. let mut seen_monitors = HashSet::new(); - subscriber - .wait_for_event(Duration::from_millis(500), |event| { + event_stream + .next_match(Duration::from_millis(500), |event| { if let Event::Sdam(SdamEvent::ServerHeartbeatStarted(e)) = event { seen_monitors.insert(e.server_address.clone()); } @@ -341,13 +308,13 @@ async fn removed_server_monitor_stops() -> crate::error::Result<()> { )) .await; - subscriber.wait_for_event(Duration::from_secs(1), |event| { + event_stream.next_match(Duration::from_secs(1), |event| { matches!(event, Event::Sdam(SdamEvent::ServerClosed(e)) if e.address == hosts[2]) }).await.expect("should see server closed event"); // Capture heartbeat events for 1 second. The monitor for the removed server should stop // publishing them. - let events = subscriber.collect_events(Duration::from_secs(1), |event| { + let events = event_stream.collect(Duration::from_secs(1), |event| { matches!(event, Event::Sdam(SdamEvent::ServerHeartbeatStarted(e)) if e.server_address == hosts[2]) }).await; @@ -361,3 +328,27 @@ async fn removed_server_monitor_stops() -> crate::error::Result<()> { Ok(()) } + +#[test] +fn ipv6_invalid_me() { + let addr = ServerAddress::Tcp { + host: "::1".to_string(), + port: Some(8191), + }; + let desc = ServerDescription { + address: addr.clone(), + server_type: super::ServerType::RsSecondary, + last_update_time: None, + average_round_trip_time: None, + reply: Ok(Some(crate::hello::HelloReply { + server_address: addr.clone(), + command_response: crate::hello::HelloCommandResponse { + me: Some("[::1]:8191".to_string()), + ..Default::default() + }, + raw_command_response: crate::bson::RawDocumentBuf::new(), + cluster_time: None, + })), + }; + assert!(!desc.invalid_me().unwrap()); +} diff --git a/src/sdam/topology.rs b/src/sdam/topology.rs index 1f061da26..4de8cd61a 100644 --- a/src/sdam/topology.rs +++ b/src/sdam/topology.rs @@ -5,7 +5,7 @@ use std::{ time::Duration, }; -use bson::oid::ObjectId; +use crate::bson::oid::ObjectId; use futures_util::{ stream::{FuturesUnordered, StreamExt}, FutureExt, @@ -18,15 +18,13 @@ use tokio::sync::{ use crate::{ client::options::{ClientOptions, ServerAddress}, cmap::{ - conn::ConnectionGeneration, + conn::{pooled::PooledConnection, ConnectionGeneration}, establish::{ConnectionEstablisher, EstablisherOptions}, Command, - Connection, PoolGeneration, }, error::{load_balanced_mode_mismatch, Error, Result}, event::sdam::{ - handle_sdam_event, SdamEvent, ServerClosedEvent, ServerDescriptionChangedEvent, @@ -43,6 +41,9 @@ use crate::{ TopologyType, }; +#[cfg(feature = "tracing-unstable")] +use crate::trace::topology::TopologyTracingEventEmitter; + use super::{ monitor::{MonitorManager, MonitorRequestReceiver}, srv_polling::SrvPollingMonitor, @@ -67,22 +68,36 @@ pub(crate) struct Topology { impl Topology { pub(crate) fn new(options: ClientOptions) -> Result { let description = TopologyDescription::default(); + let id = ObjectId::new(); - let event_emitter = options.sdam_event_handler.as_ref().map(|handler| { - let (tx, mut rx) = mpsc::unbounded_channel::>(); - - // Spin up a task to handle events so that a user's event handling code can't block the - // TopologyWorker. - let handler = handler.clone(); - runtime::execute(async move { - while let Some(event) = rx.recv().await { - let (event, ack) = event.into_parts(); - handle_sdam_event(handler.as_ref(), event); - ack.acknowledge(()); - } - }); - SdamEventEmitter { sender: tx } - }); + let event_emitter = + if options.sdam_event_handler.is_some() || cfg!(feature = "tracing-unstable") { + let user_handler = options.sdam_event_handler.clone(); + + #[cfg(feature = "tracing-unstable")] + let tracing_emitter = + TopologyTracingEventEmitter::new(options.tracing_max_document_length_bytes, id); + let (tx, mut rx) = mpsc::unbounded_channel::>(); + runtime::spawn(async move { + while let Some(event) = rx.recv().await { + let (event, ack) = event.into_parts(); + + if let Some(ref user_handler) = user_handler { + #[cfg(feature = "tracing-unstable")] + user_handler.handle(event.clone()); + #[cfg(not(feature = "tracing-unstable"))] + user_handler.handle(event); + } + #[cfg(feature = "tracing-unstable")] + tracing_emitter.handle(event); + + ack.acknowledge(()); + } + }); + Some(SdamEventEmitter { sender: tx }) + } else { + None + }; let (updater, update_receiver) = TopologyUpdater::channel(); let (worker_handle, handle_listener) = WorkerHandleListener::channel(); @@ -93,9 +108,7 @@ impl Topology { let (watcher, publisher) = TopologyWatcher::channel(state); let connection_establisher = - ConnectionEstablisher::new(EstablisherOptions::from_client_options(&options))?; - - let id = ObjectId::new(); + ConnectionEstablisher::new(EstablisherOptions::from(&options))?; let worker = TopologyWorker { id, @@ -183,11 +196,11 @@ impl Topology { } /// Updates the given `command` as needed based on the `criteria`. - pub(crate) fn update_command_with_read_pref( + pub(crate) fn update_command_with_read_pref( &self, server_address: &ServerAddress, - command: &mut Command, - criteria: Option<&SelectionCriteria>, + command: &mut Command, + criteria: &SelectionCriteria, ) { self.watcher .peek_latest() @@ -195,6 +208,14 @@ impl Topology { .update_command_with_read_pref(server_address, command, criteria) } + pub(crate) async fn shutdown(&self) { + self.updater.shutdown().await; + } + + pub(crate) async fn warm_pool(&self) { + self.updater.fill_pool().await; + } + /// Gets the addresses of the servers in the cluster. #[cfg(test)] pub(crate) fn server_addresses(&mut self) -> HashSet { @@ -253,6 +274,13 @@ pub(crate) enum UpdateMessage { error: Error, phase: HandshakePhase, }, + Broadcast(BroadcastMessage), +} + +#[derive(Debug, Clone)] +pub(crate) enum BroadcastMessage { + Shutdown, + FillPool, #[cfg(test)] SyncWorkers, } @@ -307,7 +335,7 @@ impl TopologyWorker { self.update_topology(new_description).await; if self.options.load_balanced == Some(true) { - let base = ServerDescription::new(self.options.hosts[0].clone()); + let base = ServerDescription::new(&self.options.hosts[0]); self.update_server(ServerDescription { server_type: ServerType::LoadBalancer, average_round_trip_time: None, @@ -329,13 +357,15 @@ impl TopologyWorker { } fn start(mut self) { - runtime::execute(async move { + runtime::spawn(async move { self.initialize().await; + let mut shutdown_ack = None; loop { tokio::select! { Some(update) = self.update_receiver.recv() => { let (update, ack) = update.into_parts(); + let mut ack = Some(ack); let changed = match update { UpdateMessage::AdvanceClusterTime(to) => { self.advance_cluster_time(to); @@ -344,7 +374,9 @@ impl TopologyWorker { UpdateMessage::SyncHosts(hosts) => { self.sync_hosts(hosts).await } - UpdateMessage::ServerUpdate(sd) => self.update_server(*sd).await, + UpdateMessage::ServerUpdate(sd) => { + self.update_server(*sd).await + } UpdateMessage::MonitorError { address, error } => { self.handle_monitor_error(address, error).await } @@ -353,18 +385,23 @@ impl TopologyWorker { error, phase, } => self.handle_application_error(address, error, phase).await, - #[cfg(test)] - UpdateMessage::SyncWorkers => { + UpdateMessage::Broadcast(msg) => { let rxen: FuturesUnordered<_> = self .servers .values() - .map(|v| v.pool.sync_worker()) + .map(|v| v.pool.broadcast(msg.clone())) .collect(); let _: Vec<_> = rxen.collect().await; + if matches!(msg, BroadcastMessage::Shutdown) { + shutdown_ack = ack.take(); + break + } false } }; - ack.acknowledge(changed); + if let Some(ack) = ack { + ack.acknowledge(changed); + } }, _ = self.handle_listener.wait_for_all_handle_drops() => { break @@ -375,24 +412,50 @@ impl TopologyWorker { // indicate to the topology watchers that the topology is no longer alive drop(self.publisher); - // close all the monitors. - let mut close_futures = self - .servers - .into_values() - .map(|server| { - drop(server.inner); - server.monitor_manager.close_monitor() - }) - .collect::>(); + // Close all the monitors. + let mut close_futures = FuturesUnordered::new(); + for (address, server) in self.servers.into_iter() { + if let Some(ref emitter) = self.event_emitter { + emitter + .emit(SdamEvent::ServerClosed(ServerClosedEvent { + address, + topology_id: self.id, + })) + .await; + } + drop(server.inner); + close_futures.push(server.monitor_manager.close_monitor()); + } while close_futures.next().await.is_some() {} if let Some(emitter) = self.event_emitter { + if !self.topology_description.servers.is_empty() { + let previous_description = self.topology_description; + let mut new_description = previous_description.clone(); + new_description.servers.clear(); + new_description.topology_type = TopologyType::Unknown; + + emitter + .emit(SdamEvent::TopologyDescriptionChanged(Box::new( + TopologyDescriptionChangedEvent { + topology_id: self.id, + previous_description: previous_description.into(), + new_description: new_description.into(), + }, + ))) + .await; + } + emitter .emit(SdamEvent::TopologyClosed(TopologyClosedEvent { topology_id: self.id, })) .await; } + + if let Some(ack) = shutdown_ack { + ack.acknowledge(true); + } }); } @@ -416,7 +479,7 @@ impl TopologyWorker { async fn sync_hosts(&mut self, hosts: HashSet) -> bool { let mut new_description = self.topology_description.clone(); - new_description.sync_hosts(&hosts); + new_description.sync_hosts(hosts); self.update_topology(new_description).await } @@ -436,16 +499,17 @@ impl TopologyWorker { let diff = old_description.diff(&self.topology_description); let changed = diff.is_some(); if let Some(diff) = diff { - // For ordering of events in tests, sort the addresses. - #[cfg(not(test))] let changed_servers = diff.changed_servers; + // For ordering of events in tests, sort the addresses. #[cfg(test)] let changed_servers = { let mut servers = diff.changed_servers.into_iter().collect::>(); servers.sort_by_key(|(addr, _)| match addr { ServerAddress::Tcp { host, port } => (host, port), + #[cfg(unix)] + ServerAddress::Unix { .. } => unreachable!(), }); servers }; @@ -477,6 +541,8 @@ impl TopologyWorker { let mut addresses = diff.removed_addresses.into_iter().collect::>(); addresses.sort_by_key(|addr| match addr { ServerAddress::Tcp { host, port } => (host, port), + #[cfg(unix)] + ServerAddress::Unix { .. } => unreachable!(), }); addresses }; @@ -513,6 +579,8 @@ impl TopologyWorker { let mut addresses = diff.added_addresses.into_iter().collect::>(); addresses.sort_by_key(|addr| match addr { ServerAddress::Tcp { host, port } => (host, port), + #[cfg(unix)] + ServerAddress::Unix { .. } => unreachable!(), }); addresses }; @@ -808,9 +876,20 @@ impl TopologyUpdater { self.send_message(UpdateMessage::SyncHosts(hosts)).await; } + pub(crate) async fn shutdown(&self) { + self.send_message(UpdateMessage::Broadcast(BroadcastMessage::Shutdown)) + .await; + } + + pub(crate) async fn fill_pool(&self) { + self.send_message(UpdateMessage::Broadcast(BroadcastMessage::FillPool)) + .await; + } + #[cfg(test)] pub(crate) async fn sync_workers(&self) { - self.send_message(UpdateMessage::SyncWorkers).await; + self.send_message(UpdateMessage::Broadcast(BroadcastMessage::SyncWorkers)) + .await; } } @@ -1028,7 +1107,7 @@ pub(crate) enum HandshakePhase { } impl HandshakePhase { - pub(crate) fn after_completion(handshaked_connection: &Connection) -> Self { + pub(crate) fn after_completion(handshaked_connection: &PooledConnection) -> Self { Self::AfterCompletion { generation: handshaked_connection.generation, // given that this is a handshaked connection, the stream description should diff --git a/src/search_index.rs b/src/search_index.rs new file mode 100644 index 000000000..d16b3f9b0 --- /dev/null +++ b/src/search_index.rs @@ -0,0 +1,75 @@ +use crate::bson::Document; + +use crate::bson::doc; +use serde::{Deserialize, Serialize}; +use typed_builder::TypedBuilder; + +/// Specifies the options for a search index. +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Default, TypedBuilder, Serialize, Deserialize)] +#[builder(field_defaults(default, setter(into)))] +#[non_exhaustive] +pub struct SearchIndexModel { + /// The definition for this index. + pub definition: Document, + + /// The name for this index, if present. + pub name: Option, + + /// The type for this index, if present. + #[serde(rename = "type")] + pub index_type: Option, +} + +/// Specifies the type of search index. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum SearchIndexType { + /// A regular search index. + Search, + /// A vector search index. + VectorSearch, + /// An unknown type of search index. + #[serde(untagged)] + Other(String), +} + +pub mod options { + #[cfg(docsrs)] + use crate::Collection; + use macro_magic::export_tokens; + use serde::Deserialize; + use typed_builder::TypedBuilder; + + /// Options for [Collection::create_search_index]. Present to allow additional options to be + /// added in the future as a non-breaking change. + #[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] + #[builder(field_defaults(default, setter(into)))] + #[non_exhaustive] + #[export_tokens] + pub struct CreateSearchIndexOptions {} + + /// Options for [Collection::update_search_index]. Present to allow additional options to be + /// added in the future as a non-breaking change. + #[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] + #[builder(field_defaults(default, setter(into)))] + #[non_exhaustive] + #[export_tokens] + pub struct UpdateSearchIndexOptions {} + + /// Options for [Collection::list_search_indexes]. Present to allow additional options to be + /// added in the future as a non-breaking change. + #[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] + #[builder(field_defaults(default, setter(into)))] + #[non_exhaustive] + #[export_tokens] + pub struct ListSearchIndexOptions {} + + /// Options for [Collection::drop_search_index]. Present to allow additional options to be + /// added in the future as a non-breaking change. + #[derive(Clone, Debug, Default, TypedBuilder, Deserialize)] + #[builder(field_defaults(default, setter(into)))] + #[non_exhaustive] + #[export_tokens] + pub struct DropSearchIndexOptions {} +} diff --git a/src/selection_criteria.rs b/src/selection_criteria.rs index 4dc987db6..edbdb5350 100644 --- a/src/selection_criteria.rs +++ b/src/selection_criteria.rs @@ -1,20 +1,20 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; -use derivative::Derivative; +use derive_where::derive_where; use serde::{de::Error as SerdeError, Deserialize, Deserializer, Serialize}; use typed_builder::TypedBuilder; use crate::{ bson::doc, - bson_util, error::{ErrorKind, Result}, options::ServerAddress, sdam::public::ServerInfo, + serde_util, }; /// Describes which servers are suitable for a given operation. -#[derive(Clone, Derivative, derive_more::Display)] -#[derivative(Debug)] +#[derive(Clone, derive_more::Display)] +#[derive_where(Debug)] #[non_exhaustive] pub enum SelectionCriteria { /// A read preference that describes the suitable servers based on the server type, max @@ -27,7 +27,7 @@ pub enum SelectionCriteria { /// A predicate used to filter servers that are considered suitable. A `server` will be /// considered suitable by a `predicate` if `predicate(server)` returns true. #[display(fmt = "Custom predicate")] - Predicate(#[derivative(Debug = "ignore")] Predicate), + Predicate(#[derive_where(skip)] Predicate), } impl PartialEq for SelectionCriteria { @@ -53,19 +53,6 @@ impl SelectionCriteria { } } - #[cfg(test)] - pub(crate) fn as_predicate(&self) -> Option<&Predicate> { - match self { - Self::Predicate(ref p) => Some(p), - _ => None, - } - } - - #[cfg(test)] - pub(crate) fn is_read_pref_primary(&self) -> bool { - matches!(self, Self::ReadPreference(ReadPreference::Primary)) - } - pub(crate) fn from_address(address: ServerAddress) -> Self { SelectionCriteria::Predicate(Arc::new(move |server| server.address() == &address)) } @@ -79,8 +66,8 @@ impl SelectionCriteria { S: serde::Serializer, { match selection_criteria { - Some(SelectionCriteria::ReadPreference(pref)) => { - ReadPreference::serialize_for_client_options(pref, serializer) + Some(SelectionCriteria::ReadPreference(read_preference)) => { + read_preference.serialize(serializer) } _ => serializer.serialize_none(), } @@ -111,64 +98,54 @@ pub type Predicate = Arc bool>; /// See the [MongoDB docs](https://www.mongodb.com/docs/manual/core/read-preference) for more details. #[allow(missing_docs)] #[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] pub enum ReadPreference { /// Only route this operation to the primary. Primary, /// Only route this operation to a secondary. - Secondary { options: ReadPreferenceOptions }, + Secondary { + options: Option, + }, /// Route this operation to the primary if it's available, but fall back to the secondaries if /// not. - PrimaryPreferred { options: ReadPreferenceOptions }, + PrimaryPreferred { + options: Option, + }, /// Route this operation to a secondary if one is available, but fall back to the primary if /// not. - SecondaryPreferred { options: ReadPreferenceOptions }, + SecondaryPreferred { + options: Option, + }, /// Route this operation to the node with the least network latency regardless of whether it's /// the primary or a secondary. - Nearest { options: ReadPreferenceOptions }, + Nearest { + options: Option, + }, } impl std::fmt::Display for ReadPreference { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{{ Mode: ")?; - let opts_ref = match self { - ReadPreference::Primary => { - write!(f, "Primary")?; - None - } - ReadPreference::Secondary { options } => { - write!(f, "Secondary")?; - Some(options) - } - ReadPreference::PrimaryPreferred { options } => { - write!(f, "PrimaryPreferred")?; - Some(options) - } - ReadPreference::SecondaryPreferred { options } => { - write!(f, "SecondaryPreferred")?; - Some(options) + let mut mode = self.mode().to_string(); + mode[0..1].make_ascii_uppercase(); + write!(f, "{{ Mode: {}", mode)?; + + if let Some(options) = self.options() { + if let Some(ref tag_sets) = options.tag_sets { + write!(f, ", Tag Sets: {:?}", tag_sets)?; } - ReadPreference::Nearest { options } => { - write!(f, "Nearest")?; - Some(options) + if let Some(ref max_staleness) = options.max_staleness { + write!(f, ", Max Staleness: {:?}", max_staleness)?; } - }; - if let Some(opts) = opts_ref { - if !opts.is_default() { - if let Some(ref tag_sets) = opts.tag_sets { - write!(f, ", Tag Sets: {:?}", tag_sets)?; - } - if let Some(ref max_staleness) = opts.max_staleness { - write!(f, ", Max Staleness: {:?}", max_staleness)?; - } - if let Some(ref hedge) = opts.hedge { - write!(f, ", Hedge: {}", hedge.enabled)?; - } + #[allow(deprecated)] + if let Some(ref hedge) = options.hedge { + write!(f, ", Hedge: {}", hedge.enabled)?; } } + write!(f, " }}") } } @@ -185,30 +162,28 @@ impl<'de> Deserialize<'de> for ReadPreference { #[serde(flatten)] options: ReadPreferenceOptions, } - let preference = ReadPreferenceHelper::deserialize(deserializer)?; - - match preference.mode.as_str() { - "Primary" => { - if !preference.options.is_default() { - return Err(D::Error::custom(&format!( - "no options can be specified with read preference mode = primary, but got \ - {:?}", - preference.options + let helper = ReadPreferenceHelper::deserialize(deserializer)?; + match helper.mode.to_ascii_lowercase().as_str() { + "primary" => { + if !helper.options.is_default() { + return Err(D::Error::custom(format!( + "cannot specify options for primary read preference, got {:?}", + helper.options ))); } Ok(ReadPreference::Primary) } - "Secondary" => Ok(ReadPreference::Secondary { - options: preference.options, + "secondary" => Ok(ReadPreference::Secondary { + options: Some(helper.options), }), - "PrimaryPreferred" => Ok(ReadPreference::PrimaryPreferred { - options: preference.options, + "primarypreferred" => Ok(ReadPreference::PrimaryPreferred { + options: Some(helper.options), }), - "SecondaryPreferred" | "secondaryPreferred" => Ok(ReadPreference::SecondaryPreferred { - options: preference.options, + "secondarypreferred" => Ok(ReadPreference::SecondaryPreferred { + options: Some(helper.options), }), - "Nearest" => Ok(ReadPreference::Nearest { - options: preference.options, + "nearest" => Ok(ReadPreference::Nearest { + options: Some(helper.options), }), other => Err(D::Error::custom(format!( "Unknown read preference mode: {}", @@ -225,41 +200,23 @@ impl Serialize for ReadPreference { { #[serde_with::skip_serializing_none] #[derive(Serialize)] - #[serde(rename_all = "camelCase", deny_unknown_fields)] + #[serde(rename_all = "camelCase")] struct ReadPreferenceHelper<'a> { mode: &'static str, #[serde(flatten)] options: Option<&'a ReadPreferenceOptions>, } - let helper = match self { - ReadPreference::Primary => ReadPreferenceHelper { - mode: "primary", - options: None, - }, - ReadPreference::PrimaryPreferred { options } => ReadPreferenceHelper { - mode: "primaryPreferred", - options: Some(options), - }, - ReadPreference::Secondary { options } => ReadPreferenceHelper { - mode: "secondary", - options: Some(options), - }, - ReadPreference::SecondaryPreferred { options } => ReadPreferenceHelper { - mode: "secondaryPreferred", - options: Some(options), - }, - ReadPreference::Nearest { options } => ReadPreferenceHelper { - mode: "nearest", - options: Some(options), - }, + let helper = ReadPreferenceHelper { + mode: self.mode(), + options: self.options(), }; - helper.serialize(serializer) } } /// Specifies read preference options for non-primary read preferences. +#[allow(deprecated)] #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, TypedBuilder)] #[builder(field_defaults(default, setter(into)))] @@ -268,6 +225,7 @@ impl Serialize for ReadPreference { pub struct ReadPreferenceOptions { /// Specifies which replica set members should be considered for operations. Each tag set will /// be checked in order until one or more servers is found with each tag in the set. + #[serde(alias = "tag_sets")] pub tag_sets: Option>, /// Specifies the maximum amount of lag behind the primary that a secondary can be to be @@ -279,22 +237,27 @@ pub struct ReadPreferenceOptions { #[serde( rename = "maxStalenessSeconds", default, - deserialize_with = "bson_util::deserialize_duration_option_from_u64_seconds", - serialize_with = "bson_util::serialize_duration_option_as_int_secs" + with = "serde_util::duration_option_as_int_seconds" )] pub max_staleness: Option, /// Specifies hedging behavior for reads. These options only apply to sharded clusters on /// servers that are at least version 4.4. Note that hedged reads are automatically enabled for - /// read preference mode "nearest". + /// read preference mode "nearest" on server versions less than 8.0. /// /// See the [MongoDB docs](https://www.mongodb.com/docs/manual/core/read-preference-hedge-option/) for more details. + #[deprecated( + note = "hedged reads are deprecated as of MongoDB 8.0 and will be removed in a future \ + server version" + )] pub hedge: Option, } impl ReadPreferenceOptions { pub(crate) fn is_default(&self) -> bool { - self.hedge.is_none() + #[allow(deprecated)] + let hedge = self.hedge.is_some(); + !hedge && self.max_staleness.is_none() && self .tag_sets @@ -308,6 +271,7 @@ impl ReadPreferenceOptions { /// /// See the [MongoDB docs](https://www.mongodb.com/docs/manual/core/read-preference-hedge-option/) for more details. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, TypedBuilder)] +#[builder(field_defaults(default, setter(into)))] #[non_exhaustive] pub struct HedgedReadOptions { /// Whether or not to allow reads from a sharded cluster to be "hedged" across two replica @@ -316,27 +280,38 @@ pub struct HedgedReadOptions { pub enabled: bool, } -impl HedgedReadOptions { - /// Creates a new `HedgedReadOptions` with the given value for `enabled`. - pub fn with_enabled(enabled: bool) -> Self { - Self { enabled } +impl ReadPreference { + pub(crate) fn mode(&self) -> &'static str { + match self { + Self::Primary => "primary", + Self::Secondary { .. } => "secondary", + Self::PrimaryPreferred { .. } => "primaryPreferred", + Self::SecondaryPreferred { .. } => "secondaryPreferred", + Self::Nearest { .. } => "nearest", + } } -} -impl ReadPreference { - pub(crate) fn max_staleness(&self) -> Option { + pub(crate) fn options(&self) -> Option<&ReadPreferenceOptions> { match self { - ReadPreference::Primary => None, - ReadPreference::Secondary { ref options } - | ReadPreference::PrimaryPreferred { ref options } - | ReadPreference::SecondaryPreferred { ref options } - | ReadPreference::Nearest { ref options } => options.max_staleness, + Self::Primary => None, + Self::Secondary { options } + | Self::PrimaryPreferred { options } + | Self::SecondaryPreferred { options } + | Self::Nearest { options } => options.as_ref(), } } + pub(crate) fn max_staleness(&self) -> Option { + self.options().and_then(|options| options.max_staleness) + } + + pub(crate) fn tag_sets(&self) -> Option<&Vec> { + self.options().and_then(|options| options.tag_sets.as_ref()) + } + pub(crate) fn with_tags(mut self, tag_sets: Vec) -> Result { let options = match self { - ReadPreference::Primary => { + Self::Primary => { return Err(ErrorKind::InvalidArgument { message: "read preference tags can only be specified when a non-primary mode \ is specified" @@ -344,13 +319,13 @@ impl ReadPreference { } .into()); } - ReadPreference::Secondary { ref mut options } => options, - ReadPreference::PrimaryPreferred { ref mut options } => options, - ReadPreference::SecondaryPreferred { ref mut options } => options, - ReadPreference::Nearest { ref mut options } => options, + Self::Secondary { ref mut options } => options, + Self::PrimaryPreferred { ref mut options } => options, + Self::SecondaryPreferred { ref mut options } => options, + Self::Nearest { ref mut options } => options, }; - options.tag_sets = Some(tag_sets); + options.get_or_insert_with(Default::default).tag_sets = Some(tag_sets); Ok(self) } @@ -371,59 +346,10 @@ impl ReadPreference { ReadPreference::Nearest { ref mut options } => options, }; - options.max_staleness = Some(max_staleness); + options.get_or_insert_with(Default::default).max_staleness = Some(max_staleness); Ok(self) } - - #[cfg(test)] - pub(crate) fn serialize_for_client_options( - read_preference: &ReadPreference, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - #[derive(serde::Serialize)] - struct ReadPreferenceHelper<'a> { - readpreference: &'a str, - - readpreferencetags: Option<&'a Vec>>, - - #[serde(serialize_with = "crate::bson_util::serialize_duration_option_as_int_secs")] - maxstalenessseconds: Option, - } - - let state = match read_preference { - ReadPreference::Primary => ReadPreferenceHelper { - readpreference: "primary", - readpreferencetags: None, - maxstalenessseconds: None, - }, - ReadPreference::PrimaryPreferred { options } => ReadPreferenceHelper { - readpreference: "primaryPreferred", - readpreferencetags: options.tag_sets.as_ref(), - maxstalenessseconds: options.max_staleness, - }, - ReadPreference::Secondary { options } => ReadPreferenceHelper { - readpreference: "secondary", - readpreferencetags: options.tag_sets.as_ref(), - maxstalenessseconds: options.max_staleness, - }, - ReadPreference::SecondaryPreferred { options } => ReadPreferenceHelper { - readpreference: "secondaryPreferred", - readpreferencetags: options.tag_sets.as_ref(), - maxstalenessseconds: options.max_staleness, - }, - ReadPreference::Nearest { options } => ReadPreferenceHelper { - readpreference: "nearest", - readpreferencetags: options.tag_sets.as_ref(), - maxstalenessseconds: options.max_staleness, - }, - }; - - state.serialize(serializer) - } } /// A read preference tag set. See the documentation [here](https://www.mongodb.com/docs/manual/tutorial/configure-replica-set-tag-sets/) for more details. @@ -436,12 +362,15 @@ mod test { #[test] fn hedged_read_included_in_document() { - let options = ReadPreferenceOptions::builder() - .hedge(HedgedReadOptions { enabled: true }) - .build(); + #[allow(deprecated)] + let options = Some( + ReadPreferenceOptions::builder() + .hedge(HedgedReadOptions { enabled: true }) + .build(), + ); let read_pref = ReadPreference::Secondary { options }; - let doc = bson::to_document(&read_pref).unwrap(); + let doc = crate::bson_compat::serialize_to_document(&read_pref).unwrap(); assert_eq!( doc, diff --git a/src/serde_util.rs b/src/serde_util.rs new file mode 100644 index 000000000..571887367 --- /dev/null +++ b/src/serde_util.rs @@ -0,0 +1,254 @@ +use std::time::Duration; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{ + bson::{doc, Bson, Document}, + bson_util::get_u64, + error::{Error, Result}, + options::WriteConcern, + serde_util, +}; + +pub(crate) mod duration_option_as_int_seconds { + use super::*; + + pub(crate) fn serialize( + val: &Option, + serializer: S, + ) -> std::result::Result { + match val { + Some(duration) if duration.as_secs() > i32::MAX as u64 => serializer.serialize_i64( + duration + .as_secs() + .try_into() + .map_err(serde::ser::Error::custom)?, + ), + #[allow(clippy::cast_possible_truncation)] + Some(duration) => serializer.serialize_i32(duration.as_secs() as i32), + None => serializer.serialize_none(), + } + } + + pub(crate) fn deserialize<'de, D>( + deserializer: D, + ) -> std::result::Result, D::Error> + where + D: Deserializer<'de>, + { + let millis = Option::::deserialize(deserializer)?; + Ok(millis.map(Duration::from_secs)) + } +} + +pub(crate) fn serialize_duration_option_as_int_millis( + val: &Option, + serializer: S, +) -> std::result::Result { + match val { + Some(duration) if duration.as_millis() > i32::MAX as u128 => serializer.serialize_i64( + duration + .as_millis() + .try_into() + .map_err(serde::ser::Error::custom)?, + ), + #[allow(clippy::cast_possible_truncation)] + Some(duration) => serializer.serialize_i32(duration.as_millis() as i32), + None => serializer.serialize_none(), + } +} + +pub(crate) fn deserialize_duration_option_from_u64_millis<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let millis = Option::::deserialize(deserializer)?; + Ok(millis.map(Duration::from_millis)) +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +pub(crate) fn serialize_u32_option_as_i32( + val: &Option, + serializer: S, +) -> std::result::Result { + match val { + Some(ref val) => serde_util::serialize_u32_as_i32(val, serializer), + None => serializer.serialize_none(), + } +} + +#[allow(clippy::trivially_copy_pass_by_ref)] +pub(crate) fn serialize_u32_option_as_batch_size( + val: &Option, + serializer: S, +) -> std::result::Result { + match val { + #[allow(clippy::cast_possible_wrap)] + Some(val) if *val <= i32::MAX as u32 => (doc! { + "batchSize": (*val as i32) + }) + .serialize(serializer), + None => Document::new().serialize(serializer), + _ => Err(serde::ser::Error::custom( + "batch size must be able to fit into a signed 32-bit integer", + )), + } +} + +pub(crate) fn serialize_u64_option_as_i64( + val: &Option, + serializer: S, +) -> std::result::Result { + match val { + Some(ref v) => serde_util::serialize_u64_as_i64(v, serializer), + None => serializer.serialize_none(), + } +} + +pub(crate) fn deserialize_u64_from_bson_number<'de, D>( + deserializer: D, +) -> std::result::Result +where + D: Deserializer<'de>, +{ + let bson = Bson::deserialize(deserializer)?; + get_u64(&bson).ok_or_else(|| { + serde::de::Error::custom(format!("could not deserialize u64 from {:?}", bson)) + }) +} + +pub(crate) fn serialize_error_as_string( + val: &Error, + serializer: S, +) -> std::result::Result { + serializer.serialize_str(&val.to_string()) +} + +pub(crate) fn serialize_result_error_as_string( + val: &Result, + serializer: S, +) -> std::result::Result { + val.as_ref() + .map_err(|e| e.to_string()) + .serialize(serializer) +} + +#[cfg(feature = "aws-auth")] +pub(crate) fn deserialize_datetime_option_from_double_or_string<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum AwsDateTime { + Double(f64), + String(String), + } + + let date_time = match AwsDateTime::deserialize(deserializer)? { + #[allow(clippy::cast_possible_truncation)] + AwsDateTime::Double(seconds) => { + let millis = seconds * 1000.0; + crate::bson::DateTime::from_millis(millis as i64) + } + AwsDateTime::String(string) => crate::bson::DateTime::parse_rfc3339_str(string) + .map_err(|e| serde::de::Error::custom(format!("invalid RFC 3339 string: {}", e)))?, + }; + + Ok(Some(date_time)) +} + +pub(crate) fn write_concern_is_empty(write_concern: &Option) -> bool { + write_concern + .as_ref() + .is_none_or(|write_concern| write_concern.is_empty()) +} + +#[cfg(test)] +pub(crate) fn deserialize_nonempty_vec<'de, D, T>( + deserializer: D, +) -> std::result::Result>, D::Error> +where + D: Deserializer<'de>, + T: serde::de::DeserializeOwned, +{ + let vec: Vec = Vec::deserialize(deserializer)?; + if vec.is_empty() { + return Err(serde::de::Error::custom(format!( + "list provided for {} cannot be empty", + std::any::type_name::() + ))); + } + Ok(Some(vec)) +} + +#[cfg(test)] +pub(crate) fn serialize_indexed_map( + map: &std::collections::HashMap, + serializer: S, +) -> std::result::Result { + let string_map: std::collections::BTreeMap<_, _> = map + .iter() + .map(|(index, result)| (index.to_string(), result)) + .collect(); + string_map.serialize(serializer) +} + +#[cfg(test)] +pub(crate) fn deserialize_indexed_map<'de, D, T>( + deserializer: D, +) -> std::result::Result>, D::Error> +where + D: Deserializer<'de>, + T: serde::de::DeserializeOwned, +{ + use std::{collections::HashMap, str::FromStr}; + + let string_map: HashMap = HashMap::deserialize(deserializer)?; + Ok(Some(string_map.into_iter().try_fold( + HashMap::new(), + |mut map, (index, t)| { + let index = usize::from_str(&index).map_err(serde::de::Error::custom)?; + map.insert(index, t); + Ok(map) + }, + )?)) +} + +pub(crate) fn serialize_bool_or_true( + val: &Option, + serializer: S, +) -> std::result::Result { + let val = val.unwrap_or(true); + serializer.serialize_bool(val) +} + +pub(crate) fn serialize_u32_as_i32( + n: &u32, + serializer: S, +) -> std::result::Result { + match i32::try_from(*n) { + Ok(n) => n.serialize(serializer), + Err(_) => Err(serde::ser::Error::custom(format!( + "cannot serialize u32 {} as i32", + n + ))), + } +} + +pub(crate) fn serialize_u64_as_i64( + n: &u64, + serializer: S, +) -> std::result::Result { + match i64::try_from(*n) { + Ok(n) => n.serialize(serializer), + Err(_) => Err(serde::ser::Error::custom(format!( + "cannot serialize u64 {} as i64", + n + ))), + } +} diff --git a/src/srv.rs b/src/srv.rs index 6789b2486..d62aa7070 100644 --- a/src/srv.rs +++ b/src/srv.rs @@ -1,17 +1,8 @@ use std::time::Duration; -use trust_dns_proto::rr::RData; -use trust_dns_resolver::config::ResolverConfig; - -use crate::{ - error::{ErrorKind, Result}, - options::ServerAddress, - runtime::AsyncResolver, -}; - -pub(crate) struct SrvResolver { - resolver: AsyncResolver, -} +#[cfg(feature = "dns-resolver")] +use crate::error::ErrorKind; +use crate::{client::options::ResolverConfig, error::Result, options::ServerAddress}; #[derive(Debug)] pub(crate) struct ResolvedConfig { @@ -24,33 +15,106 @@ pub(crate) struct ResolvedConfig { #[derive(Debug, Clone)] pub(crate) struct LookupHosts { - pub(crate) hosts: Vec>, + pub(crate) hosts: Vec, pub(crate) min_ttl: Duration, } +impl LookupHosts { + #[cfg(feature = "dns-resolver")] + pub(crate) fn validate(mut self, original_hostname: &str, dm: DomainMismatch) -> Result { + let original_hostname_parts: Vec<_> = original_hostname.split('.').collect(); + let original_domain_name = if original_hostname_parts.len() >= 3 { + &original_hostname_parts[1..] + } else { + &original_hostname_parts[..] + }; + + let mut ok_hosts = vec![]; + for addr in self.hosts.drain(..) { + let host = addr.host(); + let hostname_parts: Vec<_> = host.split('.').collect(); + if hostname_parts[1..].ends_with(original_domain_name) { + ok_hosts.push(addr); + } else { + let message = format!( + "SRV lookup for {} returned result {}, which does not match domain name {}", + original_hostname, + host, + original_domain_name.join(".") + ); + match dm { + DomainMismatch::Error => return Err(ErrorKind::DnsResolve { message }.into()), + DomainMismatch::Skip => { + #[cfg(feature = "tracing-unstable")] + { + use crate::trace::SERVER_SELECTION_TRACING_EVENT_TARGET; + if crate::trace::trace_or_log_enabled!( + target: SERVER_SELECTION_TRACING_EVENT_TARGET, + crate::trace::TracingOrLogLevel::Warn + ) { + tracing::warn!( + target: SERVER_SELECTION_TRACING_EVENT_TARGET, + message, + ); + } + } + continue; + } + } + } + } + self.hosts = ok_hosts; + + if self.hosts.is_empty() { + return Err(ErrorKind::DnsResolve { + message: format!("SRV lookup for {} returned no records", original_hostname), + } + .into()); + } + + Ok(self) + } +} + #[derive(Debug, Clone, PartialEq)] pub(crate) struct OriginalSrvInfo { pub(crate) hostname: String, pub(crate) min_ttl: Duration, } -impl SrvResolver { - pub(crate) async fn new(config: Option) -> Result { - let resolver = AsyncResolver::new(config).await?; +pub(crate) enum DomainMismatch { + #[allow(dead_code)] + Error, + Skip, +} + +#[cfg(feature = "dns-resolver")] +pub(crate) struct SrvResolver { + resolver: crate::runtime::AsyncResolver, + srv_service_name: Option, +} - Ok(Self { resolver }) +#[cfg(feature = "dns-resolver")] +impl SrvResolver { + pub(crate) async fn new( + config: Option, + srv_service_name: Option, + ) -> Result { + let resolver = crate::runtime::AsyncResolver::new(config.map(|c| c.inner)).await?; + + Ok(Self { + resolver, + srv_service_name, + }) } pub(crate) async fn resolve_client_options( &mut self, hostname: &str, ) -> Result { - let lookup_result = self.get_srv_hosts(hostname).await?; + let lookup_result = self.get_srv_hosts(hostname, DomainMismatch::Error).await?; let mut config = ResolvedConfig { - hosts: lookup_result - .hosts - .into_iter() - .collect::>>()?, + hosts: lookup_result.hosts, min_ttl: lookup_result.min_ttl, auth_source: None, replica_set: None, @@ -62,81 +126,47 @@ impl SrvResolver { Ok(config) } - pub(crate) async fn get_srv_hosts(&self, original_hostname: &str) -> Result { - let hostname_parts: Vec<_> = original_hostname.split('.').collect(); + async fn get_srv_hosts_unvalidated(&self, lookup_hostname: &str) -> Result { + use hickory_proto::rr::RData; - if hostname_parts.len() < 3 { - return Err(ErrorKind::InvalidArgument { - message: "a 'mongodb+srv' hostname must have at least three '.'-delimited parts" - .into(), - } - .into()); - } - - let lookup_hostname = format!("_mongodb._tcp.{}", original_hostname); - - let srv_lookup = self.resolver.srv_lookup(lookup_hostname.as_str()).await?; - let mut srv_addresses: Vec> = Vec::new(); + let srv_lookup = self.resolver.srv_lookup(lookup_hostname).await?; + let mut hosts = vec![]; let mut min_ttl = u32::MAX; - for record in srv_lookup.as_lookup().record_iter() { let srv = match record.data() { Some(RData::SRV(s)) => s, _ => continue, }; - - let hostname = srv.target().to_utf8(); - let port = Some(srv.port()); - let mut address = ServerAddress::Tcp { - host: hostname, - port, - }; - - let domain_name = &hostname_parts[1..]; - - let mut hostname_parts: Vec<_> = address.host().split('.').collect(); - - // Remove empty final section, which indicates a trailing dot. - if hostname_parts.last().map(|s| s.is_empty()).unwrap_or(false) { - hostname_parts.pop(); - } - - if !&hostname_parts[1..].ends_with(domain_name) { - srv_addresses.push(Err(ErrorKind::DnsResolve { - message: format!( - "SRV lookup for {} returned result {}, which does not match domain name {}", - original_hostname, - address, - domain_name.join(".") - ), - } - .into())); + let mut host = srv.target().to_utf8(); + // Remove the trailing '.' + if host.ends_with('.') { + host.pop(); } - - // The spec tests list the seeds without the trailing '.', so we remove it by - // joining the parts we split rather than manipulating the string. - address = ServerAddress::Tcp { - host: hostname_parts.join("."), - port: address.port(), - }; - + let port = Some(srv.port()); + hosts.push(ServerAddress::Tcp { host, port }); min_ttl = std::cmp::min(min_ttl, record.ttl()); - srv_addresses.push(Ok(address)); - } - - if srv_addresses.is_empty() { - return Err(ErrorKind::DnsResolve { - message: format!("SRV lookup for {} returned no records", original_hostname), - } - .into()); } - Ok(LookupHosts { - hosts: srv_addresses, + hosts, min_ttl: Duration::from_secs(min_ttl.into()), }) } + pub(crate) async fn get_srv_hosts( + &self, + original_hostname: &str, + dm: DomainMismatch, + ) -> Result { + let lookup_hostname = format!( + "_{}._tcp.{}", + self.srv_service_name.as_deref().unwrap_or("mongodb"), + original_hostname + ); + self.get_srv_hosts_unvalidated(&lookup_hostname) + .await? + .validate(original_hostname, dm) + } + async fn get_txt_options( &self, original_hostname: &str, @@ -225,3 +255,38 @@ impl SrvResolver { Ok(()) } } + +/// Stub implementation when dns resolution isn't enabled. +#[cfg(not(feature = "dns-resolver"))] +pub(crate) struct SrvResolver {} + +#[cfg(not(feature = "dns-resolver"))] +impl SrvResolver { + pub(crate) async fn new( + _config: Option, + _srv_service_name: Option, + ) -> Result { + Ok(Self {}) + } + + pub(crate) async fn resolve_client_options( + &mut self, + _hostname: &str, + ) -> Result { + Err(crate::error::Error::invalid_argument( + "mongodb+srv connection strings cannot be used when the 'dns-resolver' feature is \ + disabled", + )) + } + + pub(crate) async fn get_srv_hosts( + &self, + _original_hostname: &str, + _dm: DomainMismatch, + ) -> Result { + return Err(crate::error::Error::invalid_argument( + "mongodb+srv connection strings cannot be used when the 'dns-resolver' feature is \ + disabled", + )); + } +} diff --git a/src/sync.rs b/src/sync.rs new file mode 100644 index 000000000..30f2b7522 --- /dev/null +++ b/src/sync.rs @@ -0,0 +1,27 @@ +//! Contains the sync API. This is only available when the `sync` feature is enabled. + +mod change_stream; +mod client; +mod coll; +mod cursor; +mod db; +pub mod gridfs; + +#[cfg(test)] +mod test; + +pub use change_stream::{ChangeStream, SessionChangeStream}; +pub use client::{session::ClientSession, Client}; +pub use coll::Collection; +pub use cursor::{Cursor, SessionCursor, SessionCursorIter}; +pub use db::Database; + +#[cfg(feature = "sync")] +pub(crate) static TOKIO_RUNTIME: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| match tokio::runtime::Runtime::new() { + Ok(runtime) => runtime, + Err(err) => panic!( + "Error occurred when starting the underlying async runtime: {}", + err + ), + }); diff --git a/src/sync/change_stream.rs b/src/sync/change_stream.rs index b3fa597ae..d74e5bbc7 100644 --- a/src/sync/change_stream.rs +++ b/src/sync/change_stream.rs @@ -8,7 +8,6 @@ use crate::{ ChangeStream as AsyncChangeStream, }, error::Result, - runtime, }; use super::ClientSession; @@ -19,11 +18,11 @@ use super::ClientSession; /// /// `ChangeStream`s are "resumable", meaning that they can be restarted at a given place in the /// stream of events. This is done automatically when the `ChangeStream` encounters certain -/// ["resumable"](https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error) +/// ["resumable"](https://specifications.readthedocs.io/en/latest/change-streams/change-streams/#resumable-error) /// errors, such as transient network failures. It can also be done manually by passing /// a [`ResumeToken`] retrieved from a past event into either the -/// [`resume_after`](crate::options::ChangeStreamOptions::resume_after) or -/// [`start_after`](crate::options::ChangeStreamOptions::start_after) (4.2+) options used to create +/// [`resume_after`](crate::action::Watch::resume_after) or +/// [`start_after`](crate::action::Watch::start_after) (4.2+) options used to create /// the `ChangeStream`. Issuing a raw change stream aggregation is discouraged unless users wish to /// explicitly opt out of resumability. /// @@ -36,8 +35,8 @@ use super::ClientSession; /// # fn func() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; /// # let coll = client.database("foo").collection("bar"); -/// let mut change_stream = coll.watch(None, None)?; -/// coll.insert_one(doc! { "x": 1 }, None)?; +/// let mut change_stream = coll.watch().run()?; +/// coll.insert_one(doc! { "x": 1 }).run()?; /// for event in change_stream { /// let event = event?; /// println!("operation performed: {:?}, document: {:?}", event.operation_type, event.full_document); @@ -99,7 +98,7 @@ where /// # fn func() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; /// # let coll: Collection = client.database("foo").collection("bar"); - /// let mut change_stream = coll.watch(None, None)?; + /// let mut change_stream = coll.watch().run()?; /// let mut resume_token = None; /// while change_stream.is_alive() { /// if let Some(event) = change_stream.next_if_any()? { @@ -112,7 +111,7 @@ where /// # } /// ``` pub fn next_if_any(&mut self) -> Result> { - runtime::block_on(self.async_stream.next_if_any()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.next_if_any()) } } @@ -123,7 +122,7 @@ where type Item = Result; fn next(&mut self) -> Option { - runtime::block_on(self.async_stream.next()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.next()) } } @@ -135,10 +134,10 @@ where /// # /// # async fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; -/// # let mut session = client.start_session(None)?; +/// # let mut session = client.start_session().run()?; /// # let coll = client.database("foo").collection::("bar"); /// # -/// let mut cs = coll.watch_with_session(None, None, &mut session)?; +/// let mut cs = coll.watch().session(&mut session).run()?; /// while let Some(event) = cs.next(&mut session)? { /// println!("{:?}", event) /// } @@ -182,25 +181,25 @@ where /// The session provided must be the same session used to create the change stream. /// /// ``` - /// # use bson::{doc, Document}; - /// # use mongodb::sync::Client; + /// # use mongodb::{bson::{self, doc, Document}, sync::Client}; /// # fn main() { /// # async { /// # let client = Client::with_uri_str("foo")?; /// # let coll = client.database("foo").collection::("bar"); /// # let other_coll = coll.clone(); - /// # let mut session = client.start_session(None)?; - /// let mut cs = coll.watch_with_session(None, None, &mut session)?; + /// # let mut session = client.start_session().run()?; + /// let mut cs = coll.watch().session(&mut session).run()?; /// while let Some(event) = cs.next(&mut session)? { /// let id = bson::to_bson(&event.id)?; - /// other_coll.insert_one_with_session(doc! { "id": id }, None, &mut session)?; + /// other_coll.insert_one(doc! { "id": id }).session(&mut session).run()?; /// } /// # Ok::<(), mongodb::error::Error>(()) /// # }; /// # } /// ``` pub fn next(&mut self, session: &mut ClientSession) -> Result> { - runtime::block_on(self.async_stream.next(&mut session.async_client_session)) + crate::sync::TOKIO_RUNTIME + .block_on(self.async_stream.next(&mut session.async_client_session)) } /// Returns whether the change stream will continue to receive events. @@ -220,8 +219,8 @@ where /// # async fn func() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; /// # let coll: Collection = client.database("foo").collection("bar"); - /// # let mut session = client.start_session(None)?; - /// let mut change_stream = coll.watch_with_session(None, None, &mut session)?; + /// # let mut session = client.start_session().run()?; + /// let mut change_stream = coll.watch().session(&mut session).run()?; /// let mut resume_token = None; /// while change_stream.is_alive() { /// if let Some(event) = change_stream.next_if_any(&mut session)? { @@ -234,7 +233,7 @@ where /// # } /// ``` pub fn next_if_any(&mut self, session: &mut ClientSession) -> Result> { - runtime::block_on( + crate::sync::TOKIO_RUNTIME.block_on( self.async_stream .next_if_any(&mut session.async_client_session), ) diff --git a/src/sync/client.rs b/src/sync/client.rs new file mode 100644 index 000000000..ba372fd9f --- /dev/null +++ b/src/sync/client.rs @@ -0,0 +1,138 @@ +pub mod session; + +use super::Database; +use crate::{ + concern::{ReadConcern, WriteConcern}, + error::Result, + options::{ClientOptions, DatabaseOptions, SelectionCriteria}, + Client as AsyncClient, +}; + +/// This is the main entry point for the synchronous API. A `Client` is used to connect to a MongoDB +/// cluster. By default, it will monitor the topology of the cluster, keeping track of any changes, +/// such as servers being added or removed. +/// +/// `Client` is a wrapper around the asynchronous [`mongodb::Client`](../struct.Client.html), and it +/// starts up a tokio runtime internally to run that wrapped client on. +/// +/// `Client` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, +/// so it can safely be shared across threads. For example: +/// +/// ```rust +/// # use mongodb::{bson::Document, sync::Client, error::Result}; +/// # +/// # fn start_workers() -> Result<()> { +/// let client = Client::with_uri_str("mongodb://example.com")?; +/// +/// for i in 0..5 { +/// let client_ref = client.clone(); +/// +/// std::thread::spawn(move || { +/// let collection = client_ref.database("items").collection::(&format!("coll{}", i)); +/// +/// // Do something with the collection +/// }); +/// } +/// # +/// # // Technically we should join the threads here, but for the purpose of the example, we'll just +/// # // sleep for a bit. +/// # std::thread::sleep(std::time::Duration::from_secs(3)); +/// # Ok(()) +/// # } +/// ``` +/// +/// ## TCP Keepalive +/// TCP keepalive is enabled by default with ``tcp_keepalive_time`` set to 120 seconds. The +/// driver does not set ``tcp_keepalive_intvl``. See the +/// [MongoDB Diagnostics FAQ keepalive section](https://www.mongodb.com/docs/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments) +/// for instructions on setting these values at the system level. +/// +/// ## Clean shutdown +/// Because Rust has no async equivalent of `Drop`, values that require server-side cleanup when +/// dropped spawn a new async task to perform that cleanup. This can cause two potential issues: +/// +/// * Drop tasks pending or in progress when the async runtime shuts down may not complete, causing +/// server-side resources to not be freed. +/// * Drop tasks may run at an arbitrary time even after no `Client` values exist, making it hard to +/// reason about associated resources (e.g. event handlers). +/// +/// To address these issues, we highly recommend you use [`Client::shutdown`] in the termination +/// path of your application. This will ensure that outstanding resources have been cleaned up and +/// terminate internal worker tasks before returning. Please note that `shutdown` will wait for +/// _all_ outstanding resource handles to be dropped, so they must either have been dropped before +/// calling `shutdown` or in a concurrent task; see the documentation of `shutdown` for more +/// details. + +#[derive(Clone, Debug)] +pub struct Client { + pub(crate) async_client: AsyncClient, +} + +impl From for Client { + fn from(async_client: AsyncClient) -> Self { + Self { async_client } + } +} + +impl Client { + /// Creates a new `Client` connected to the cluster specified by `uri`. `uri` must be a valid + /// MongoDB connection string. + /// + /// See the documentation on + /// [`ClientOptions::parse`](../options/struct.ClientOptions.html#method.parse) for more + /// details. + pub fn with_uri_str(uri: impl AsRef) -> Result { + let async_client = + crate::sync::TOKIO_RUNTIME.block_on(AsyncClient::with_uri_str(uri.as_ref()))?; + Ok(Self { async_client }) + } + + /// Creates a new `Client` connected to the cluster specified by `options`. + pub fn with_options(options: ClientOptions) -> Result { + let async_client = AsyncClient::with_options(options)?; + Ok(Self { async_client }) + } + + /// Gets the default selection criteria the `Client` uses for operations.. + pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { + self.async_client.selection_criteria() + } + + /// Gets the default read concern the `Client` uses for operations. + pub fn read_concern(&self) -> Option<&ReadConcern> { + self.async_client.read_concern() + } + + /// Gets the default write concern the `Client` uses for operations. + pub fn write_concern(&self) -> Option<&WriteConcern> { + self.async_client.write_concern() + } + + /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. + /// The `Database` options (e.g. read preference and write concern) will default to those of the + /// `Client`. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn database(&self, name: &str) -> Database { + Database::new(self.async_client.database(name)) + } + + /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. + /// Operations done with this `Database` will use the options specified by `options` by default + /// and will otherwise default to those of the `Client`. + /// + /// This method does not send or receive anything across the wire to the database, so it can be + /// used repeatedly without incurring any costs from I/O. + pub fn database_with_options(&self, name: &str, options: DatabaseOptions) -> Database { + Database::new(self.async_client.database_with_options(name, options)) + } + + /// Gets a handle to the default database specified in the `ClientOptions` or MongoDB connection + /// string used to construct this `Client`. + /// + /// If no default database was specified, `None` will be returned. + pub fn default_database(&self) -> Option { + self.async_client.default_database().map(Database::new) + } +} diff --git a/src/sync/client/mod.rs b/src/sync/client/mod.rs deleted file mode 100644 index 1aba5ce87..000000000 --- a/src/sync/client/mod.rs +++ /dev/null @@ -1,201 +0,0 @@ -pub mod session; - -use super::{ChangeStream, ClientSession, Database, SessionChangeStream}; -use crate::{ - bson::Document, - change_stream::{event::ChangeStreamEvent, options::ChangeStreamOptions}, - concern::{ReadConcern, WriteConcern}, - error::Result, - options::{ - ClientOptions, - DatabaseOptions, - ListDatabasesOptions, - SelectionCriteria, - SessionOptions, - }, - results::DatabaseSpecification, - runtime, - Client as AsyncClient, -}; - -/// This is the main entry point for the synchronous API. A `Client` is used to connect to a MongoDB -/// cluster. By default, it will monitor the topology of the cluster, keeping track of any changes, -/// such as servers being added or removed. -/// -/// `Client` is a wrapper around the asynchronous [`mongodb::Client`](../struct.Client.html), and it -/// starts up an async-std runtime internally to run that wrapped client on. -/// -/// `Client` uses [`std::sync::Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) internally, -/// so it can safely be shared across threads. For example: -/// -/// ```rust -/// # use mongodb::{bson::Document, sync::Client, error::Result}; -/// # -/// # fn start_workers() -> Result<()> { -/// let client = Client::with_uri_str("mongodb://example.com")?; -/// -/// for i in 0..5 { -/// let client_ref = client.clone(); -/// -/// std::thread::spawn(move || { -/// let collection = client_ref.database("items").collection::(&format!("coll{}", i)); -/// -/// // Do something with the collection -/// }); -/// } -/// # -/// # // Technically we should join the threads here, but for the purpose of the example, we'll just -/// # // sleep for a bit. -/// # std::thread::sleep(std::time::Duration::from_secs(3)); -/// # Ok(()) -/// # } -/// ``` -/// -/// ## TCP Keepalive -/// TCP keepalive is enabled by default with ``tcp_keepalive_time`` set to 120 seconds. The -/// driver does not set ``tcp_keepalive_intvl``. See the -/// [MongoDB Diagnostics FAQ keepalive section](https://www.mongodb.com/docs/manual/faq/diagnostics/#does-tcp-keepalive-time-affect-mongodb-deployments) -/// for instructions on setting these values at the system level. -#[derive(Clone, Debug)] -pub struct Client { - async_client: AsyncClient, -} - -impl From for Client { - fn from(async_client: AsyncClient) -> Self { - Self { async_client } - } -} - -impl Client { - /// Creates a new `Client` connected to the cluster specified by `uri`. `uri` must be a valid - /// MongoDB connection string. - /// - /// See the documentation on - /// [`ClientOptions::parse`](../options/struct.ClientOptions.html#method.parse) for more - /// details. - pub fn with_uri_str(uri: impl AsRef) -> Result { - let async_client = runtime::block_on(AsyncClient::with_uri_str(uri.as_ref()))?; - Ok(Self { async_client }) - } - - /// Creates a new `Client` connected to the cluster specified by `options`. - pub fn with_options(options: ClientOptions) -> Result { - let async_client = AsyncClient::with_options(options)?; - Ok(Self { async_client }) - } - - /// Gets the default selection criteria the `Client` uses for operations.. - pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { - self.async_client.selection_criteria() - } - - /// Gets the default read concern the `Client` uses for operations. - pub fn read_concern(&self) -> Option<&ReadConcern> { - self.async_client.read_concern() - } - - /// Gets the default write concern the `Client` uses for operations. - pub fn write_concern(&self) -> Option<&WriteConcern> { - self.async_client.write_concern() - } - - /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. - /// The `Database` options (e.g. read preference and write concern) will default to those of the - /// `Client`. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn database(&self, name: &str) -> Database { - Database::new(self.async_client.database(name)) - } - - /// Gets a handle to a database specified by `name` in the cluster the `Client` is connected to. - /// Operations done with this `Database` will use the options specified by `options` by default - /// and will otherwise default to those of the `Client`. - /// - /// This method does not send or receive anything across the wire to the database, so it can be - /// used repeatedly without incurring any costs from I/O. - pub fn database_with_options(&self, name: &str, options: DatabaseOptions) -> Database { - Database::new(self.async_client.database_with_options(name, options)) - } - - /// Gets a handle to the default database specified in the `ClientOptions` or MongoDB connection - /// string used to construct this `Client`. - /// - /// If no default database was specified, `None` will be returned. - pub fn default_database(&self) -> Option { - self.async_client.default_database().map(Database::new) - } - - /// Gets information about each database present in the cluster the Client is connected to. - pub fn list_databases( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on( - self.async_client - .list_databases(filter.into(), options.into()), - ) - } - - /// Gets the names of the databases present in the cluster the Client is connected to. - pub fn list_database_names( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on( - self.async_client - .list_database_names(filter.into(), options.into()), - ) - } - - /// Starts a new `ClientSession`. - pub fn start_session(&self, options: Option) -> Result { - runtime::block_on(self.async_client.start_session(options)).map(Into::into) - } - - /// Starts a new [`ChangeStream`] that receives events for all changes in the cluster. The - /// stream does not observe changes from system collections or the "config", "local" or - /// "admin" databases. Note that this method (`watch` on a cluster) is only supported in - /// MongoDB 4.0 or greater. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read - /// concern. Anything else will cause a server error. - /// - /// Note that using a `$project` stage to remove any of the `_id` `operationType` or `ns` fields - /// will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> { - runtime::block_on(self.async_client.watch(pipeline, options)).map(ChangeStream::new) - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in the cluster - /// using the provided [`ClientSession`]. See [`Client::watch`] for more information. - pub fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> { - runtime::block_on(self.async_client.watch_with_session( - pipeline, - options, - &mut session.async_client_session, - )) - .map(SessionChangeStream::new) - } -} diff --git a/src/sync/client/session.rs b/src/sync/client/session.rs index 1ff94fc7e..18070afec 100644 --- a/src/sync/client/session.rs +++ b/src/sync/client/session.rs @@ -1,12 +1,5 @@ use super::Client; -use crate::{ - bson::Document, - client::session::ClusterTime, - error::Result, - options::{SessionOptions, TransactionOptions}, - runtime, - ClientSession as AsyncClientSession, -}; +use crate::{bson::Document, client::session::ClusterTime, ClientSession as AsyncClientSession}; /// A MongoDB client session. This struct represents a logical session used for ordering sequential /// operations. To create a `ClientSession`, call `start_session` on a @@ -26,7 +19,19 @@ impl From for ClientSession { } } +impl<'a> From<&'a mut ClientSession> for &'a mut AsyncClientSession { + fn from(value: &'a mut ClientSession) -> &'a mut AsyncClientSession { + &mut value.async_client_session + } +} + impl ClientSession { + pub(crate) fn new(async_client_session: AsyncClientSession) -> Self { + Self { + async_client_session, + } + } + /// The client used to create this session. pub fn client(&self) -> Client { self.async_client_session.client().into() @@ -43,164 +48,9 @@ impl ClientSession { self.async_client_session.cluster_time() } - /// The options used to create this session. - pub fn options(&self) -> Option<&SessionOptions> { - self.async_client_session.options() - } - /// Set the cluster time to the provided one if it is greater than this session's highest seen /// cluster time or if this session's cluster time is `None`. pub fn advance_cluster_time(&mut self, to: &ClusterTime) { self.async_client_session.advance_cluster_time(to) } - - /// Starts a new transaction on this session with the given `TransactionOptions`. If no options - /// are provided, the session's `defaultTransactionOptions` will be used. This session must - /// be passed into each operation within the transaction; otherwise, the operation will be - /// executed outside of the transaction. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession}}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com")?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None)?; - /// session.start_transaction(None)?; - /// let result = coll.insert_one_with_session(doc! { "x": 1 }, None, &mut session)?; - /// session.commit_transaction()?; - /// # Ok(()) - /// # } - /// ``` - pub fn start_transaction( - &mut self, - options: impl Into>, - ) -> Result<()> { - runtime::block_on(self.async_client_session.start_transaction(options)) - } - - /// Commits the transaction that is currently active on this session. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession}}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com")?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None)?; - /// session.start_transaction(None)?; - /// let result = coll.insert_one_with_session(doc! { "x": 1 }, None, &mut session)?; - /// session.commit_transaction()?; - /// # Ok(()) - /// # } - /// ``` - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn commit_transaction(&mut self) -> Result<()> { - runtime::block_on(self.async_client_session.commit_transaction()) - } - - /// Aborts the transaction that is currently active on this session. Any open transaction will - /// be aborted automatically in the `Drop` implementation of `ClientSession`. - /// - /// ```rust - /// # use mongodb::{bson::{doc, Document}, error::Result, sync::{Client, ClientSession, Collection}}; - /// # - /// # async fn do_stuff() -> Result<()> { - /// # let client = Client::with_uri_str("mongodb://example.com")?; - /// # let coll = client.database("foo").collection::("bar"); - /// # let mut session = client.start_session(None)?; - /// session.start_transaction(None)?; - /// match execute_transaction(coll, &mut session) { - /// Ok(_) => session.commit_transaction()?, - /// Err(_) => session.abort_transaction()?, - /// } - /// # Ok(()) - /// # } - /// - /// fn execute_transaction(coll: Collection, session: &mut ClientSession) -> Result<()> { - /// coll.insert_one_with_session(doc! { "x": 1 }, None, session)?; - /// coll.delete_one_with_session(doc! { "y": 2 }, None, session)?; - /// Ok(()) - /// } - /// ``` - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn abort_transaction(&mut self) -> Result<()> { - runtime::block_on(self.async_client_session.abort_transaction()) - } - - /// Starts a transaction, runs the given callback, and commits or aborts the transaction. - /// Transient transaction errors will cause the callback or the commit to be retried; - /// other errors will cause the transaction to be aborted and the error returned to the - /// caller. If the callback needs to provide its own error information, the - /// [`Error::custom`](crate::error::Error::custom) method can accept an arbitrary payload that - /// can be retrieved via [`Error::get_custom`](crate::error::Error::get_custom). - pub fn with_transaction( - &mut self, - mut callback: F, - options: impl Into>, - ) -> Result - where - F: for<'a> FnMut(&'a mut ClientSession) -> Result, - { - let options = options.into(); - let timeout = std::time::Duration::from_secs(120); - let start = std::time::Instant::now(); - - use crate::{ - client::session::TransactionState, - error::{TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}, - }; - - 'transaction: loop { - self.start_transaction(options.clone())?; - let ret = match callback(self) { - Ok(v) => v, - Err(e) => { - if matches!( - self.async_client_session.transaction.state, - TransactionState::Starting | TransactionState::InProgress - ) { - self.abort_transaction()?; - } - if e.contains_label(TRANSIENT_TRANSACTION_ERROR) && start.elapsed() < timeout { - continue 'transaction; - } - return Err(e); - } - }; - if matches!( - self.async_client_session.transaction.state, - TransactionState::None - | TransactionState::Aborted - | TransactionState::Committed { .. } - ) { - return Ok(ret); - } - 'commit: loop { - match self.commit_transaction() { - Ok(()) => return Ok(ret), - Err(e) => { - if e.is_max_time_ms_expired_error() || start.elapsed() >= timeout { - return Err(e); - } - if e.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) { - continue 'commit; - } - if e.contains_label(TRANSIENT_TRANSACTION_ERROR) { - continue 'transaction; - } - return Err(e); - } - } - } - } - } } diff --git a/src/sync/coll.rs b/src/sync/coll.rs index f3d1d592f..74c7bcc7c 100644 --- a/src/sync/coll.rs +++ b/src/sync/coll.rs @@ -1,46 +1,5 @@ -use std::{borrow::Borrow, fmt::Debug}; - -use serde::{de::DeserializeOwned, Serialize}; - -use super::{ChangeStream, ClientSession, Cursor, SessionChangeStream, SessionCursor}; use crate::{ - bson::{Bson, Document}, - change_stream::{event::ChangeStreamEvent, options::ChangeStreamOptions}, - error::Result, - index::IndexModel, - options::{ - AggregateOptions, - CountOptions, - CreateIndexOptions, - DeleteOptions, - DistinctOptions, - DropCollectionOptions, - DropIndexOptions, - EstimatedDocumentCountOptions, - FindOneAndDeleteOptions, - FindOneAndReplaceOptions, - FindOneAndUpdateOptions, - FindOneOptions, - FindOptions, - InsertManyOptions, - InsertOneOptions, - ListIndexesOptions, - ReadConcern, - ReplaceOptions, - SelectionCriteria, - UpdateModifications, - UpdateOptions, - WriteConcern, - }, - results::{ - CreateIndexResult, - CreateIndexesResult, - DeleteResult, - InsertManyResult, - InsertOneResult, - UpdateResult, - }, - runtime, + options::{ReadConcern, SelectionCriteria, WriteConcern}, Collection as AsyncCollection, Namespace, }; @@ -70,7 +29,7 @@ use crate::{ /// /// std::thread::spawn(move || { /// // Perform operations with `coll_ref`. For example: -/// coll_ref.insert_one(doc! { "x": i }, None); +/// coll_ref.insert_one(doc! { "x": i }); /// }); /// } /// # @@ -82,17 +41,23 @@ use crate::{ /// ``` #[derive(Clone, Debug)] -pub struct Collection { - async_collection: AsyncCollection, +pub struct Collection +where + T: Send + Sync, +{ + pub(crate) async_collection: AsyncCollection, } -impl Collection { +impl Collection +where + T: Send + Sync, +{ pub(crate) fn new(async_collection: AsyncCollection) -> Self { Self { async_collection } } /// Gets a clone of the `Collection` with a different type `U`. - pub fn clone_with_type(&self) -> Collection { + pub fn clone_with_type(&self) -> Collection { Collection::new(self.async_collection.clone_with_type()) } @@ -125,764 +90,4 @@ impl Collection { pub fn write_concern(&self) -> Option<&WriteConcern> { self.async_collection.write_concern() } - - /// Drops the collection, deleting all data, users, and indexes stored in it. - pub fn drop(&self, options: impl Into>) -> Result<()> { - runtime::block_on(self.async_collection.drop(options.into())) - } - - /// Drops the collection, deleting all data, users, and indexes stored in it using the provided - /// `ClientSession`. - pub fn drop_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - runtime::block_on( - self.async_collection - .drop_with_session(options.into(), &mut session.async_client_session), - ) - } - - /// Runs an aggregation operation. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub fn aggregate( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result> { - let pipeline: Vec = pipeline.into_iter().collect(); - runtime::block_on(self.async_collection.aggregate(pipeline, options.into())) - .map(Cursor::new) - } - - /// Runs an aggregation operation using the provided `ClientSession`. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub fn aggregate_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let pipeline: Vec = pipeline.into_iter().collect(); - runtime::block_on(self.async_collection.aggregate_with_session( - pipeline, - options.into(), - &mut session.async_client_session, - )) - .map(SessionCursor::new) - } - - /// Estimates the number of documents in the collection using collection metadata. - /// - /// Due to an oversight in versions 5.0.0 - 5.0.7 of MongoDB, the `count` server command, - /// which `estimatedDocumentCount` uses in its implementation, was not included in v1 of the - /// Stable API. Users of the Stable API with `estimatedDocumentCount` are recommended to - /// upgrade their cluster to 5.0.8+ or set - /// [`ServerApi::strict`](crate::options::ServerApi::strict) to false to avoid encountering - /// errors. - /// - /// For more information on the behavior of the `count` server command, see - /// [Count: Behavior](https://www.mongodb.com/docs/manual/reference/command/count/#behavior). - pub fn estimated_document_count( - &self, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_collection - .estimated_document_count(options.into()), - ) - } - - /// Gets the number of documents matching `filter`. - /// - /// Note that using [`Collection::estimated_document_count`](#method.estimated_document_count) - /// is recommended instead of this method is most cases. - pub fn count_documents( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_collection - .count_documents(filter.into(), options.into()), - ) - } - - /// Gets the number of documents matching `filter` using the provided `ClientSession`. - /// - /// Note that using [`Collection::estimated_document_count`](#method.estimated_document_count) - /// is recommended instead of this method is most cases. - pub fn count_documents_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.count_documents_with_session( - filter.into(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Creates the given index on this collection. - pub fn create_index( - &self, - index: IndexModel, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.create_index(index, options)) - } - - /// Creates the given index on this collection using the provided `ClientSession`. - pub fn create_index_with_session( - &self, - index: IndexModel, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.create_index_with_session( - index, - options, - &mut session.async_client_session, - )) - } - - /// Creates the given indexes on this collection. - pub fn create_indexes( - &self, - indexes: impl IntoIterator, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.create_indexes(indexes, options)) - } - - /// Creates the given indexes on this collection using the provided `ClientSession`. - pub fn create_indexes_with_session( - &self, - indexes: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.create_indexes_with_session( - indexes, - options, - &mut session.async_client_session, - )) - } - - /// Deletes all documents stored in the collection matching `query`. - pub fn delete_many( - &self, - query: Document, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.delete_many(query, options.into())) - } - - /// Deletes all documents stored in the collection matching `query` using the provided - /// `ClientSession`. - pub fn delete_many_with_session( - &self, - query: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.delete_many_with_session( - query, - options.into(), - &mut session.async_client_session, - )) - } - - /// Deletes up to one document found matching `query`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn delete_one( - &self, - query: Document, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.delete_one(query, options.into())) - } - - /// Deletes up to one document found matching `query` using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn delete_one_with_session( - &self, - query: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.delete_one_with_session( - query, - options.into(), - &mut session.async_client_session, - )) - } - - /// Finds the distinct values of the field specified by `field_name` across the collection. - pub fn distinct( - &self, - field_name: impl AsRef, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_collection.distinct( - field_name.as_ref(), - filter.into(), - options.into(), - )) - } - - /// Finds the distinct values of the field specified by `field_name` across the collection using - /// the provided `ClientSession`. - pub fn distinct_with_session( - &self, - field_name: impl AsRef, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.distinct_with_session( - field_name.as_ref(), - filter.into(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Updates all documents matching `query` in the collection. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - pub fn update_many( - &self, - query: Document, - update: impl Into, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_collection - .update_many(query, update.into(), options.into()), - ) - } - - /// Drops the index specified by `name` from this collection. - pub fn drop_index( - &self, - name: impl AsRef, - options: impl Into>, - ) -> Result<()> { - runtime::block_on(self.async_collection.drop_index(name, options)) - } - - /// Drops the index specified by `name` from this collection using the provided `ClientSession`. - pub fn drop_index_with_session( - &self, - name: impl AsRef, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - runtime::block_on(self.async_collection.drop_index_with_session( - name, - options, - &mut session.async_client_session, - )) - } - - /// Drops all indexes associated with this collection. - pub fn drop_indexes(&self, options: impl Into>) -> Result<()> { - runtime::block_on(self.async_collection.drop_indexes(options)) - } - - /// Drops all indexes associated with this collection using the provided `ClientSession`. - pub fn drop_indexes_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - runtime::block_on( - self.async_collection - .drop_indexes_with_session(options, &mut session.async_client_session), - ) - } - - /// Lists all indexes on this collection. - pub fn list_indexes( - &self, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_collection.list_indexes(options)).map(Cursor::new) - } - - /// Lists all indexes on this collection using the provided `ClientSession`. - pub fn list_indexes_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on( - self.async_collection - .list_indexes_with_session(options, &mut session.async_client_session), - ) - .map(SessionCursor::new) - } - - /// Gets the names of all indexes on the collection. - pub fn list_index_names(&self) -> Result> { - runtime::block_on(self.async_collection.list_index_names()) - } - - /// Gets the names of all indexes on the collection using the provided `ClientSession`. - pub fn list_index_names_with_session( - &self, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on( - self.async_collection - .list_index_names_with_session(&mut session.async_client_session), - ) - } - - /// Updates all documents matching `query` in the collection using the provided `ClientSession`. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - pub fn update_many_with_session( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.update_many_with_session( - query, - update.into(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Updates up to one document matching `query` in the collection. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn update_one( - &self, - query: Document, - update: impl Into, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_collection - .update_one(query, update.into(), options.into()), - ) - } - - /// Updates up to one document matching `query` in the collection using the provided - /// `ClientSession`. - /// - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. See the official MongoDB - /// [documentation](https://www.mongodb.com/docs/manual/reference/command/update/#behavior) for more information on specifying updates. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn update_one_with_session( - &self, - query: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.update_one_with_session( - query, - update.into(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events - /// for all changes in this collection. A - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) cannot be started on system - /// collections. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read concern. Anything else - /// will cause a server error. - /// - /// Also note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` - /// fields will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> - where - T: DeserializeOwned + Unpin + Send + Sync, - { - runtime::block_on(self.async_collection.watch(pipeline, options)).map(ChangeStream::new) - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in this collection - /// using the provided [`ClientSession`]. See [`Client::watch`](crate::sync::Client::watch) for - /// more information. - pub fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> - where - T: DeserializeOwned + Unpin + Send + Sync, - { - runtime::block_on(self.async_collection.watch_with_session( - pipeline, - options, - &mut session.async_client_session, - )) - .map(SessionChangeStream::new) - } - - /// Finds the documents in the collection matching `filter`. - pub fn find( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_collection.find(filter.into(), options.into())) - .map(Cursor::new) - } - - /// Finds the documents in the collection matching `filter` using the provided `ClientSession`. - pub fn find_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.find_with_session( - filter.into(), - options.into(), - &mut session.async_client_session, - )) - .map(SessionCursor::new) - } -} - -impl Collection -where - T: DeserializeOwned + Unpin + Send + Sync, -{ - /// Finds a single document in the collection matching `filter`. - pub fn find_one( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on( - self.async_collection - .find_one(filter.into(), options.into()), - ) - } - - /// Finds a single document in the collection matching `filter` using the provided - /// `ClientSession`. - pub fn find_one_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_with_session( - filter.into(), - options.into(), - &mut session.async_client_session, - )) - } -} - -impl Collection -where - T: DeserializeOwned, -{ - /// Atomically finds up to one document in the collection matching `filter` and deletes it. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_delete( - &self, - filter: Document, - options: impl Into>, - ) -> Result> { - runtime::block_on( - self.async_collection - .find_one_and_delete(filter, options.into()), - ) - } - - /// Atomically finds up to one document in the collection matching `filter` and deletes it using - /// the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_delete_with_session( - &self, - filter: Document, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_and_delete_with_session( - filter, - options.into(), - &mut session.async_client_session, - )) - } - - /// Atomically finds up to one document in the collection matching `filter` and updates it. - /// Both `Document` and `Vec` implement `Into`, so either can be - /// passed in place of constructing the enum case. Note: pipeline updates are only supported - /// in MongoDB 4.2+. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_update( - &self, - filter: Document, - update: impl Into, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_and_update( - filter, - update.into(), - options.into(), - )) - } - - /// Atomically finds up to one document in the collection matching `filter` and updates it using - /// the provided `ClientSession`. Both `Document` and `Vec` implement - /// `Into`, so either can be passed in place of constructing the enum - /// case. Note: pipeline updates are only supported in MongoDB 4.2+. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_update_with_session( - &self, - filter: Document, - update: impl Into, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_and_update_with_session( - filter, - update.into(), - options.into(), - &mut session.async_client_session, - )) - } -} - -impl Collection -where - T: Serialize + DeserializeOwned, -{ - /// Atomically finds up to one document in the collection matching `filter` and replaces it with - /// `replacement`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_replace( - &self, - filter: Document, - replacement: T, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_and_replace( - filter, - replacement, - options.into(), - )) - } - - /// Atomically finds up to one document in the collection matching `filter` and replaces it with - /// `replacement` using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn find_one_and_replace_with_session( - &self, - filter: Document, - replacement: T, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_collection.find_one_and_replace_with_session( - filter, - replacement, - options.into(), - &mut session.async_client_session, - )) - } -} - -impl Collection -where - T: Serialize, -{ - /// Inserts the documents in `docs` into the collection. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn insert_many( - &self, - docs: impl IntoIterator>, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.insert_many(docs, options.into())) - } - - /// Inserts the documents in `docs` into the collection using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn insert_many_with_session( - &self, - docs: impl IntoIterator>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.insert_many_with_session( - docs, - options.into(), - &mut session.async_client_session, - )) - } - - /// Inserts `doc` into the collection. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn insert_one( - &self, - doc: impl Borrow, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_collection - .insert_one(doc.borrow(), options.into()), - ) - } - - /// Inserts `doc` into the collection using the provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn insert_one_with_session( - &self, - doc: impl Borrow, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.insert_one_with_session( - doc.borrow(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Replaces up to one document matching `query` in the collection with `replacement`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn replace_one( - &self, - query: Document, - replacement: impl Borrow, - options: impl Into>, - ) -> Result { - runtime::block_on(self.async_collection.replace_one( - query, - replacement.borrow(), - options.into(), - )) - } - - /// Replaces up to one document matching `query` in the collection with `replacement` using the - /// provided `ClientSession`. - /// - /// This operation will retry once upon failure if the connection and encountered error support - /// retryability. See the documentation - /// [here](https://www.mongodb.com/docs/manual/core/retryable-writes/) for more information on - /// retryable writes. - pub fn replace_one_with_session( - &self, - query: Document, - replacement: impl Borrow, - options: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_collection.replace_one_with_session( - query, - replacement.borrow(), - options.into(), - &mut session.async_client_session, - )) - } } diff --git a/src/sync/cursor.rs b/src/sync/cursor.rs index 37fe85e8e..69baaef2c 100644 --- a/src/sync/cursor.rs +++ b/src/sync/cursor.rs @@ -5,7 +5,6 @@ use super::ClientSession; use crate::{ bson::{Document, RawDocument}, error::Result, - runtime, Cursor as AsyncCursor, SessionCursor as AsyncSessionCursor, SessionCursorStream, @@ -32,12 +31,12 @@ use crate::{ /// documents it yields using a for loop: /// /// ```rust -/// # use mongodb::{bson::Document, sync::Client, error::Result}; +/// # use mongodb::{bson::{doc, Document}, sync::Client, error::Result}; /// # /// # fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; /// # let coll = client.database("foo").collection::("bar"); -/// # let mut cursor = coll.find(None, None)?; +/// # let mut cursor = coll.find(doc! {}).run()?; /// # /// for doc in cursor { /// println!("{}", doc?) @@ -61,7 +60,7 @@ use crate::{ /// # fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; /// # let coll = client.database("foo").collection("bar"); -/// # let cursor = coll.find(Some(doc! { "x": 1 }), None)?; +/// # let cursor = coll.find(doc! { "x": 1 }).run()?; /// # /// let results: Vec> = cursor.collect(); /// # Ok(()) @@ -93,11 +92,11 @@ impl Cursor { /// calling [`Cursor::advance`] first or after [`Cursor::advance`] returns an error / false. /// /// ``` - /// # use mongodb::{sync::Client, bson::Document, error::Result}; + /// # use mongodb::{sync::Client, bson::{Document, doc}, error::Result}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find(None, None)?; + /// let mut cursor = coll.find(doc! {}).run()?; /// while cursor.advance()? { /// println!("{:?}", cursor.deserialize_current()?); /// } @@ -105,7 +104,7 @@ impl Cursor { /// # } /// ``` pub fn advance(&mut self) -> Result { - runtime::block_on(self.async_cursor.advance()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_cursor.advance()) } /// Returns a reference to the current result in the cursor. @@ -116,11 +115,11 @@ impl Cursor { /// or without calling [`Cursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{sync::Client, bson::Document, error::Result}; + /// # use mongodb::{sync::Client, bson::{doc, Document}, error::Result}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find(None, None)?; + /// let mut cursor = coll.find(doc! {}).run()?; /// while cursor.advance()? { /// println!("{:?}", cursor.current()); /// } @@ -139,7 +138,7 @@ impl Cursor { /// true or without calling [`Cursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{sync::Client, error::Result}; + /// # use mongodb::{sync::Client, error::Result, bson::doc}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; /// # let db = client.database("foo"); @@ -152,7 +151,7 @@ impl Cursor { /// } /// /// let coll = db.collection::("cat"); - /// let mut cursor = coll.find(None, None)?; + /// let mut cursor = coll.find(doc! {}).run()?; /// while cursor.advance()? { /// println!("{:?}", cursor.deserialize_current()?); /// } @@ -174,7 +173,7 @@ where type Item = Result; fn next(&mut self) -> Option { - runtime::block_on(self.async_cursor.next()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_cursor.next()) } } @@ -182,13 +181,13 @@ where /// one. To iterate, retrieve a [`SessionCursorIter]` using [`SessionCursor::iter`]: /// /// ```rust -/// # use mongodb::{bson::Document, sync::Client, error::Result}; +/// # use mongodb::{bson::{doc, Document}, sync::Client, error::Result}; /// # /// # fn do_stuff() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://example.com")?; -/// # let mut session = client.start_session(None)?; +/// # let mut session = client.start_session().run()?; /// # let coll = client.database("foo").collection::("bar"); -/// # let mut cursor = coll.find_with_session(None, None, &mut session)?; +/// # let mut cursor = coll.find(doc! {}).session(&mut session).run()?; /// # /// for doc in cursor.iter(&mut session) { /// println!("{}", doc?) @@ -221,12 +220,12 @@ impl SessionCursor { /// calling [`Cursor::advance`] first or after [`Cursor::advance`] returns an error / false. /// /// ``` - /// # use mongodb::{sync::Client, bson::Document, error::Result}; + /// # use mongodb::{sync::Client, bson::{doc, Document}, error::Result}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; - /// # let mut session = client.start_session(None)?; + /// # let mut session = client.start_session().run()?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find_with_session(None, None, &mut session)?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).run()?; /// while cursor.advance(&mut session)? { /// println!("{:?}", cursor.deserialize_current()?); /// } @@ -234,7 +233,8 @@ impl SessionCursor { /// # } /// ``` pub fn advance(&mut self, session: &mut ClientSession) -> Result { - runtime::block_on(self.async_cursor.advance(&mut session.async_client_session)) + crate::sync::TOKIO_RUNTIME + .block_on(self.async_cursor.advance(&mut session.async_client_session)) } /// Returns a reference to the current result in the cursor. @@ -245,12 +245,12 @@ impl SessionCursor { /// or without calling [`Cursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{sync::Client, bson::Document, error::Result}; + /// # use mongodb::{sync::Client, bson::{doc, Document}, error::Result}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; - /// # let mut session = client.start_session(None)?; + /// # let mut session = client.start_session().run()?; /// # let coll = client.database("stuff").collection::("stuff"); - /// let mut cursor = coll.find_with_session(None, None, &mut session)?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).run()?; /// while cursor.advance(&mut session)? { /// println!("{:?}", cursor.current()); /// } @@ -269,10 +269,10 @@ impl SessionCursor { /// true or without calling [`Cursor::advance`] at all may result in a panic. /// /// ``` - /// # use mongodb::{sync::Client, error::Result}; + /// # use mongodb::{sync::Client, error::Result, bson::doc}; /// # fn foo() -> Result<()> { /// # let client = Client::with_uri_str("mongodb://localhost:27017")?; - /// # let mut session = client.start_session(None)?; + /// # let mut session = client.start_session().run()?; /// # let db = client.database("foo"); /// use serde::Deserialize; /// @@ -283,7 +283,7 @@ impl SessionCursor { /// } /// /// let coll = db.collection::("cat"); - /// let mut cursor = coll.find_with_session(None, None, &mut session)?; + /// let mut cursor = coll.find(doc! {}).session(&mut session).run()?; /// while cursor.advance(&mut session)? { /// println!("{:?}", cursor.deserialize_current()?); /// } @@ -320,16 +320,15 @@ where /// functionality of `Iterator` is not needed. /// /// ``` - /// # use bson::{doc, Document}; - /// # use mongodb::sync::Client; + /// # use mongodb::{bson::{doc, Document}, sync::Client}; /// # fn foo() -> mongodb::error::Result<()> { /// # let client = Client::with_uri_str("foo")?; /// # let coll = client.database("foo").collection::("bar"); /// # let other_coll = coll.clone(); - /// # let mut session = client.start_session(None)?; - /// let mut cursor = coll.find_with_session(doc! { "x": 1 }, None, &mut session)?; + /// # let mut session = client.start_session().run()?; + /// let mut cursor = coll.find(doc! { "x": 1 }).session(&mut session).run()?; /// while let Some(doc) = cursor.next(&mut session).transpose()? { - /// other_coll.insert_one_with_session(doc, None, &mut session)?; + /// other_coll.insert_one(doc).session(&mut session).run()?; /// } /// # Ok::<(), mongodb::error::Error>(()) /// # } @@ -357,6 +356,6 @@ where type Item = Result; fn next(&mut self) -> Option { - runtime::block_on(self.async_stream.next()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.next()) } } diff --git a/src/sync/db.rs b/src/sync/db.rs index c97a03fe7..970470a61 100644 --- a/src/sync/db.rs +++ b/src/sync/db.rs @@ -1,31 +1,14 @@ use std::fmt::Debug; -use super::{ - gridfs::GridFsBucket, - ChangeStream, - ClientSession, - Collection, - Cursor, - SessionChangeStream, - SessionCursor, -}; +use super::{gridfs::GridFsBucket, Collection}; use crate::{ - bson::Document, - change_stream::{event::ChangeStreamEvent, options::ChangeStreamOptions}, - error::Result, options::{ - AggregateOptions, CollectionOptions, - CreateCollectionOptions, - DropDatabaseOptions, GridFsBucketOptions, - ListCollectionsOptions, ReadConcern, SelectionCriteria, WriteConcern, }, - results::CollectionSpecification, - runtime, Database as AsyncDatabase, }; @@ -63,7 +46,7 @@ use crate::{ /// ``` #[derive(Debug, Clone)] pub struct Database { - async_database: AsyncDatabase, + pub(crate) async_database: AsyncDatabase, } impl Database { @@ -97,7 +80,7 @@ impl Database { /// /// This method does not send or receive anything across the wire to the database, so it can be /// used repeatedly without incurring any costs from I/O. - pub fn collection(&self, name: &str) -> Collection { + pub fn collection(&self, name: &str) -> Collection { Collection::new(self.async_database.collection(name)) } @@ -107,7 +90,7 @@ impl Database { /// /// This method does not send or receive anything across the wire to the database, so it can be /// used repeatedly without incurring any costs from I/O. - pub fn collection_with_options( + pub fn collection_with_options( &self, name: &str, options: CollectionOptions, @@ -115,217 +98,6 @@ impl Database { Collection::new(self.async_database.collection_with_options(name, options)) } - /// Drops the database, deleting all data, collections, users, and indexes stored in it. - pub fn drop(&self, options: impl Into>) -> Result<()> { - runtime::block_on(self.async_database.drop(options.into())) - } - - /// Drops the database, deleting all data, collections, users, and indexes stored in it using - /// the provided `ClientSession`. - pub fn drop_with_session( - &self, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - runtime::block_on( - self.async_database - .drop_with_session(options.into(), &mut session.async_client_session), - ) - } - - /// Gets information about each of the collections in the database. The cursor will yield a - /// document pertaining to each collection in the database. - pub fn list_collections( - &self, - filter: impl Into>, - options: impl Into>, - ) -> Result> { - runtime::block_on( - self.async_database - .list_collections(filter.into(), options.into()), - ) - .map(Cursor::new) - } - - /// Gets information about each of the collections in the database using the provided - /// `ClientSession`. The cursor will yield a document pertaining to each collection in the - /// database. - pub fn list_collections_with_session( - &self, - filter: impl Into>, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on(self.async_database.list_collections_with_session( - filter.into(), - options.into(), - &mut session.async_client_session, - )) - .map(SessionCursor::new) - } - - /// Gets the names of the collections in the database. - pub fn list_collection_names( - &self, - filter: impl Into>, - ) -> Result> { - runtime::block_on(self.async_database.list_collection_names(filter.into())) - } - - /// Gets the names of the collections in the database using the provided `ClientSession`. - pub fn list_collection_names_with_session( - &self, - filter: impl Into>, - session: &mut ClientSession, - ) -> Result> { - runtime::block_on( - self.async_database.list_collection_names_with_session( - filter.into(), - &mut session.async_client_session, - ), - ) - } - - /// Creates a new collection in the database with the given `name` and `options`. - /// - /// Note that MongoDB creates collections implicitly when data is inserted, so this method is - /// not needed if no special options are required. - pub fn create_collection( - &self, - name: impl AsRef, - options: impl Into>, - ) -> Result<()> { - runtime::block_on( - self.async_database - .create_collection(name.as_ref(), options.into()), - ) - } - - /// Creates a new collection in the database with the given `name` and `options` using the - /// provided `ClientSession`. - /// - /// Note that MongoDB creates collections implicitly when data is inserted, so this method is - /// not needed if no special options are required. - pub fn create_collection_with_session( - &self, - name: impl AsRef, - options: impl Into>, - session: &mut ClientSession, - ) -> Result<()> { - runtime::block_on(self.async_database.create_collection_with_session( - name.as_ref(), - options.into(), - &mut session.async_client_session, - )) - } - - /// Runs a database-level command. - /// - /// Note that no inspection is done on `doc`, so the command will not use the database's default - /// read concern or write concern. If specific read concern or write concern is desired, it must - /// be specified manually. - pub fn run_command( - &self, - command: Document, - selection_criteria: impl Into>, - ) -> Result { - runtime::block_on( - self.async_database - .run_command(command, selection_criteria.into()), - ) - } - - /// Runs a database-level command using the provided `ClientSession`. - /// - /// Note that no inspection is done on `doc`, so the command will not use the database's default - /// read concern or write concern. If specific read concern or write concern is desired, it must - /// be specified manually. - pub fn run_command_with_session( - &self, - command: Document, - selection_criteria: impl Into>, - session: &mut ClientSession, - ) -> Result { - runtime::block_on(self.async_database.run_command_with_session( - command, - selection_criteria.into(), - &mut session.async_client_session, - )) - } - - /// Runs an aggregation operation. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub fn aggregate( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result> { - let pipeline: Vec = pipeline.into_iter().collect(); - runtime::block_on(self.async_database.aggregate(pipeline, options.into())).map(Cursor::new) - } - - /// Runs an aggregation operation using the provided `ClientSession`. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/aggregation/) for more - /// information on aggregations. - pub fn aggregate_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result> { - let pipeline: Vec = pipeline.into_iter().collect(); - runtime::block_on(self.async_database.aggregate_with_session( - pipeline, - options.into(), - &mut session.async_client_session, - )) - .map(SessionCursor::new) - } - - /// Starts a new [`ChangeStream`](change_stream/struct.ChangeStream.html) that receives events - /// for all changes in this database. The stream does not observe changes from system - /// collections and cannot be started on "config", "local" or "admin" databases. - /// - /// See the documentation [here](https://www.mongodb.com/docs/manual/changeStreams/) on change - /// streams. - /// - /// Change streams require either a "majority" read concern or no read - /// concern. Anything else will cause a server error. - /// - /// Note that using a `$project` stage to remove any of the `_id`, `operationType` or `ns` - /// fields will cause an error. The driver requires these fields to support resumability. For - /// more information on resumability, see the documentation for - /// [`ChangeStream`](change_stream/struct.ChangeStream.html) - /// - /// If the pipeline alters the structure of the returned events, the parsed type will need to be - /// changed via [`ChangeStream::with_type`]. - pub fn watch( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - ) -> Result>> { - runtime::block_on(self.async_database.watch(pipeline, options)).map(ChangeStream::new) - } - - /// Starts a new [`SessionChangeStream`] that receives events for all changes in this database - /// using the provided [`ClientSession`]. See [`Database::watch`] for more information. - pub fn watch_with_session( - &self, - pipeline: impl IntoIterator, - options: impl Into>, - session: &mut ClientSession, - ) -> Result>> { - runtime::block_on(self.async_database.watch_with_session( - pipeline, - options, - &mut session.async_client_session, - )) - .map(SessionChangeStream::new) - } - /// Creates a new [`GridFsBucket`] in the database with the given options. pub fn gridfs_bucket(&self, options: impl Into>) -> GridFsBucket { GridFsBucket::new(self.async_database.gridfs_bucket(options)) diff --git a/src/sync/gridfs.rs b/src/sync/gridfs.rs index 711c60843..88389dddc 100644 --- a/src/sync/gridfs.rs +++ b/src/sync/gridfs.rs @@ -4,24 +4,15 @@ use std::io::{Read, Write}; use futures_util::{AsyncReadExt, AsyncWriteExt}; -use super::Cursor; use crate::{ - bson::{Bson, Document}, + bson::Bson, error::Result, gridfs::{ GridFsBucket as AsyncGridFsBucket, GridFsDownloadStream as AsyncGridFsDownloadStream, GridFsUploadStream as AsyncGridFsUploadStream, }, - options::{ - GridFsDownloadByNameOptions, - GridFsFindOptions, - GridFsUploadOptions, - ReadConcern, - SelectionCriteria, - WriteConcern, - }, - runtime, + options::{ReadConcern, SelectionCriteria, WriteConcern}, }; pub use crate::gridfs::FilesCollectionDocument; @@ -36,7 +27,7 @@ pub use crate::gridfs::FilesCollectionDocument; /// `GridFsBucket` uses [`std::sync::Arc`] internally, so it can be shared safely across threads or /// async tasks. pub struct GridFsBucket { - async_bucket: AsyncGridFsBucket, + pub(crate) async_bucket: AsyncGridFsBucket, } impl GridFsBucket { @@ -58,33 +49,6 @@ impl GridFsBucket { pub fn selection_criteria(&self) -> Option<&SelectionCriteria> { self.async_bucket.selection_criteria() } - - /// Deletes the [`FilesCollectionDocument`] with the given `id` and its associated chunks from - /// this bucket. This method returns an error if the `id` does not match any files in the - /// bucket. - pub fn delete(&self, id: Bson) -> Result<()> { - runtime::block_on(self.async_bucket.delete(id)) - } - - /// Finds the [`FilesCollectionDocument`]s in the bucket matching the given `filter`. - pub fn find( - &self, - filter: Document, - options: impl Into>, - ) -> Result> { - runtime::block_on(self.async_bucket.find(filter, options)).map(Cursor::new) - } - - /// Renames the file with the given `id` to `new_filename`. This method returns an error if the - /// `id` does not match any files in the bucket. - pub fn rename(&self, id: Bson, new_filename: impl AsRef) -> Result<()> { - runtime::block_on(self.async_bucket.rename(id, new_filename)) - } - - /// Removes all of the files and their associated chunks from this bucket. - pub fn drop(&self) -> Result<()> { - runtime::block_on(self.async_bucket.drop()) - } } /// A stream from which a file stored in a GridFS bucket can be downloaded. @@ -98,55 +62,30 @@ impl GridFsBucket { /// use std::io::Read; /// /// let mut buf = Vec::new(); -/// let mut download_stream = bucket.open_download_stream(id)?; +/// let mut download_stream = bucket.open_download_stream(id).run()?; /// download_stream.read_to_end(&mut buf)?; /// # Ok(()) /// # } /// ``` +/// +/// If the destination is a local file (or other `Write` byte sink), the contents of the stream +/// can be efficiently written to it with [`std::io::copy`]. pub struct GridFsDownloadStream { async_stream: AsyncGridFsDownloadStream, } impl Read for GridFsDownloadStream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - runtime::block_on(self.async_stream.read(buf)) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.read(buf)) } } impl GridFsDownloadStream { - fn new(async_stream: AsyncGridFsDownloadStream) -> Self { + pub(crate) fn new(async_stream: AsyncGridFsDownloadStream) -> Self { Self { async_stream } } } -// Download API -impl GridFsBucket { - /// Opens and returns a [`GridFsDownloadStream`] from which the application can read - /// the contents of the stored file specified by `id`. - pub fn open_download_stream(&self, id: Bson) -> Result { - runtime::block_on(self.async_bucket.open_download_stream(id)).map(GridFsDownloadStream::new) - } - - /// Opens and returns a [`GridFsDownloadStream`] from which the application can read - /// the contents of the stored file specified by `filename`. - /// - /// If there are multiple files in the bucket with the given filename, the `revision` in the - /// options provided is used to determine which one to download. See the documentation for - /// [`GridFsDownloadByNameOptions`] for details on how to specify a revision. If no revision is - /// provided, the file with `filename` most recently uploaded will be downloaded. - pub fn open_download_stream_by_name( - &self, - filename: impl AsRef, - options: impl Into>, - ) -> Result { - runtime::block_on( - self.async_bucket - .open_download_stream_by_name(filename, options), - ) - .map(GridFsDownloadStream::new) - } -} - /// A stream to which bytes can be written to be uploaded to a GridFS bucket. /// /// # Uploading to the Stream @@ -163,13 +102,16 @@ impl GridFsBucket { /// use std::io::Write; /// /// let bytes = vec![0u8; 100]; -/// let mut upload_stream = bucket.open_upload_stream("example_file", None); +/// let mut upload_stream = bucket.open_upload_stream("example_file").run()?; /// upload_stream.write_all(&bytes[..])?; /// upload_stream.close()?; /// # Ok(()) /// # } /// ``` /// +/// If the data is a local file (or other `Read` byte source), its contents can be efficiently +/// written to the stream with [`std::io::copy`]. +/// /// # Aborting the Stream /// A stream can be aborted by calling the `abort` method. This will remove any chunks associated /// with the stream from the chunks collection. It is an error to write to, abort, or close the @@ -181,7 +123,7 @@ impl GridFsBucket { /// use std::io::Write; /// /// let bytes = vec![0u8; 100]; -/// let mut upload_stream = bucket.open_upload_stream("example_file", None); +/// let mut upload_stream = bucket.open_upload_stream("example_file").run()?; /// upload_stream.write_all(&bytes[..])?; /// upload_stream.abort()?; /// # Ok(()) @@ -206,6 +148,10 @@ pub struct GridFsUploadStream { } impl GridFsUploadStream { + pub(crate) fn new(async_stream: AsyncGridFsUploadStream) -> Self { + Self { async_stream } + } + /// Gets the stream's unique [`Bson`] identifier. This value will be the `id` field for the /// [`FilesCollectionDocument`] uploaded to the files collection when the stream is closed. pub fn id(&self) -> &Bson { @@ -217,54 +163,23 @@ impl GridFsUploadStream { /// these steps, the chunks associated with this stream are deleted. It is an error to write to, /// abort, or close the stream after this method has been called. pub fn close(&mut self) -> std::io::Result<()> { - runtime::block_on(self.async_stream.close()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.close()) } /// Aborts the stream, discarding any chunks that have already been written to the chunks /// collection. Once this method has been called, it is an error to attempt to write to, abort, /// or close the stream. pub fn abort(&mut self) -> Result<()> { - runtime::block_on(self.async_stream.abort()) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.abort()) } } impl Write for GridFsUploadStream { fn write(&mut self, buf: &[u8]) -> std::io::Result { - runtime::block_on(self.async_stream.write(buf)) + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.write(buf)) } fn flush(&mut self) -> std::io::Result<()> { - runtime::block_on(self.async_stream.flush()) - } -} - -// Upload API -impl GridFsBucket { - /// Creates and returns a [`GridFsUploadStream`] that the application can write the contents of - /// the file to. This method generates a unique [`ObjectId`](crate::bson::oid::ObjectId) for the - /// corresponding [`FilesCollectionDocument`]'s `id` field that can be accessed via the - /// stream's `id` method. - pub fn open_upload_stream( - &self, - filename: impl AsRef, - options: impl Into>, - ) -> GridFsUploadStream { - let async_stream = self.async_bucket.open_upload_stream(filename, options); - GridFsUploadStream { async_stream } - } - - /// Opens a [`GridFsUploadStream`] that the application can write the contents of the file to. - /// The provided `id` will be used for the corresponding [`FilesCollectionDocument`]'s `id` - /// field. - pub fn open_upload_stream_with_id( - &self, - id: Bson, - filename: impl AsRef, - options: impl Into>, - ) -> GridFsUploadStream { - let async_stream = self - .async_bucket - .open_upload_stream_with_id(id, filename, options); - GridFsUploadStream { async_stream } + crate::sync::TOKIO_RUNTIME.block_on(self.async_stream.flush()) } } diff --git a/src/sync/mod.rs b/src/sync/mod.rs deleted file mode 100644 index 1fc498355..000000000 --- a/src/sync/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Contains the sync API. This is only available when the `sync` feature is enabled. - -mod change_stream; -mod client; -mod coll; -mod cursor; -mod db; -pub mod gridfs; - -#[cfg(test)] -mod test; - -pub use change_stream::{ChangeStream, SessionChangeStream}; -pub use client::{session::ClientSession, Client}; -pub use coll::Collection; -pub use cursor::{Cursor, SessionCursor, SessionCursorIter}; -pub use db::Database; - -#[cfg(feature = "tokio-sync")] -lazy_static::lazy_static! { - pub(crate) static ref TOKIO_RUNTIME: tokio::runtime::Runtime = { - match tokio::runtime::Runtime::new() { - Ok(runtime) => runtime, - Err(err) => panic!("Error occurred when starting the underlying async runtime: {}", err) - } - }; -} diff --git a/src/sync/test.rs b/src/sync/test.rs index 590c6f2f7..032b29a3b 100644 --- a/src/sync/test.rs +++ b/src/sync/test.rs @@ -3,10 +3,9 @@ use std::{ io::{Read, Write}, }; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; -use tokio::sync::RwLockReadGuard; use crate::{ bson::{doc, Document}, @@ -20,33 +19,36 @@ use crate::{ ServerAddress, WriteConcern, }, - runtime, sync::{Client, ClientSession, Collection}, - test::{TestClient as AsyncTestClient, LOCK}, + test::transactions_supported, + Client as AsyncClient, }; fn init_db_and_coll(client: &Client, db_name: &str, coll_name: &str) -> Collection { let coll = client.database(db_name).collection(coll_name); - coll.drop(None).unwrap(); + coll.drop().run().unwrap(); coll } -fn init_db_and_typed_coll(client: &Client, db_name: &str, coll_name: &str) -> Collection { +fn init_db_and_typed_coll( + client: &Client, + db_name: &str, + coll_name: &str, +) -> Collection { let coll = client.database(db_name).collection(coll_name); - coll.drop(None).unwrap(); + coll.drop().run().unwrap(); coll } -lazy_static! { - static ref CLIENT_OPTIONS: ClientOptions = - runtime::block_on(async { crate::test::CLIENT_OPTIONS.get().await.clone() }); -} +static CLIENT_OPTIONS: Lazy = Lazy::new(|| { + crate::sync::TOKIO_RUNTIME.block_on(async { crate::test::get_client_options().await.clone() }) +}); #[test] fn client_options() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - - let mut options = ClientOptions::parse("mongodb://localhost:27017/").unwrap(); + let mut options = ClientOptions::parse("mongodb://localhost:27017/") + .run() + .unwrap(); options.original_uri.take(); @@ -64,19 +66,19 @@ fn client_options() { #[test] #[function_name::named] fn client() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); client .database(function_name!()) .collection(function_name!()) - .insert_one(Document::new(), None) + .insert_one(Document::new()) + .run() .expect("insert should succeed"); let db_names = client - .list_database_names(None, None) + .list_database_names() + .run() .expect("list_database_names should succeed"); assert!(db_names.contains(&function_name!().to_string())); } @@ -85,7 +87,6 @@ fn client() { fn default_database() { // here we just test default database name matched, the database interactive logic // is tested in `database`. - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); @@ -113,19 +114,19 @@ fn default_database() { #[test] #[function_name::named] fn database() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); let db = client.database(function_name!()); let coll = init_db_and_coll(&client, function_name!(), function_name!()); - coll.insert_one(doc! { "x": 1 }, None) + coll.insert_one(doc! { "x": 1 }) + .run() .expect("insert should succeed"); let coll_names = db - .list_collection_names(None) + .list_collection_names() + .run() .expect("list_database_names should succeed"); assert!(coll_names.contains(&function_name!().to_string())); @@ -137,7 +138,8 @@ fn database() { doc! { "$project": { "_id": 0, "dummy": 1 } }, ]; let cursor = admin_db - .aggregate(pipeline, None) + .aggregate(pipeline) + .run() .expect("aggregate should succeed"); let results: Vec = cursor .collect::>>() @@ -158,18 +160,18 @@ fn database() { #[test] #[function_name::named] fn collection() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); let coll = init_db_and_coll(&client, function_name!(), function_name!()); - coll.insert_one(doc! { "x": 1 }, None) + coll.insert_one(doc! { "x": 1 }) + .run() .expect("insert should succeed"); - let find_options = FindOptions::builder().projection(doc! { "_id": 0 }).build(); let cursor = coll - .find(doc! { "x": 1 }, find_options) + .find(doc! { "x": 1 }) + .projection(doc! { "_id": 0 }) + .run() .expect("find should succeed"); let results = cursor .collect::>>() @@ -181,7 +183,8 @@ fn collection() { doc! { "$project": { "_id" : 0 } }, ]; let cursor = coll - .aggregate(pipeline, None) + .aggregate(pipeline) + .run() .expect("aggregate should succeed"); let results = cursor .collect::>>() @@ -211,8 +214,6 @@ fn collection() { #[test] #[function_name::named] fn typed_collection() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); let coll = init_db_and_typed_coll(&client, function_name!(), function_name!()); @@ -227,18 +228,13 @@ fn typed_collection() { str: "hello".into(), }; - assert!(coll.insert_one(my_type, None).is_ok()); + assert!(coll.insert_one(my_type).run().is_ok()); } #[test] #[function_name::named] fn transactions() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - - let should_skip = runtime::block_on(async { - let test_client = AsyncTestClient::new().await; - !test_client.supports_transactions() - }); + let should_skip = crate::sync::TOKIO_RUNTIME.block_on(async { transactions_supported().await }); if should_skip { return; } @@ -256,7 +252,7 @@ fn transactions() { if error.contains_label(TRANSIENT_TRANSACTION_ERROR) { continue; } else { - session.abort_transaction()?; + session.abort_transaction().run()?; return Err(error); } } @@ -267,27 +263,30 @@ fn transactions() { let options = CLIENT_OPTIONS.clone(); let client = Client::with_options(options).expect("client creation should succeed"); let mut session = client - .start_session(None) + .start_session() + .run() .expect("session creation should succeed"); let coll = init_db_and_typed_coll(&client, function_name!(), function_name!()); client .database(function_name!()) - .create_collection(function_name!(), None) + .create_collection(function_name!()) + .run() .expect("create collection should succeed"); session - .start_transaction(None) + .start_transaction() + .run() .expect("start transaction should succeed"); run_transaction_with_retry(&mut session, |s| { - coll.insert_one_with_session(doc! { "x": 1 }, None, s)?; + coll.insert_one(doc! { "x": 1 }).session(s).run()?; Ok(()) }) .unwrap(); loop { - match session.commit_transaction() { + match session.commit_transaction().run() { Ok(()) => { break; } @@ -302,23 +301,23 @@ fn transactions() { } session - .start_transaction(None) + .start_transaction() + .run() .expect("start transaction should succeed"); run_transaction_with_retry(&mut session, |s| { - coll.insert_one_with_session(doc! { "x": 1 }, None, s)?; + coll.insert_one(doc! { "x": 1 }).session(s).run()?; Ok(()) }) .unwrap(); session .abort_transaction() + .run() .expect("abort transaction should succeed"); } #[test] #[function_name::named] fn collection_generic_bounds() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - #[derive(Deserialize)] struct Foo; @@ -332,7 +331,7 @@ fn collection_generic_bounds() { let coll: Collection = client .database(function_name!()) .collection(function_name!()); - let _result: Result> = coll.find_one(None, None); + let _result: Result> = coll.find_one(doc! {}).run(); #[derive(Serialize)] struct Bar; @@ -341,13 +340,11 @@ fn collection_generic_bounds() { let coll: Collection = client .database(function_name!()) .collection(function_name!()); - let _result = coll.insert_one(Bar {}, None); + let _result = coll.insert_one(Bar {}); } #[test] fn borrowed_deserialization() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let client = Client::with_options(CLIENT_OPTIONS.clone()).expect("client creation should succeed"); @@ -387,13 +384,17 @@ fn borrowed_deserialization() { Doc { id: 5, foo: "1" }, ]; - coll.insert_many(&docs, None).unwrap(); + coll.insert_many(&docs).run().unwrap(); let options = FindOptions::builder() .batch_size(2) .sort(doc! { "_id": 1 }) .build(); - let mut cursor = coll.find(None, options.clone()).unwrap(); + let mut cursor = coll + .find(doc! {}) + .with_options(options.clone()) + .run() + .unwrap(); let mut i = 0; while cursor.advance().unwrap() { @@ -402,8 +403,13 @@ fn borrowed_deserialization() { i += 1; } - let mut session = client.start_session(None).unwrap(); - let mut cursor = coll.find_with_session(None, options, &mut session).unwrap(); + let mut session = client.start_session().run().unwrap(); + let mut cursor = coll + .find(doc! {}) + .with_options(options) + .session(&mut session) + .run() + .unwrap(); let mut i = 0; while cursor.advance(&mut session).unwrap() { @@ -418,23 +424,23 @@ fn mixed_sync_and_async() -> Result<()> { const DB_NAME: &str = "mixed_sync_and_async"; const COLL_NAME: &str = "test"; - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); - let sync_client = Client::with_options(CLIENT_OPTIONS.clone())?; - let async_client = runtime::block_on(async { AsyncTestClient::new().await }); + let async_client = crate::sync::TOKIO_RUNTIME.block_on(async { AsyncClient::for_test().await }); let sync_db = sync_client.database(DB_NAME); - sync_db.drop(None)?; + sync_db.drop().run()?; sync_db .collection::(COLL_NAME) - .insert_one(doc! { "a": 1 }, None)?; - let mut found = runtime::block_on(async { - async_client - .database(DB_NAME) - .collection::(COLL_NAME) - .find_one(doc! {}, None) - .await - })? - .unwrap(); + .insert_one(doc! { "a": 1 }) + .run()?; + let mut found = crate::sync::TOKIO_RUNTIME + .block_on(async { + async_client + .database(DB_NAME) + .collection::(COLL_NAME) + .find_one(doc! {}) + .await + })? + .unwrap(); found.remove("_id"); assert_eq!(found, doc! { "a": 1 }); @@ -443,19 +449,19 @@ fn mixed_sync_and_async() -> Result<()> { #[test] fn gridfs() { - let _guard: RwLockReadGuard<()> = runtime::block_on(async { LOCK.run_concurrently().await }); let client = Client::with_options(CLIENT_OPTIONS.clone()).unwrap(); let bucket = client.database("gridfs").gridfs_bucket(None); let upload = vec![0u8; 100]; let mut download = vec![]; - let mut upload_stream = bucket.open_upload_stream("sync gridfs", None); + let mut upload_stream = bucket.open_upload_stream("sync gridfs").run().unwrap(); upload_stream.write_all(&upload[..]).unwrap(); upload_stream.close().unwrap(); let mut download_stream = bucket .open_download_stream(upload_stream.id().clone()) + .run() .unwrap(); download_stream.read_to_end(&mut download).unwrap(); diff --git a/src/test.rs b/src/test.rs new file mode 100644 index 000000000..035247cfc --- /dev/null +++ b/src/test.rs @@ -0,0 +1,378 @@ +#![allow(clippy::cast_possible_truncation)] +#![allow(clippy::cast_possible_wrap)] + +#[cfg(feature = "dns-resolver")] +#[path = "test/atlas_connectivity.rs"] +mod atlas_connectivity_skip_ci; // requires Atlas URI environment variables set +mod auth; +mod bulk_write; +mod change_stream; +mod client; +mod coll; +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +mod compression; +#[cfg(feature = "in-use-encryption")] +#[path = "test/csfle.rs"] +pub(crate) mod csfle_skip_local; // see modules for requirements +mod cursor; +mod db; +mod documentation_examples; +#[path = "test/happy_eyeballs.rs"] +mod happy_eyeballs_skip_ci; // requires happy eyeballs server +mod index_management; +mod lambda_examples; +pub(crate) mod spec; +mod timeseries; +pub(crate) mod util; + +#[cfg(feature = "in-use-encryption")] +pub(crate) use self::csfle_skip_local as csfle; +pub(crate) use self::{ + spec::{run_spec_test, RunOn, Serverless, Topology}, + util::{ + assert_matches, + eq_matches, + file_level_log, + log_uncaptured, + Event, + EventClient, + MatchErrExt, + Matchable, + TestClient, + }, +}; + +use futures::FutureExt; +use home::home_dir; +use once_cell::sync::Lazy; +use tokio::sync::OnceCell; + +#[cfg(feature = "tracing-unstable")] +use self::util::TracingHandler; +use crate::{ + bson::{doc, Document}, + client::{ + auth::Credential, + options::{ServerApi, ServerApiVersion}, + }, + hello::HelloCommandResponse, + options::{ + oidc::{Callback, IdpServerResponse}, + AuthMechanism, + ClientOptions, + ServerAddress, + }, + test::spec::oidc::get_access_token_test_user_1, + Client, +}; +use std::{fs::read_to_string, str::FromStr}; + +pub(crate) async fn get_client_options() -> &'static ClientOptions { + static CLIENT_OPTIONS: OnceCell = OnceCell::const_new(); + CLIENT_OPTIONS + .get_or_init(|| async { + let mut options = ClientOptions::parse(&*DEFAULT_URI).await.unwrap(); + update_options_for_testing(&mut options); + options + }) + .await +} +pub(crate) async fn auth_enabled() -> bool { + get_client_options().await.credential.is_some() +} + +struct TestClientMetadata { + server_version: semver::Version, + hello_response: HelloCommandResponse, + server_parameters: Document, +} +async fn get_test_client_metadata() -> &'static TestClientMetadata { + static TEST_CLIENT_METADATA: OnceCell = OnceCell::const_new(); + TEST_CLIENT_METADATA + .get_or_init(|| async { + let mut client_options = get_client_options().await.clone(); + // OIDC admin credentials are required to call getParameter when running with OIDC + // authentication. + if let (Ok(username), Ok(password)) = ( + std::env::var("OIDC_ADMIN_USER"), + std::env::var("OIDC_ADMIN_PWD"), + ) { + let credential = Credential::builder() + .username(username) + .password(password) + .build(); + client_options.credential = Some(credential); + } + let client = Client::for_test().options(client_options).await; + + let build_info = client + .database("test") + .run_command(doc! { "buildInfo": 1 }) + .await + .unwrap(); + let mut server_version = + semver::Version::parse(build_info.get_str("version").unwrap()).unwrap(); + // ignore whether the version is a prerelease + server_version.pre = semver::Prerelease::EMPTY; + + let hello_response = client.hello().await.unwrap(); + + let server_parameters = client + .database("admin") + .run_command(doc! { "getParameter": "*" }) + .await + .unwrap(); + + TestClientMetadata { + server_version, + hello_response, + server_parameters, + } + }) + .await +} + +// Utility functions to check server version requirements. All but server_version_matches ignore +// the server's patch version; specify a requirement string to server_version_matches for a +// patch-sensitive comparison. +pub(crate) async fn server_version_eq(major: u64, minor: u64) -> bool { + let server_version = &get_test_client_metadata().await.server_version; + server_version.major == major && server_version.minor == minor +} +pub(crate) async fn server_version_gt(major: u64, minor: u64) -> bool { + let server_version = &get_test_client_metadata().await.server_version; + server_version.major > major || server_version.major == major && server_version.minor > minor +} +pub(crate) async fn server_version_gte(major: u64, minor: u64) -> bool { + let server_version = &get_test_client_metadata().await.server_version; + server_version.major > major || server_version.major == major && server_version.minor >= minor +} +pub(crate) async fn server_version_lt(major: u64, minor: u64) -> bool { + let server_version = &get_test_client_metadata().await.server_version; + server_version.major < major || server_version.major == major && server_version.minor < minor +} +pub(crate) async fn server_version_lte(major: u64, minor: u64) -> bool { + let server_version = &get_test_client_metadata().await.server_version; + server_version.major < major || server_version.major == major && server_version.minor <= minor +} +pub(crate) async fn server_version_matches(requirement: &str) -> bool { + let requirement = semver::VersionReq::parse(requirement).unwrap(); + let server_version = &get_test_client_metadata().await.server_version; + requirement.matches(server_version) +} + +pub(crate) async fn get_server_parameters() -> &'static Document { + &get_test_client_metadata().await.server_parameters +} + +pub(crate) async fn get_primary() -> Option { + get_test_client_metadata() + .await + .hello_response + .primary + .as_ref() + .map(|s| ServerAddress::parse(s).unwrap()) +} +pub(crate) async fn get_max_write_batch_size() -> usize { + get_test_client_metadata() + .await + .hello_response + .max_write_batch_size + .unwrap() + .try_into() + .unwrap() +} +pub(crate) async fn get_max_bson_object_size() -> usize { + get_test_client_metadata() + .await + .hello_response + .max_bson_object_size + .try_into() + .unwrap() +} +pub(crate) async fn get_max_message_size_bytes() -> usize { + get_test_client_metadata() + .await + .hello_response + .max_message_size_bytes + .try_into() + .unwrap() +} + +async fn get_topology() -> &'static Topology { + static TOPOLOGY: OnceCell = OnceCell::const_new(); + TOPOLOGY + .get_or_init(|| async { + let client_options = get_client_options().await; + if client_options.load_balanced == Some(true) { + return Topology::LoadBalanced; + } + + let hello_response = &get_test_client_metadata().await.hello_response; + if hello_response.msg.as_deref() == Some("isdbgrid") { + return Topology::Sharded; + } + if hello_response.set_name.is_some() { + return Topology::ReplicaSet; + } + + Topology::Single + }) + .await +} +pub(crate) async fn topology_is_standalone() -> bool { + get_topology().await == &Topology::Single +} +pub(crate) async fn topology_is_replica_set() -> bool { + get_topology().await == &Topology::ReplicaSet +} +pub(crate) async fn topology_is_sharded() -> bool { + get_topology().await == &Topology::Sharded +} +pub(crate) async fn topology_is_load_balanced() -> bool { + get_topology().await == &Topology::LoadBalanced +} + +pub(crate) async fn transactions_supported() -> bool { + topology_is_replica_set().await || topology_is_sharded().await && server_version_gte(4, 2).await +} +pub(crate) async fn block_connection_supported() -> bool { + server_version_matches(">=4.2.9").await +} +pub(crate) async fn fail_command_supported() -> bool { + if topology_is_sharded().await { + server_version_matches(">=4.1.5").await + } else { + true + } +} +pub(crate) async fn fail_command_appname_initial_handshake_supported() -> bool { + let requirements = [">= 4.2.15, < 4.3.0", ">= 4.4.7, < 4.5.0", ">= 4.9.0"]; + for requirement in requirements { + if server_version_matches(requirement).await { + return true; + } + } + false +} +pub(crate) async fn streaming_monitor_protocol_supported() -> bool { + get_test_client_metadata() + .await + .hello_response + .topology_version + .is_some() +} + +pub(crate) static DEFAULT_URI: Lazy = Lazy::new(get_default_uri); +pub(crate) static SERVER_API: Lazy> = + Lazy::new(|| match std::env::var("MONGODB_API_VERSION") { + Ok(server_api_version) if !server_api_version.is_empty() => Some(ServerApi { + version: ServerApiVersion::from_str(server_api_version.as_str()).unwrap(), + deprecation_errors: None, + strict: None, + }), + _ => None, + }); +pub(crate) static LOAD_BALANCED_SINGLE_URI: Lazy> = + Lazy::new(|| std::env::var("SINGLE_MONGOS_LB_URI").ok()); +pub(crate) static LOAD_BALANCED_MULTIPLE_URI: Lazy> = + Lazy::new(|| std::env::var("MULTI_MONGOS_LB_URI").ok()); +pub(crate) static OIDC_URI: Lazy> = + Lazy::new(|| std::env::var("MONGODB_URI_SINGLE").ok()); + +// conditional definitions do not work within the lazy_static! macro, so this +// needs to be defined separately. +#[cfg(feature = "tracing-unstable")] +/// A global default tracing handler that will be installed the first time this +/// value is accessed. A global handler must be used anytime the multi-threaded +/// test runtime is in use, as non-global handlers only apply to the thread +/// they are registered in. +/// By default this handler will collect no tracing events. +/// Its minimum severity levels can be configured on a per-component basis using +/// [`TracingHandler:set_levels`]. The test lock MUST be acquired exclusively in +/// any test that will use the handler to avoid mixing events from multiple tests. +pub(crate) static DEFAULT_GLOBAL_TRACING_HANDLER: Lazy = Lazy::new(|| { + let handler = TracingHandler::new(); + tracing::subscriber::set_global_default(handler.clone()) + .expect("setting global default tracing subscriber failed"); + handler +}); + +pub(crate) fn update_options_for_testing(options: &mut ClientOptions) { + if options.server_api.is_none() { + options.server_api.clone_from(&SERVER_API); + } + + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + set_compressor(options); + + if let Some(ref mut credential) = options.credential { + if credential.mechanism == Some(AuthMechanism::MongoDbOidc) + && credential + .mechanism_properties + .as_ref() + .map(|properties| properties.get("ENVIRONMENT").is_none()) + .unwrap_or(true) + { + credential.oidc_callback = Callback::machine(move |_| { + async move { + Ok(IdpServerResponse::builder() + .access_token(get_access_token_test_user_1().await) + .build()) + } + .boxed() + }); + } + } +} + +fn get_default_uri() -> String { + if let Some(uri) = LOAD_BALANCED_SINGLE_URI.clone() { + if !uri.is_empty() { + return uri; + } + } + if let Some(uri) = &*OIDC_URI { + return uri.clone(); + } + if let Ok(uri) = std::env::var("MONGODB_URI") { + return uri; + } + if let Some(mut home) = home_dir() { + home.push(".mongodb_uri"); + if let Ok(uri) = read_to_string(home) { + return uri; + } + } + "mongodb://localhost:27017".to_string() +} + +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +fn set_compressor(options: &mut ClientOptions) { + use crate::options::Compressor; + + #[cfg(feature = "zstd-compression")] + { + options.compressors = Some(vec![Compressor::Zstd { level: None }]); + } + #[cfg(feature = "zlib-compression")] + { + options.compressors = Some(vec![Compressor::Zlib { level: None }]); + } + #[cfg(feature = "snappy-compression")] + { + options.compressors = Some(vec![Compressor::Snappy]); + } +} diff --git a/src/test/README.md b/src/test/README.md new file mode 100644 index 000000000..ef4020ce3 --- /dev/null +++ b/src/test/README.md @@ -0,0 +1,18 @@ +# Test development guide + +This document is a work-in-progress guide for developing tests for the driver. + +## Filtering tests +Tests that require any additional setup are filtered using cargo nextest's [filterset](https://nexte.st/docs/filtersets/). The driver uses two filters (configured in [nextest.toml](../../.config/nextest.toml)): + +- `skip_local`: skips filtered tests when running locally. +- `skip_ci`: skips filtered tests when running locally and in CI. + +Filtered tests should be organized into modules with the desired filter at the end of the module name (e.g. `search_index_skip_ci`). + +Filters can be bypassed locally by passing `--ignore-default-filter` to `cargo nextest run`. Filters can be bypassed in CI by adding the following to the script used to run the test: + +``` +source .evergreen/cargo-test.sh +CARGO_OPTIONS+=("--ignore-default-filter") +``` diff --git a/src/test/atlas_connectivity.rs b/src/test/atlas_connectivity.rs index a93c68f2f..1e1f951e4 100644 --- a/src/test/atlas_connectivity.rs +++ b/src/test/atlas_connectivity.rs @@ -1,141 +1,106 @@ -use crate::{bson::doc, client::options::ResolverConfig, options::ClientOptions, Client}; -use bson::Document; - -use super::log_uncaptured; +use crate::{ + bson::{doc, Document}, + client::options::ResolverConfig, + options::ClientOptions, + Client, +}; async fn run_test(uri_env_var: &str, resolver_config: Option) { - if std::env::var_os("MONGO_ATLAS_TESTS").is_none() { - log_uncaptured( - "skipping atlas_connectivity test due to undefined environment variable \ - MONGO_ATLAS_TESTS", - ); - return; - } + let uri = std::env::var(uri_env_var).expect(uri_env_var); - let uri = if let Some(uri) = std::env::var_os(uri_env_var) { - uri - } else { - panic!("could not find variable {}", uri_env_var); - }; - - let uri_string = uri.to_string_lossy(); let options = match resolver_config { Some(resolver_config) => { - ClientOptions::parse_with_resolver_config(uri_string.as_ref(), resolver_config).await + ClientOptions::parse(uri) + .resolver_config(resolver_config) + .await } - None => ClientOptions::parse(uri_string.as_ref()).await, + None => ClientOptions::parse(uri).await, } .expect("uri parsing should succeed"); let client = Client::with_options(options).expect("option validation should succeed"); let db = client.database("test"); - db.run_command(doc! { "hello": 1 }, None) + db.run_command(doc! { "hello": 1 }) .await .expect("hello should succeed"); let coll = db.collection::("test"); - coll.find_one(None, None) + coll.find_one(doc! {}) .await .expect("findOne should succeed"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_free_tier_repl_set() { - run_test("MONGO_ATLAS_FREE_TIER_REPL_URI", None).await; + run_test("ATLAS_FREE", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_free_tier_repl_set_srv() { - run_test("MONGO_ATLAS_FREE_TIER_REPL_URI_SRV", None).await; - run_test( - "MONGO_ATLAS_FREE_TIER_REPL_URI_SRV", - Some(ResolverConfig::cloudflare()), - ) - .await; + run_test("ATLAS_SRV_FREE", None).await; + run_test("ATLAS_SRV_FREE", Some(ResolverConfig::cloudflare())).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_serverless() { - run_test("MONGO_ATLAS_SERVERLESS_URI", None).await; + run_test("ATLAS_SERVERLESS", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_serverless_srv() { - run_test("MONGO_ATLAS_SERVERLESS_URI_SRV", None).await; - run_test( - "MONGO_ATLAS_SERVERLESS_URI_SRV", - Some(ResolverConfig::cloudflare()), - ) - .await; + run_test("ATLAS_SRV_SERVERLESS", None).await; + run_test("ATLAS_SRV_SERVERLESS", Some(ResolverConfig::cloudflare())).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_repl_set() { - run_test("MONGO_ATLAS_REPL_URI", None).await; + run_test("ATLAS_REPL", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_repl_set_srv() { - run_test("MONGO_ATLAS_REPL_URI_SRV", None).await; - run_test( - "MONGO_ATLAS_REPL_URI_SRV", - Some(ResolverConfig::cloudflare()), - ) - .await; + run_test("ATLAS_SRV_REPL", None).await; + run_test("ATLAS_SRV_REPL", Some(ResolverConfig::cloudflare())).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_sharded() { - run_test("MONGO_ATLAS_SHARDED_URI", None).await; + run_test("ATLAS_SHRD", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_sharded_srv() { - run_test("MONGO_ATLAS_SHARDED_URI_SRV", None).await; - run_test( - "MONGO_ATLAS_SHARDED_URI_SRV", - Some(ResolverConfig::cloudflare()), - ) - .await; + run_test("ATLAS_SRV_SHRD", None).await; + run_test("ATLAS_SRV_SHRD", Some(ResolverConfig::cloudflare())).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_tls_11() { - run_test("MONGO_ATLAS_TLS11_URI", None).await; + run_test("ATLAS_TLS11", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_tls11_srv() { - run_test("MONGO_ATLAS_TLS11_URI_SRV", None).await; - run_test( - "MONGO_ATLAS_TLS11_URI_SRV", - Some(ResolverConfig::cloudflare()), - ) - .await; + run_test("ATLAS_SRV_TLS11", None).await; + run_test("ATLAS_SRV_TLS11", Some(ResolverConfig::cloudflare())).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_tls_12() { - run_test("MONGO_ATLAS_TLS12_URI", None).await; + run_test("ATLAS_TLS12", None).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn atlas_tls12_srv() { - run_test("MONGO_ATLAS_TLS12_URI_SRV", None).await; + run_test("ATLAS_SRV_TLS12", None).await; + run_test("ATLAS_SRV_TLS12", Some(ResolverConfig::cloudflare())).await; +} + +#[tokio::test] +async fn atlas_x509() { + run_test("ATLAS_X509_DEV_WITH_CERT", None).await; run_test( - "MONGO_ATLAS_TLS12_URI_SRV", + "ATLAS_X509_DEV_WITH_CERT", Some(ResolverConfig::cloudflare()), ) .await; diff --git a/src/test/atlas_planned_maintenance_testing/json_models.rs b/src/test/atlas_planned_maintenance_testing/json_models.rs deleted file mode 100644 index 9dde61125..000000000 --- a/src/test/atlas_planned_maintenance_testing/json_models.rs +++ /dev/null @@ -1,39 +0,0 @@ -use serde::Serialize; -use serde_json::Number; - -use crate::bson::Bson; - -#[derive(Serialize)] -pub(crate) struct Events { - pub(crate) errors: Vec, - pub(crate) failures: Vec, -} - -impl Events { - pub(crate) fn new_empty() -> Self { - Self { - errors: vec![], - failures: vec![], - } - } -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct Results { - pub(crate) num_errors: Number, - pub(crate) num_failures: Number, - pub(crate) num_successes: Number, - pub(crate) num_iterations: Number, -} - -impl Results { - pub(crate) fn new_empty() -> Self { - Self { - num_errors: (-1i8).into(), - num_failures: (-1i8).into(), - num_successes: (-1i8).into(), - num_iterations: (-1i8).into(), - } - } -} diff --git a/src/test/atlas_planned_maintenance_testing/mod.rs b/src/test/atlas_planned_maintenance_testing/mod.rs deleted file mode 100644 index c5c6bc17b..000000000 --- a/src/test/atlas_planned_maintenance_testing/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -// NOTE: Please run a patch against drivers-atlas-testing when making changes within this file. The -// DRIVER_REPOSITORY and DRIVER_REVISION fields for the Rust axis in drivers-atlas-testing's -// evergreen config file can be updated to test against your branch. - -mod json_models; - -use std::{ - env, - fs::File, - io::{BufWriter, Write}, - panic::AssertUnwindSafe, - path::PathBuf, -}; - -use futures::FutureExt; -use serde_json::Value; -use time::OffsetDateTime; - -use crate::{ - bson::{doc, Bson}, - test::{ - log_uncaptured, - spec::unified_runner::{entity::Entity, test_file::TestFile, test_runner::TestRunner}, - }, -}; - -use json_models::{Events, Results}; - -use super::spec::unified_runner::EntityMap; - -#[test] -#[ignore] -fn get_exe_name() { - let mut file = File::create("exe_name.txt").expect("Failed to create file"); - let exe_name = env::current_exe() - .expect("Failed to determine name of test executable") - .into_os_string() - .into_string() - .expect("Failed to convert OS string to string"); - write!(&mut file, "{}", exe_name).expect("Failed to write executable name to file"); -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn workload_executor() { - if env::var("ATLAS_PLANNED_MAINTENANCE_TESTING").is_err() { - // This test should only be run from the workload-executor script. - log_uncaptured( - "Skipping workload_executor due to being run outside of planned maintenance testing", - ); - return; - } - - let connection_string = - env::var("WORKLOAD_EXECUTOR_CONNECTION_STRING").expect("No connection string specified"); - - let workload_string = env::var("WORKLOAD_EXECUTOR_WORKLOAD").expect("No workload specified"); - let workload = - serde_json::from_str(&workload_string).expect("Error converting workload to JSON"); - - let mut test_runner = TestRunner::new_with_connection_string(&connection_string).await; - - let execution_errors = execute_workload(&mut test_runner, workload).await; - let mut entities = test_runner.entities.write().await; - write_json(&mut entities, execution_errors); -} - -async fn execute_workload(test_runner: &mut TestRunner, workload: Value) -> Vec { - let mut execution_errors: Vec = vec![]; - - let test_file: TestFile = serde_json::from_value(workload).unwrap(); - let description = test_file.description.clone(); - - log_uncaptured("Running planned maintenance tests"); - - if AssertUnwindSafe(test_runner.run_test(test_file, None, None)) - .catch_unwind() - .await - .is_err() - { - execution_errors.push( - doc! { - "error": format!("Unexpected error occurred while running {}", description), - "time": OffsetDateTime::now_utc().unix_timestamp(), - } - .into(), - ) - } - - log_uncaptured("Planned maintenance tests completed"); - - execution_errors -} - -fn write_json(entities: &mut EntityMap, mut errors: Vec) { - log_uncaptured("Writing planned maintenance test results to files"); - - let mut events = Events::new_empty(); - if let Some(Entity::Bson(Bson::Array(mut operation_errors))) = entities.remove("errors") { - errors.append(&mut operation_errors); - } - events.errors = errors; - if let Some(Entity::Bson(Bson::Array(failures))) = entities.remove("failures") { - events.failures = failures; - } - - let mut results = Results::new_empty(); - results.num_errors = events.errors.len().into(); - results.num_failures = events.failures.len().into(); - if let Some(Entity::Bson(Bson::Int64(iterations))) = entities.remove("iterations") { - results.num_iterations = iterations.into(); - } - if let Some(Entity::Bson(Bson::Int64(successes))) = entities.remove("successes") { - results.num_successes = successes.into(); - } - - let path = - env::var("WORKLOAD_EXECUTOR_WORKING_DIRECTORY").expect("No working directory specified"); - - let mut events_path = PathBuf::from(&path); - events_path.push("events.json"); - let mut writer = - BufWriter::new(File::create(events_path).expect("Failed to create events.json")); - - let mut json_string = serde_json::to_string(&events).unwrap(); - // Pop the final "}" from the string as we still need to insert the events k/v pair. - json_string.pop(); - write!(&mut writer, "{}", json_string).unwrap(); - // The events key is expected to be present regardless of whether storeEventsAsEntities was - // defined. - write!(&mut writer, ",\"events\":[").unwrap(); - if let Some(entity) = entities.get("events") { - let event_list_entity = entity.as_event_list().to_owned(); - let client = entities - .get(&event_list_entity.client_id) - .unwrap() - .as_client(); - let names: Vec<&str> = event_list_entity - .event_names - .iter() - .map(String::as_ref) - .collect(); - client.write_events_list_to_file(&names, &mut writer); - } - write!(&mut writer, "]}}").unwrap(); - - let mut results_path = PathBuf::from(&path); - results_path.push("results.json"); - let file = File::create(results_path).expect("Failed to create results.json"); - serde_json::to_writer(file, &results) - .expect("Failed to convert results to JSON and write to file"); - - log_uncaptured("Writing planned maintenance test results to files completed"); -} diff --git a/src/test/auth.rs b/src/test/auth.rs new file mode 100644 index 000000000..c6f4ca430 --- /dev/null +++ b/src/test/auth.rs @@ -0,0 +1,48 @@ +#[cfg(feature = "aws-auth")] +mod aws; + +use serde::Deserialize; + +use crate::{ + bson::doc, + options::{AuthMechanism, ClientOptions, Credential, ServerAddress}, + Client, +}; + +#[tokio::test] +async fn plain_auth() { + let options = ClientOptions::builder() + .hosts(vec![ServerAddress::Tcp { + host: "ldaptest.10gen.cc".into(), + port: None, + }]) + .credential( + Credential::builder() + .mechanism(AuthMechanism::Plain) + .username("drivers-team".to_string()) + .password("mongor0x$xgen".to_string()) + .build(), + ) + .build(); + + let client = Client::with_options(options).unwrap(); + let coll = client.database("ldap").collection("test"); + + let doc = coll.find_one(doc! {}).await.unwrap().unwrap(); + + #[derive(Debug, Deserialize, PartialEq)] + struct TestDocument { + ldap: bool, + authenticated: String, + } + + let doc: TestDocument = crate::bson_compat::deserialize_from_document(doc).unwrap(); + + assert_eq!( + doc, + TestDocument { + ldap: true, + authenticated: "yeah".into() + } + ); +} diff --git a/src/test/auth/aws.rs b/src/test/auth/aws.rs new file mode 100644 index 000000000..4319dfe70 --- /dev/null +++ b/src/test/auth/aws.rs @@ -0,0 +1,125 @@ +use std::env::{remove_var, set_var, var}; + +use crate::{ + bson::{doc, Document}, + client::auth::aws::test_utils::*, + test::DEFAULT_URI, + Client, +}; + +#[tokio::test] +async fn auth_aws() { + let client = Client::for_test().await; + let coll = client.database("aws").collection::("somecoll"); + + coll.find_one(doc! {}).await.unwrap(); +} + +// The TestClient performs operations upon creation that trigger authentication, so the credential +// caching tests use a regular client instead to avoid that noise. +async fn get_client() -> Client { + Client::with_uri_str(DEFAULT_URI.clone()).await.unwrap() +} + +#[tokio::test] +async fn credential_caching() { + // This test should only be run when authenticating using AWS endpoints. + if var("SKIP_CREDENTIAL_CACHING_TESTS").is_ok() { + return; + } + + clear_cached_credential().await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_some()); + + let now = crate::bson::DateTime::now(); + set_cached_expiration(now).await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_some()); + assert!(cached_expiration().await > now); + + poison_cached_credential().await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + match coll.find_one(doc! {}).await { + Ok(_) => panic!( + "find one should have failed with authentication error due to poisoned cached \ + credential" + ), + Err(error) => assert!(error.is_auth_error()), + } + assert!(cached_credential().await.is_none()); + + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_some()); +} + +#[tokio::test] +async fn credential_caching_environment_vars() { + // This test should only be run when authenticating using AWS endpoints. + if var("SKIP_CREDENTIAL_CACHING_TESTS").is_ok() { + return; + } + + clear_cached_credential().await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_some()); + + set_var("AWS_ACCESS_KEY_ID", cached_access_key_id().await); + set_var("AWS_SECRET_ACCESS_KEY", cached_secret_access_key().await); + if let Some(session_token) = cached_session_token().await { + set_var("AWS_SESSION_TOKEN", session_token); + } + clear_cached_credential().await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_none()); + + set_var("AWS_ACCESS_KEY_ID", "bad"); + set_var("AWS_SECRET_ACCESS_KEY", "bad"); + set_var("AWS_SESSION_TOKEN", "bad"); + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + match coll.find_one(doc! {}).await { + Ok(_) => panic!( + "find one should have failed with authentication error due to poisoned environment \ + variables" + ), + Err(error) => assert!(error.is_auth_error()), + } + + remove_var("AWS_ACCESS_KEY_ID"); + remove_var("AWS_SECRET_ACCESS_KEY"); + remove_var("AWS_SESSION_TOKEN"); + clear_cached_credential().await; + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + assert!(cached_credential().await.is_some()); + + set_var("AWS_ACCESS_KEY_ID", "bad"); + set_var("AWS_SECRET_ACCESS_KEY", "bad"); + set_var("AWS_SESSION_TOKEN", "bad"); + + let client = get_client().await; + let coll = client.database("aws").collection::("somecoll"); + coll.find_one(doc! {}).await.unwrap(); + + remove_var("AWS_ACCESS_KEY_ID"); + remove_var("AWS_SECRET_ACCESS_KEY"); + remove_var("AWS_SESSION_TOKEN"); +} diff --git a/src/test/auth_aws.rs b/src/test/auth_aws.rs deleted file mode 100644 index 95b52ac39..000000000 --- a/src/test/auth_aws.rs +++ /dev/null @@ -1,15 +0,0 @@ -use bson::Document; -use tokio::sync::RwLockReadGuard; - -use super::{TestClient, LOCK}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn auth_aws() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let coll = client.database("aws").collection::("somecoll"); - - coll.find_one(None, None).await.unwrap(); -} diff --git a/src/test/bulk_write.rs b/src/test/bulk_write.rs new file mode 100644 index 000000000..e4741fab4 --- /dev/null +++ b/src/test/bulk_write.rs @@ -0,0 +1,581 @@ +use std::collections::HashMap; + +use crate::{ + bson::{doc, Document}, + error::{bulk_write::PartialBulkWriteResult, BulkWriteError, ErrorKind}, + options::{InsertOneModel, UpdateOneModel}, + results::UpdateResult, + test::{ + get_client_options, + get_max_bson_object_size, + get_max_message_size_bytes, + get_max_write_batch_size, + log_uncaptured, + server_version_gte, + server_version_lt, + topology_is_sharded, + topology_is_standalone, + util::fail_point::{FailPoint, FailPointMode}, + }, + Client, + Namespace, +}; + +impl PartialBulkWriteResult { + fn inserted_count(&self) -> i64 { + match self { + Self::Summary(summary_result) => summary_result.inserted_count, + Self::Verbose(verbose_result) => verbose_result.summary.inserted_count, + } + } + + fn upserted_count(&self) -> i64 { + match self { + Self::Summary(summary_result) => summary_result.upserted_count, + Self::Verbose(verbose_result) => verbose_result.summary.upserted_count, + } + } + + fn update_results(&self) -> Option<&HashMap> { + match self { + Self::Summary(_) => None, + Self::Verbose(verbose_result) => Some(&verbose_result.update_results), + } + } +} + +// CRUD prose test 3 +#[tokio::test] +async fn max_write_batch_size_batching() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping max_write_batch_size_batching: bulkWrite requires 8.0+"); + return; + } + + let client = Client::for_test().monitor_events().await; + + let max_write_batch_size = get_max_write_batch_size().await; + + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); + let models = vec![model; max_write_batch_size + 1]; + + let result = client.bulk_write(models).await.unwrap(); + assert_eq!(result.inserted_count as usize, max_write_batch_size + 1); + + let mut command_started_events = client + .events + .get_command_started_events(&["bulkWrite"]) + .into_iter(); + + let first_event = command_started_events + .next() + .expect("no first event observed"); + let first_len = first_event.command.get_array("ops").unwrap().len(); + assert_eq!(first_len, max_write_batch_size); + + let second_event = command_started_events + .next() + .expect("no second event observed"); + let second_len = second_event.command.get_array("ops").unwrap().len(); + assert_eq!(second_len, 1); +} + +// CRUD prose test 4 +#[tokio::test] +async fn max_message_size_bytes_batching() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping max_message_size_bytes_batching: bulkWrite requires 8.0+"); + return; + } + + let client = Client::for_test().monitor_events().await; + + let max_bson_object_size = get_max_bson_object_size().await; + let max_message_size_bytes = get_max_message_size_bytes().await; + + let document = doc! { "a": "b".repeat(max_bson_object_size - 500) }; + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(document) + .build(); + let num_models = max_message_size_bytes / max_bson_object_size + 1; + let models = vec![model; num_models]; + + let result = client.bulk_write(models).await.unwrap(); + assert_eq!(result.inserted_count as usize, num_models); + + let mut command_started_events = client + .events + .get_command_started_events(&["bulkWrite"]) + .into_iter(); + + let first_event = command_started_events + .next() + .expect("no first event observed"); + let first_len = first_event.command.get_array("ops").unwrap().len(); + assert_eq!(first_len, num_models - 1); + + let second_event = command_started_events + .next() + .expect("no second event observed"); + let second_len = second_event.command.get_array("ops").unwrap().len(); + assert_eq!(second_len, 1); +} + +// CRUD prose test 5 +#[tokio::test(flavor = "multi_thread")] +async fn write_concern_error_batches() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping write_concern_error_batches: bulkWrite requires 8.0+"); + return; + } + + let mut options = get_client_options().await.clone(); + options.retry_writes = Some(false); + if topology_is_sharded().await { + options.hosts.drain(1..); + } + let client = Client::for_test().options(options).monitor_events().await; + + let max_write_batch_size = get_max_write_batch_size().await; + + let fail_point = FailPoint::fail_command(&["bulkWrite"], FailPointMode::Times(2)) + .write_concern_error(doc! { "code": 91, "errmsg": "Replication is being shut down" }); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); + + let models = vec![ + InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); + max_write_batch_size + 1 + ]; + let error = client.bulk_write(models).ordered(false).await.unwrap_err(); + + let ErrorKind::BulkWrite(bulk_write_error) = *error.kind else { + panic!("Expected bulk write error, got {:?}", error); + }; + + assert_eq!(bulk_write_error.write_concern_errors.len(), 2); + + let partial_result = bulk_write_error.partial_result.unwrap(); + assert_eq!( + partial_result.inserted_count() as usize, + max_write_batch_size + 1 + ); + + let command_started_events = client.events.get_command_started_events(&["bulkWrite"]); + assert_eq!(command_started_events.len(), 2); +} + +// CRUD prose test 6 +#[tokio::test] +async fn write_error_batches() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping write_error_batches: bulkWrite requires 8.0+"); + return; + } + + let mut client = Client::for_test().monitor_events().await; + + let max_write_batch_size = get_max_write_batch_size().await; + + let document = doc! { "_id": 1 }; + let collection = client.database("db").collection("coll"); + collection.drop().await.unwrap(); + collection.insert_one(document.clone()).await.unwrap(); + + let models = vec![ + InsertOneModel::builder() + .namespace(collection.namespace()) + .document(document) + .build(); + max_write_batch_size + 1 + ]; + + let error = client + .bulk_write(models.clone()) + .ordered(false) + .await + .unwrap_err(); + + let ErrorKind::BulkWrite(bulk_write_error) = *error.kind else { + panic!("Expected bulk write error, got {:?}", error); + }; + + assert_eq!( + bulk_write_error.write_errors.len(), + max_write_batch_size + 1 + ); + + let command_started_events = client.events.get_command_started_events(&["bulkWrite"]); + assert_eq!(command_started_events.len(), 2); + + client.events.clear_cached_events(); + + let error = client.bulk_write(models).ordered(true).await.unwrap_err(); + + let ErrorKind::BulkWrite(bulk_write_error) = *error.kind else { + panic!("Expected bulk write error, got {:?}", error); + }; + + assert_eq!(bulk_write_error.write_errors.len(), 1); + + let command_started_events = client.events.get_command_started_events(&["bulkWrite"]); + assert_eq!(command_started_events.len(), 1); +} + +// CRUD prose test 7 +#[tokio::test] +async fn successful_cursor_iteration() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping successful_cursor_iteration: bulkWrite requires 8.0+"); + return; + } + + let client = Client::for_test().monitor_events().await; + + let max_bson_object_size = get_max_bson_object_size().await; + + let collection = client.database("db").collection::("coll"); + collection.drop().await.unwrap(); + + let models = vec![ + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + ]; + + let result = client.bulk_write(models).verbose_results().await.unwrap(); + assert_eq!(result.summary.upserted_count, 2); + assert_eq!(result.update_results.len(), 2); + + let command_started_events = client.events.get_command_started_events(&["getMore"]); + assert_eq!(command_started_events.len(), 1); +} + +// CRUD prose test 8 +#[tokio::test] +async fn cursor_iteration_in_a_transaction() { + if server_version_lt(8, 0).await || topology_is_standalone().await { + log_uncaptured( + "skipping cursor_iteration_in_a_transaction: bulkWrite requires 8.0+, transactions \ + require a non-standalone topology", + ); + return; + } + + let client = Client::for_test().monitor_events().await; + + let max_bson_object_size = get_max_bson_object_size().await; + + let collection = client.database("db").collection::("coll"); + collection.drop().await.unwrap(); + + let mut session = client.start_session().await.unwrap(); + session.start_transaction().await.unwrap(); + + let models = vec![ + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + ]; + + let result = client + .bulk_write(models) + .verbose_results() + .session(&mut session) + .await + .unwrap(); + assert_eq!(result.summary.upserted_count, 2); + assert_eq!(result.update_results.len(), 2); + + let command_started_events = client.events.get_command_started_events(&["getMore"]); + assert_eq!(command_started_events.len(), 1); +} + +// CRUD prose test 9 +#[tokio::test(flavor = "multi_thread")] +async fn failed_cursor_iteration() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping failed_cursor_iteration: bulkWrite requires 8.0+"); + return; + } + + let mut options = get_client_options().await.clone(); + if topology_is_sharded().await { + options.hosts.drain(1..); + } + let client = Client::for_test().options(options).monitor_events().await; + + let max_bson_object_size = get_max_bson_object_size().await; + + let fail_point = FailPoint::fail_command(&["getMore"], FailPointMode::Times(1)).error_code(8); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); + + let collection = client.database("db").collection::("coll"); + collection.drop().await.unwrap(); + + let models = vec![ + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "a".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + UpdateOneModel::builder() + .namespace(collection.namespace()) + .filter(doc! { "_id": "b".repeat(max_bson_object_size / 2) }) + .update(doc! { "$set": { "x": 1 } }) + .upsert(true) + .build(), + ]; + + let error = client + .bulk_write(models) + .verbose_results() + .await + .unwrap_err(); + + let Some(ref source) = error.source else { + panic!("Expected error to contain source"); + }; + assert_eq!(source.code(), Some(8)); + + let ErrorKind::BulkWrite(BulkWriteError { + partial_result: Some(partial_result), + .. + }) = *error.kind + else { + panic!( + "Expected bulk write error with partial result, got {:?}", + error + ); + }; + assert_eq!(partial_result.upserted_count(), 2); + assert_eq!(partial_result.update_results().unwrap().len(), 1); + + let get_more_events = client.events.get_command_started_events(&["getMore"]); + assert_eq!(get_more_events.len(), 1); + + let kill_cursors_events = client.events.get_command_started_events(&["killCursors"]); + assert_eq!(kill_cursors_events.len(), 1); +} + +// CRUD prose test 10 not implemented. The driver does not support unacknowledged writes. + +// CRUD prose test 11 +#[tokio::test] +async fn namespace_batch_splitting() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping namespace_batch_splitting: bulkWrite requires 8.0+"); + return; + } + + let first_namespace = Namespace::new("db", "coll"); + + let mut client = Client::for_test().monitor_events().await; + + let max_message_size_bytes = get_max_message_size_bytes().await; + let max_bson_object_size = get_max_bson_object_size().await; + + let ops_bytes = max_message_size_bytes - 1122; + let num_models = ops_bytes / max_bson_object_size; + + let model = InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b".repeat(max_bson_object_size - 57) }) + .build(); + let mut models = vec![model; num_models]; + + let remainder_bytes = ops_bytes % max_bson_object_size; + if remainder_bytes >= 217 { + models.push( + InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b".repeat(remainder_bytes - 57) }) + .build(), + ); + } + + // Case 1: no batch-splitting required + + let mut first_models = models.clone(); + first_models.push( + InsertOneModel::builder() + .namespace(first_namespace.clone()) + .document(doc! { "a": "b" }) + .build(), + ); + let num_models = first_models.len(); + + let result = client.bulk_write(first_models).await.unwrap(); + assert_eq!(result.inserted_count as usize, num_models); + + let command_started_events = client.events.get_command_started_events(&["bulkWrite"]); + assert_eq!(command_started_events.len(), 1); + + let event = &command_started_events[0]; + + let ops = event.command.get_array("ops").unwrap(); + assert_eq!(ops.len(), num_models); + + let ns_info = event.command.get_array("nsInfo").unwrap(); + assert_eq!(ns_info.len(), 1); + let namespace = ns_info[0].as_document().unwrap().get_str("ns").unwrap(); + assert_eq!(namespace, &first_namespace.to_string()); + + // Case 2: batch-splitting required + + client.events.clear_cached_events(); + + let second_namespace = Namespace::new("db", "c".repeat(200)); + + let mut second_models = models.clone(); + second_models.push( + InsertOneModel::builder() + .namespace(second_namespace.clone()) + .document(doc! { "a": "b" }) + .build(), + ); + let num_models = second_models.len(); + + let result = client.bulk_write(second_models).await.unwrap(); + assert_eq!(result.inserted_count as usize, num_models); + + let command_started_events = client.events.get_command_started_events(&["bulkWrite"]); + assert_eq!(command_started_events.len(), 2); + + let first_event = &command_started_events[0]; + + let first_ops = first_event.command.get_array("ops").unwrap(); + assert_eq!(first_ops.len(), num_models - 1); + + let first_ns_info = first_event.command.get_array("nsInfo").unwrap(); + assert_eq!(first_ns_info.len(), 1); + let actual_first_namespace = first_ns_info[0] + .as_document() + .unwrap() + .get_str("ns") + .unwrap(); + assert_eq!(actual_first_namespace, &first_namespace.to_string()); + + let second_event = &command_started_events[1]; + + let second_ops = second_event.command.get_array("ops").unwrap(); + assert_eq!(second_ops.len(), 1); + + let second_ns_info = second_event.command.get_array("nsInfo").unwrap(); + assert_eq!(second_ns_info.len(), 1); + let actual_second_namespace = second_ns_info[0] + .as_document() + .unwrap() + .get_str("ns") + .unwrap(); + assert_eq!(actual_second_namespace, &second_namespace.to_string()); +} + +// CRUD prose test 12 +#[tokio::test] +async fn too_large_client_error() { + if server_version_lt(8, 0).await { + log_uncaptured("skipping too_large_client_error: bulkWrite requires 8.0+"); + return; + } + + let client = Client::for_test().monitor_events().await; + let max_message_size_bytes = get_max_message_size_bytes().await; + + // Case 1: document too large + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b".repeat(max_message_size_bytes) }) + .build(); + + let error = client.bulk_write(vec![model]).await.unwrap_err(); + assert!(error.is_invalid_argument()); + + // Case 2: namespace too large + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "c".repeat(max_message_size_bytes))) + .document(doc! { "a": "b" }) + .build(); + + let error = client.bulk_write(vec![model]).await.unwrap_err(); + assert!(error.is_invalid_argument()); +} + +// CRUD prose test 13 +#[cfg(feature = "in-use-encryption")] +#[tokio::test] +async fn encryption_error() { + use crate::{ + client::csfle::options::{AutoEncryptionOptions, KmsProviders}, + mongocrypt::ctx::KmsProvider, + }; + + let kms_providers = KmsProviders::new(vec![( + KmsProvider::aws(), + doc! { "accessKeyId": "foo", "secretAccessKey": "bar" }, + None, + )]) + .unwrap(); + let encrypted_options = AutoEncryptionOptions::new(Namespace::new("db", "coll"), kms_providers); + let encrypted_client = Client::for_test() + .encrypted_options(encrypted_options) + .await; + + let model = InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build(); + let error = encrypted_client.bulk_write(vec![model]).await.unwrap_err(); + + let ErrorKind::Encryption(encryption_error) = *error.kind else { + panic!("expected encryption error, got {:?}", error); + }; + + assert_eq!( + encryption_error.message, + Some("bulkWrite does not currently support automatic encryption".to_string()) + ); +} + +#[tokio::test] +async fn unsupported_server_client_error() { + if server_version_gte(8, 0).await { + log_uncaptured("skipping unsupported_server_client_error: bulk write supported"); + return; + } + + let client = Client::for_test().await; + let error = client + .bulk_write(vec![InsertOneModel::builder() + .namespace(Namespace::new("db", "coll")) + .document(doc! { "a": "b" }) + .build()]) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::IncompatibleServer { .. })); +} diff --git a/src/test/change_stream.rs b/src/test/change_stream.rs index b55e97aa0..5139a31fa 100644 --- a/src/test/change_stream.rs +++ b/src/test/change_stream.rs @@ -1,23 +1,33 @@ -use bson::{doc, Bson, Document}; +use crate::bson::{doc, Bson, Document}; use futures_util::{StreamExt, TryStreamExt}; -use semver::VersionReq; use crate::{ change_stream::{ event::{ChangeStreamEvent, OperationType}, - options::{ChangeStreamOptions, FullDocumentBeforeChangeType}, + options::FullDocumentBeforeChangeType, ChangeStream, }, coll::options::CollectionOptions, - db::options::{ChangeStreamPreAndPostImages, CreateCollectionOptions}, + db::options::ChangeStreamPreAndPostImages, event::command::{CommandEvent, CommandStartedEvent, CommandSucceededEvent}, options::{Acknowledgment, WriteConcern}, - test::{FailCommandOptions, FailPoint, FailPointMode}, + test::util::fail_point::{FailPoint, FailPointMode}, Client, Collection, }; -use super::{log_uncaptured, EventClient, TestClient, CLIENT_OPTIONS, LOCK}; +use super::{ + fail_command_supported, + get_client_options, + log_uncaptured, + server_version_gte, + server_version_lt, + server_version_matches, + topology_is_replica_set, + topology_is_sharded, + transactions_supported, + EventClient, +}; type Result = std::result::Result>; @@ -31,23 +41,22 @@ async fn init_stream( ChangeStream>, )>, > { - let init_client = TestClient::new().await; - if !init_client.is_replica_set() && !init_client.is_sharded() { + if !(topology_is_replica_set().await || topology_is_sharded().await) { log_uncaptured("skipping change stream test on unsupported topology"); return Ok(None); } - if !init_client.supports_fail_command() { + if !fail_command_supported().await { log_uncaptured("skipping change stream test on version without fail commands"); return Ok(None); } - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); // Direct connection is needed for reliable behavior with fail points. - if direct_connection && init_client.is_sharded() { + if direct_connection && topology_is_sharded().await { options.direct_connection = Some(true); options.hosts.drain(1..); } - let client = EventClient::with_options(options).await; + let client = Client::for_test().options(options).monitor_events().await; let db = client.database("change_stream_tests"); let coll = db.collection_with_options::( coll_name, @@ -55,17 +64,14 @@ async fn init_stream( .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build()) .build(), ); - coll.drop(None).await?; - let stream = coll.watch(None, None).await?; + coll.drop().await?; + let stream = coll.watch().await?; Ok(Some((client, coll, stream))) } /// Prose test 1: ChangeStream must continuously track the last seen resumeToken -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn tracks_resume_token() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - let (client, coll, mut stream) = match init_stream("track_resume_token", false).await? { Some(t) => t, None => return Ok(()), @@ -76,12 +82,13 @@ async fn tracks_resume_token() -> Result<()> { tokens.push(token.parsed()?); } for _ in 0..3 { - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await.transpose()?; tokens.push(stream.resume_token().unwrap().parsed()?); } let events: Vec<_> = client + .events .get_command_events(&["aggregate", "getMore"]) .into_iter() .filter_map(|ev| match ev { @@ -133,19 +140,17 @@ fn expected_tokens(events: &[CommandSucceededEvent]) -> Result> { /// Prose test 2: ChangeStream will throw an exception if the server response is missing the resume /// token -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn errors_on_missing_token() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - let (_, coll, _) = match init_stream("errors_on_missing_token", false).await? { Some(t) => t, None => return Ok(()), }; let mut stream = coll - .watch(vec![doc! { "$project": { "_id": 0 } }], None) + .watch() + .pipeline(vec![doc! { "$project": { "_id": 0 } }]) .await?; - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; assert!(stream.next().await.transpose().is_err()); Ok(()) @@ -153,17 +158,14 @@ async fn errors_on_missing_token() -> Result<()> { /// Prose test 3: After receiving a resumeToken, ChangeStream will automatically resume one time on /// a resumable error -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn resumes_on_error() -> Result<()> { - let _guard = LOCK.run_exclusively().await; - let (client, coll, mut stream) = match init_stream("resumes_on_error", true).await? { Some(t) => t, None => return Ok(()), }; - coll.insert_one(doc! { "_id": 1 }, None).await?; + coll.insert_one(doc! { "_id": 1 }).await?; assert!(matches!(stream.next().await.transpose()?, Some(ChangeStreamEvent { operation_type: OperationType::Insert, @@ -172,15 +174,10 @@ async fn resumes_on_error() -> Result<()> { }) if key == doc! { "_id": 1 } )); - let _guard = FailPoint::fail_command( - &["getMore"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + let fail_point = FailPoint::fail_command(&["getMore"], FailPointMode::Times(1)).error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; - coll.insert_one(doc! { "_id": 2 }, None).await?; + coll.insert_one(doc! { "_id": 2 }).await?; assert!(matches!(stream.next().await.transpose()?, Some(ChangeStreamEvent { operation_type: OperationType::Insert, @@ -190,7 +187,7 @@ async fn resumes_on_error() -> Result<()> { )); // Assert that two `aggregate`s were issued, i.e. that a resume happened. - let events = client.get_command_started_events(&["aggregate"]); + let events = client.events.get_command_started_events(&["aggregate"]); assert_eq!(events.len(), 2); Ok(()) @@ -198,25 +195,18 @@ async fn resumes_on_error() -> Result<()> { /// Prose test 4: ChangeStream will not attempt to resume on any error encountered while executing /// an aggregate command -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn does_not_resume_aggregate() -> Result<()> { - let _guard = LOCK.run_exclusively().await; - let (client, coll, _) = match init_stream("does_not_resume_aggregate", true).await? { Some(t) => t, None => return Ok(()), }; - let _guard = FailPoint::fail_command( - &["aggregate"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + let fail_point = + FailPoint::fail_command(&["aggregate"], FailPointMode::Times(1)).error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; - assert!(coll.watch(None, None).await.is_err()); + assert!(coll.watch().await.is_err()); Ok(()) } @@ -229,11 +219,8 @@ async fn does_not_resume_aggregate() -> Result<()> { /// Prose test 7: A cursor returned from an aggregate command with a cursor id and an initial empty /// batch is not closed on the driver side -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn empty_batch_not_closed() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - let (client, coll, mut stream) = match init_stream("empty_batch_not_closed", false).await? { Some(t) => t, None => return Ok(()), @@ -241,10 +228,10 @@ async fn empty_batch_not_closed() -> Result<()> { assert!(stream.next_if_any().await?.is_none()); - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await.transpose()?; - let events = client.get_command_events(&["aggregate", "getMore"]); + let events = client.events.get_command_events(&["aggregate", "getMore"]); let cursor_id = match &events[1] { CommandEvent::Succeeded(CommandSucceededEvent { reply, .. }) => { reply.get_document("cursor")?.get_i64("id")? @@ -262,18 +249,15 @@ async fn empty_batch_not_closed() -> Result<()> { /// Prose test 8: The killCursors command sent during the "Resume Process" must not be allowed to /// throw an exception -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn resume_kill_cursor_error_suppressed() -> Result<()> { - let _guard = LOCK.run_exclusively().await; - let (client, coll, mut stream) = match init_stream("resume_kill_cursor_error_suppressed", true).await? { Some(t) => t, None => return Ok(()), }; - coll.insert_one(doc! { "_id": 1 }, None).await?; + coll.insert_one(doc! { "_id": 1 }).await?; assert!(matches!(stream.next().await.transpose()?, Some(ChangeStreamEvent { operation_type: OperationType::Insert, @@ -282,15 +266,11 @@ async fn resume_kill_cursor_error_suppressed() -> Result<()> { }) if key == doc! { "_id": 1 } )); - let _guard = FailPoint::fail_command( - &["getMore", "killCursors"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + let fail_point = FailPoint::fail_command(&["getMore", "killCursors"], FailPointMode::Times(1)) + .error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; - coll.insert_one(doc! { "_id": 2 }, None).await?; + coll.insert_one(doc! { "_id": 2 }).await?; assert!(matches!(stream.next().await.transpose()?, Some(ChangeStreamEvent { operation_type: OperationType::Insert, @@ -300,7 +280,7 @@ async fn resume_kill_cursor_error_suppressed() -> Result<()> { )); // Assert that two `aggregate`s were issued, i.e. that a resume happened. - let events = client.get_command_started_events(&["aggregate"]); + let events = client.events.get_command_started_events(&["aggregate"]); assert_eq!(events.len(), 2); Ok(()) @@ -309,39 +289,26 @@ async fn resume_kill_cursor_error_suppressed() -> Result<()> { /// Prose test 9: $changeStream stage for ChangeStream against a server >=4.0 and <4.0.7 that has /// not received any results yet MUST include a startAtOperationTime option when resuming a change /// stream. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn resume_start_at_operation_time() -> Result<()> { - let _guard = LOCK.run_exclusively().await; + if !server_version_matches(">=4.0, <4.0.7").await { + log_uncaptured("skipping change stream test due to server version"); + return Ok(()); + } let (client, coll, mut stream) = match init_stream("resume_start_at_operation_time", true).await? { Some(t) => t, None => return Ok(()), }; - if !VersionReq::parse(">=4.0, <4.0.7") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test due to server version {:?}", - client.server_version - )); - return Ok(()); - } - let _guard = FailPoint::fail_command( - &["getMore"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + let fail_point = FailPoint::fail_command(&["getMore"], FailPointMode::Times(1)).error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; - coll.insert_one(doc! { "_id": 2 }, None).await?; + coll.insert_one(doc! { "_id": 2 }).await?; stream.next().await.transpose()?; - let events = client.get_command_events(&["aggregate"]); + let events = client.events.get_command_events(&["aggregate"]); assert_eq!(events.len(), 4); fn has_saot(command: &Document) -> Result { @@ -365,29 +332,21 @@ async fn resume_start_at_operation_time() -> Result<()> { /// Prose test 11: Running against a server >=4.0.7, resume token at the end of a batch must return /// the postBatchResumeToken from the current command response -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn batch_end_resume_token() -> Result<()> { - let _guard = LOCK.run_concurrently().await; + if !server_version_matches(">=4.0.7").await { + log_uncaptured("skipping change stream test due to server version"); + return Ok(()); + } let (client, _, mut stream) = match init_stream("batch_end_resume_token", false).await? { Some(t) => t, None => return Ok(()), }; - if !VersionReq::parse(">=4.0.7") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test due to server version {:?}", - client.server_version - )); - return Ok(()); - } assert_eq!(stream.next_if_any().await?, None); let token = stream.resume_token().unwrap().parsed()?; - let commands = client.get_command_events(&["aggregate", "getMore"]); + let commands = client.events.get_command_events(&["aggregate", "getMore"]); assert!(matches!(commands.last(), Some( CommandEvent::Succeeded(CommandSucceededEvent { reply, @@ -399,46 +358,30 @@ async fn batch_end_resume_token() -> Result<()> { } /// Prose test 12: Running against a server <4.0.7, end of batch resume token must follow the spec -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn batch_end_resume_token_legacy() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - - let (client, coll, mut stream) = - match init_stream("batch_end_resume_token_legacy", false).await? { - Some(t) => t, - None => return Ok(()), - }; - if !VersionReq::parse("<4.0.7") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test due to server version {:?}", - client.server_version - )); + if !server_version_matches("<4.0.7").await { + log_uncaptured("skipping change stream test due to server version"); return Ok(()); } + let (_, coll, mut stream) = match init_stream("batch_end_resume_token_legacy", false).await? { + Some(t) => t, + None => return Ok(()), + }; + // Case: empty batch, `resume_after` not specified assert_eq!(stream.next_if_any().await?, None); assert_eq!(stream.resume_token(), None); // Case: end of batch - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; let expected_id = stream.next_if_any().await?.unwrap().id; assert_eq!(stream.next_if_any().await?, None); assert_eq!(stream.resume_token().as_ref(), Some(&expected_id)); // Case: empty batch, `resume_after` specified - let mut stream = coll - .watch( - None, - ChangeStreamOptions::builder() - .resume_after(Some(expected_id.clone())) - .build(), - ) - .await?; + let mut stream = coll.watch().resume_after(expected_id.clone()).await?; assert_eq!(stream.next_if_any().await?, None); assert_eq!(stream.resume_token(), Some(expected_id)); @@ -446,11 +389,8 @@ async fn batch_end_resume_token_legacy() -> Result<()> { } /// Prose test 13: Mid-batch resume token must be `_id` of last document returned. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn batch_mid_resume_token() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - let (_, coll, mut stream) = match init_stream("batch_mid_resume_token", false).await? { Some(t) => t, None => return Ok(()), @@ -466,7 +406,7 @@ async fn batch_mid_resume_token() -> Result<()> { } // If we're out of events, make some more. None => { - coll.insert_many((0..3).map(|_| doc! {}), None).await?; + coll.insert_many((0..3).map(|_| doc! {})).await?; } }; @@ -484,59 +424,37 @@ async fn batch_mid_resume_token() -> Result<()> { /// Prose test 14: Resume token with a non-empty batch for the initial `aggregate` must follow the /// spec. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn aggregate_batch() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - - let (client, coll, mut stream) = match init_stream("aggregate_batch", false).await? { - Some(t) => t, - None => return Ok(()), - }; - if client.is_sharded() { + if topology_is_sharded().await { log_uncaptured("skipping change stream test on unsupported topology"); return Ok(()); } - if !VersionReq::parse(">=4.2") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test on unsupported version {:?}", - client.server_version - )); + if server_version_lt(4, 2).await { + log_uncaptured("skipping change stream test on unsupported version > 4.2"); return Ok(()); } + let (_, coll, mut stream) = match init_stream("aggregate_batch", false).await? { + Some(t) => t, + None => return Ok(()), + }; + // Synthesize a resume token for the new stream to start at. - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await; let token = stream.resume_token().unwrap(); // Populate the initial batch of the new stream. - coll.insert_one(doc! {}, None).await?; - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; + coll.insert_one(doc! {}).await?; // Case: `start_after` is given - let stream = coll - .watch( - None, - ChangeStreamOptions::builder() - .start_after(Some(token.clone())) - .build(), - ) - .await?; + let stream = coll.watch().start_after(token.clone()).await?; assert_eq!(stream.resume_token().as_ref(), Some(&token)); // Case: `resume_after` is given - let stream = coll - .watch( - None, - ChangeStreamOptions::builder() - .resume_after(Some(token.clone())) - .build(), - ) - .await?; + let stream = coll.watch().resume_after(token.clone()).await?; assert_eq!(stream.resume_token().as_ref(), Some(&token)); Ok(()) @@ -546,51 +464,31 @@ async fn aggregate_batch() -> Result<()> { // Prose test 16: removed /// Prose test 17: Resuming a change stream with no results uses `startAfter`. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn resume_uses_start_after() -> Result<()> { - let _guard = LOCK.run_exclusively().await; + if !server_version_matches(">=4.1.1").await { + log_uncaptured("skipping change stream test on unsupported version"); + return Ok(()); + } let (client, coll, mut stream) = match init_stream("resume_uses_start_after", true).await? { Some(t) => t, None => return Ok(()), }; - if !VersionReq::parse(">=4.1.1") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test on unsupported version {:?}", - client.server_version - )); - return Ok(()); - } - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await.transpose()?; let token = stream.resume_token().unwrap(); - let mut stream = coll - .watch( - None, - ChangeStreamOptions::builder() - .start_after(Some(token.clone())) - .build(), - ) - .await?; + let mut stream = coll.watch().start_after(token.clone()).await?; // Create an event, and synthesize a resumable error when calling `getMore` for that event. - coll.insert_one(doc! {}, None).await?; - let _guard = FailPoint::fail_command( - &["getMore"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + coll.insert_one(doc! {}).await?; + let fail_point = FailPoint::fail_command(&["getMore"], FailPointMode::Times(1)).error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; stream.next().await.transpose()?; - let commands = client.get_command_started_events(&["aggregate"]); + let commands = client.events.get_command_started_events(&["aggregate"]); fn has_start_after(command: &Document) -> Result { let stage = command.get_array("pipeline")?[0] .as_document() @@ -612,55 +510,35 @@ async fn resume_uses_start_after() -> Result<()> { } /// Prose test 18: Resuming a change stream after results uses `resumeAfter`. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn resume_uses_resume_after() -> Result<()> { - let _guard = LOCK.run_exclusively().await; + if !server_version_matches(">=4.1.1").await { + log_uncaptured("skipping change stream test on unsupported version"); + return Ok(()); + } let (client, coll, mut stream) = match init_stream("resume_uses_resume_after", true).await? { Some(t) => t, None => return Ok(()), }; - if !VersionReq::parse(">=4.1.1") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test on unsupported version {:?}", - client.server_version - )); - return Ok(()); - } - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await.transpose()?; let token = stream.resume_token().unwrap(); - let mut stream = coll - .watch( - None, - ChangeStreamOptions::builder() - .start_after(Some(token.clone())) - .build(), - ) - .await?; + let mut stream = coll.watch().start_after(token.clone()).await?; // Create an event and read it. - coll.insert_one(doc! {}, None).await?; + coll.insert_one(doc! {}).await?; stream.next().await.transpose()?; // Create an event, and synthesize a resumable error when calling `getMore` for that event. - coll.insert_one(doc! {}, None).await?; - let _guard = FailPoint::fail_command( - &["getMore"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(43).build(), - ) - .enable(&client, None) - .await?; + coll.insert_one(doc! {}).await?; + let fail_point = FailPoint::fail_command(&["getMore"], FailPointMode::Times(1)).error_code(43); + let _guard = client.enable_fail_point(fail_point).await?; stream.next().await.transpose()?; - let commands = client.get_command_started_events(&["aggregate"]); + let commands = client.events.get_command_started_events(&["aggregate"]); fn has_resume_after(command: &Document) -> Result { let stage = command.get_array("pipeline")?[0] .as_document() @@ -681,83 +559,58 @@ async fn resume_uses_resume_after() -> Result<()> { Ok(()) } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn create_coll_pre_post() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - if !VersionReq::parse(">=6.0") - .unwrap() - .matches(&client.server_version) - { - log_uncaptured(format!( - "skipping change stream test on unsupported version {:?}", - client.server_version - )); + if server_version_lt(6, 0).await { + log_uncaptured("skipping change stream test on unsupported version"); return Ok(()); } + let client = Client::for_test().await; + let db = client.database("create_coll_pre_post"); - db.collection::("test").drop(None).await?; - db.create_collection( - "test", - CreateCollectionOptions::builder() - .change_stream_pre_and_post_images(ChangeStreamPreAndPostImages { enabled: true }) - .build(), - ) - .await?; + db.collection::("test").drop().await?; + db.create_collection("test") + .change_stream_pre_and_post_images(ChangeStreamPreAndPostImages { enabled: true }) + .await?; Ok(()) } // Prose test 19: large event splitting -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn split_large_event() -> Result<()> { - let _guard = LOCK.run_concurrently().await; - - let client = Client::test_builder().build().await; - if !client.server_version_gte(7, 0) { - log_uncaptured(format!( - "skipping change stream test on unsupported version {:?}", - client.server_version - )); + if !topology_is_replica_set().await && !topology_is_sharded().await { + log_uncaptured("skipping change stream test on unsupported topology"); return Ok(()); } - if !client.is_replica_set() && !client.is_sharded() { - log_uncaptured("skipping change stream test on unsupported topology"); + if !(server_version_matches(">= 6.0.9, < 6.1").await || server_version_gte(7, 0).await) { + log_uncaptured("skipping change stream test on unsupported version"); return Ok(()); } + let client = Client::for_test().await; + let db = client.database("change_stream_tests"); db.collection::("split_large_event") - .drop(None) + .drop() + .await?; + db.create_collection("split_large_event") + .change_stream_pre_and_post_images(ChangeStreamPreAndPostImages { enabled: true }) .await?; - db.create_collection( - "split_large_event", - CreateCollectionOptions::builder() - .change_stream_pre_and_post_images(ChangeStreamPreAndPostImages { enabled: true }) - .build(), - ) - .await?; let coll = db.collection::("split_large_event"); - coll.insert_one(doc! { "value": "q".repeat(10 * 1024 * 1024) }, None) + coll.insert_one(doc! { "value": "q".repeat(10 * 1024 * 1024) }) .await?; let stream = coll - .watch( - [doc! { "$changeStreamSplitLargeEvent": {} }], - ChangeStreamOptions::builder() - .full_document_before_change(Some(FullDocumentBeforeChangeType::Required)) - .build(), - ) + .watch() + .pipeline([doc! { "$changeStreamSplitLargeEvent": {} }]) + .full_document_before_change(FullDocumentBeforeChangeType::Required) .await? .with_type::(); coll.update_one( doc! {}, doc! { "$set": { "value": "z".repeat(10 * 1024 * 1024) } }, - None, ) .await?; @@ -774,3 +627,62 @@ async fn split_large_event() -> Result<()> { Ok(()) } + +/// Test that transaction fields are parsed correctly +#[tokio::test] +async fn transaction_fields() -> Result<()> { + if topology_is_sharded().await { + log_uncaptured("skipping change stream test transaction_fields on unsupported topology"); + return Ok(()); + } + if server_version_lt(5, 0).await { + log_uncaptured( + "skipping change stream test transaction_fields on unsupported server version", + ); + return Ok(()); + } + if !transactions_supported().await { + log_uncaptured( + "skipping change stream transaction_fields test due to lack of transaction support", + ); + return Ok(()); + } + + let (client, coll, mut stream) = + match init_stream("chang_stream_transaction_fields", true).await? { + Some(t) => t, + None => return Ok(()), + }; + + let mut session = client.start_session().await.unwrap(); + let session_id = session.id().get("id").cloned(); + assert!(session_id.is_some()); + session.start_transaction().await.unwrap(); + coll.insert_one(doc! {"_id": 1}) + .session(&mut session) + .await?; + session.commit_transaction().await.unwrap(); + + let next_event = stream.next().await.transpose()?; + assert!(matches!(next_event, + Some(ChangeStreamEvent { + operation_type: OperationType::Insert, + document_key: Some(key), + lsid: Some(lsid), + txn_number: Some(1), + .. + }) if key == doc! { "_id": 1 } && lsid.get("id") == session_id.as_ref() + )); + + Ok(()) +} + +// Regression test: `Collection::watch` uses the type parameter. This is not flagged as a test to +// run because it's just asserting that this compiles. +#[allow(unreachable_code, unused_variables, clippy::diverging_sub_expression)] +async fn _collection_watch_typed() { + let coll: Collection = unimplemented!(); + let mut stream = coll.watch().await.unwrap(); + let _: Option>> = + stream.next().await; +} diff --git a/src/test/client.rs b/src/test/client.rs index 407167f1b..95033b768 100644 --- a/src/test/client.rs +++ b/src/test/client.rs @@ -1,33 +1,46 @@ -use std::{borrow::Cow, collections::HashMap, sync::Arc, time::Duration}; +use std::{ + borrow::Cow, + collections::HashMap, + future::IntoFuture, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + time::Duration, +}; -use bson::Document; -use serde::Deserialize; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; +use crate::bson::Document; +use serde::{Deserialize, Serialize}; use crate::{ bson::{doc, Bson}, error::{CommandError, Error, ErrorKind}, - event::cmap::CmapEvent, + event::{cmap::CmapEvent, sdam::SdamEvent}, hello::LEGACY_HELLO_COMMAND_NAME, - options::{AuthMechanism, ClientOptions, Credential, ListDatabasesOptions, ServerAddress}, + options::{AuthMechanism, Credential, ServerAddress}, runtime, selection_criteria::{ReadPreference, ReadPreferenceOptions, SelectionCriteria}, test::{ + auth_enabled, + get_client_options, log_uncaptured, - util::TestClient, + topology_is_replica_set, + topology_is_sharded, + topology_is_standalone, + transactions_supported, + util::{ + event_buffer::{EventBuffer, EventStream}, + fail_point::{FailPoint, FailPointMode}, + }, Event, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - SdamEvent, - CLIENT_OPTIONS, - LOCK, + SERVER_API, }, Client, ServerType, }; +use super::{ + fail_command_appname_initial_handshake_supported, + streaming_monitor_protocol_supported, +}; + #[derive(Debug, Deserialize)] struct ClientMetadata { pub driver: DriverMetadata, @@ -42,28 +55,22 @@ struct DriverMetadata { pub version: String, } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn metadata_sent_in_handshake() { - let _: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - // skip on other topologies due to different currentOp behavior - if !client.is_standalone() || !client.is_replica_set() { + if !(topology_is_standalone().await || topology_is_replica_set().await) { log_uncaptured("skipping metadata_sent_in_handshake due to unsupported topology"); return; } + let client = Client::for_test().await; + let result = client .database("admin") - .run_command( - doc! { - "currentOp": 1, - "command.currentOp": { "$exists": true } - }, - None, - ) + .run_command(doc! { + "currentOp": 1, + "command.currentOp": { "$exists": true } + }) .await .unwrap(); @@ -73,30 +80,19 @@ async fn metadata_sent_in_handshake() { .get_document("clientMetadata") .unwrap() .clone(); - let metadata: ClientMetadata = bson::from_document(metadata_document).unwrap(); + let metadata: ClientMetadata = + crate::bson_compat::deserialize_from_document(metadata_document).unwrap(); assert_eq!(metadata.driver.name, "mongo-rust-driver"); assert_eq!(metadata.driver.version, env!("CARGO_PKG_VERSION")); - #[cfg(feature = "tokio-runtime")] - { - assert!( - metadata.platform.contains("tokio"), - "platform should contain tokio: {}", - metadata.platform - ); - } - - #[cfg(feature = "async-std-runtime")] - { - assert!( - metadata.platform.contains("async-std"), - "platform should contain async-std: {}", - metadata.platform - ); - } + assert!( + metadata.platform.contains("tokio"), + "platform should contain tokio: {}", + metadata.platform + ); - #[cfg(any(feature = "sync", feature = "tokio-sync"))] + #[cfg(feature = "sync")] { assert!( metadata.platform.contains("sync"), @@ -106,51 +102,43 @@ async fn metadata_sent_in_handshake() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn connection_drop_during_read() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.max_pool_size = Some(1); let client = Client::with_options(options.clone()).unwrap(); let db = client.database("test"); db.collection(function_name!()) - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); let _: Result<_, _> = runtime::timeout( Duration::from_millis(50), - db.run_command( - doc! { - "count": function_name!(), - "query": { - "$where": "sleep(100) && true" - } - }, - None, - ), + db.run_command(doc! { + "count": function_name!(), + "query": { + "$where": "sleep(100) && true" + } + }) + .into_future(), ) .await; - runtime::delay_for(Duration::from_millis(200)).await; + tokio::time::sleep(Duration::from_millis(200)).await; - let build_info_response = db.run_command(doc! { "buildInfo": 1 }, None).await.unwrap(); + let build_info_response = db.run_command(doc! { "buildInfo": 1 }).await.unwrap(); // Ensure that the response to `buildInfo` is read, not the response to `count`. assert!(build_info_response.get("version").is_some()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn server_selection_timeout_message() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - if CLIENT_OPTIONS.get().await.repl_set_name.is_none() { + if get_client_options().await.repl_set_name.is_none() { log_uncaptured("skipping server_selection_timeout_message due to missing replica set name"); return; } @@ -159,21 +147,23 @@ async fn server_selection_timeout_message() { tag_set.insert("asdfasdf".to_string(), "asdfadsf".to_string()); let unsatisfiable_read_preference = ReadPreference::Secondary { - options: ReadPreferenceOptions::builder() - .tag_sets(vec![tag_set]) - .build(), + options: Some( + ReadPreferenceOptions::builder() + .tag_sets(vec![tag_set]) + .build(), + ), }; - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.server_selection_timeout = Some(Duration::from_millis(500)); let client = Client::with_options(options.clone()).unwrap(); let db = client.database("test"); let error = db - .run_command( - doc! { "ping": 1 }, - SelectionCriteria::ReadPreference(unsatisfiable_read_preference), - ) + .run_command(doc! { "ping": 1 }) + .selection_criteria(SelectionCriteria::ReadPreference( + unsatisfiable_read_preference, + )) .await .expect_err("should fail with server selection timeout error"); @@ -183,25 +173,22 @@ async fn server_selection_timeout_message() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn list_databases() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let expected_dbs = &[ format!("{}1", function_name!()), format!("{}2", function_name!()), format!("{}3", function_name!()), ]; - let client = TestClient::new().await; + let client = Client::for_test().await; for name in expected_dbs { - client.database(name).drop(None).await.unwrap(); + client.database(name).drop().await.unwrap(); } - let prev_dbs = client.list_databases(None, None).await.unwrap(); + let prev_dbs = client.list_databases().await.unwrap(); for name in expected_dbs { assert!(!prev_dbs.iter().any(|doc| doc.name.as_str() == name)); @@ -209,12 +196,12 @@ async fn list_databases() { let db = client.database(name); db.collection("foo") - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); } - let new_dbs = client.list_databases(None, None).await.unwrap(); + let new_dbs = client.list_databases().await.unwrap(); let new_dbs: Vec<_> = new_dbs .into_iter() .filter(|db_spec| expected_dbs.contains(&db_spec.name)) @@ -231,13 +218,10 @@ async fn list_databases() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn list_database_names() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let expected_dbs = &[ format!("{}1", function_name!()), @@ -246,10 +230,10 @@ async fn list_database_names() { ]; for name in expected_dbs { - client.database(name).drop(None).await.unwrap(); + client.database(name).drop().await.unwrap(); } - let prev_dbs = client.list_database_names(None, None).await.unwrap(); + let prev_dbs = client.list_database_names().await.unwrap(); for name in expected_dbs { assert!(!prev_dbs.iter().any(|db_name| db_name == name)); @@ -257,30 +241,28 @@ async fn list_database_names() { let db = client.database(name); db.collection("foo") - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); } - let new_dbs = client.list_database_names(None, None).await.unwrap(); + let new_dbs = client.list_database_names().await.unwrap(); for name in expected_dbs { assert_eq!(new_dbs.iter().filter(|db_name| db_name == &name).count(), 1); } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn list_authorized_databases() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - if client.server_version_lt(4, 0) || !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping list_authorized_databases due to test configuration"); return; } + let client = Client::for_test().await; + let dbs = &[ format!("{}1", function_name!()), format!("{}2", function_name!()), @@ -289,7 +271,7 @@ async fn list_authorized_databases() { for name in dbs { client .database(name) - .create_collection("coll", None) + .create_collection("coll") .await .unwrap(); client @@ -305,7 +287,7 @@ async fn list_authorized_databases() { } for name in dbs { - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); let credential = Credential::builder() .username(format!("user_{}", name)) .password(String::from("pwd")) @@ -313,17 +295,18 @@ async fn list_authorized_databases() { options.credential = Some(credential); let client = Client::with_options(options).unwrap(); - let options = ListDatabasesOptions::builder() + let result = client + .list_database_names() .authorized_databases(true) - .build(); - let result = client.list_database_names(None, options).await.unwrap(); + .await + .unwrap(); assert_eq!(result.len(), 1); - assert_eq!(result.get(0).unwrap(), name); + assert_eq!(result.first().unwrap(), name); } for name in dbs { - client.database(name).drop(None).await.unwrap(); + client.database(name).drop().await.unwrap(); } } @@ -334,7 +317,7 @@ fn is_auth_error(error: Error) -> bool { /// Performs an operation that requires authentication and verifies that it either succeeded or /// failed with an authentication error according to the `should_succeed` parameter. async fn auth_test(client: Client, should_succeed: bool) { - let result = client.list_database_names(None, None).await; + let result = client.list_database_names().await; if should_succeed { result.expect("operation should have succeeded"); } else { @@ -352,7 +335,7 @@ async fn auth_test_options( mechanism: Option, success: bool, ) { - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.max_pool_size = Some(1); options.credential = Credential { username: Some(user.to_string()), @@ -375,8 +358,13 @@ async fn auth_test_uri( mechanism: Option, should_succeed: bool, ) { - let host = CLIENT_OPTIONS - .get() + // A server API version cannot be set in the connection string. + if SERVER_API.is_some() { + log_uncaptured("Skipping URI auth test due to server API version being set"); + return; + } + + let host = get_client_options() .await .hosts .iter() @@ -395,7 +383,7 @@ async fn auth_test_uri( mechanism_str.as_ref() ); - if let Some(ref tls_options) = CLIENT_OPTIONS.get().await.tls_options() { + if let Some(ref tls_options) = get_client_options().await.tls_options() { if let Some(true) = tls_options.allow_invalid_certificates { uri.push_str("&tlsAllowInvalidCertificates=true"); } @@ -423,7 +411,7 @@ async fn auth_test_uri( } } - if let Some(true) = CLIENT_OPTIONS.get().await.load_balanced { + if let Some(true) = get_client_options().await.load_balanced { uri.push_str("&loadBalanced=true"); } @@ -439,12 +427,7 @@ async fn auth_test_uri( /// /// If only one mechanism is supplied, this will also test that using the other SCRAM mechanism will /// fail. -async fn scram_test( - client: &TestClient, - username: &str, - password: &str, - mechanisms: &[AuthMechanism], -) { +async fn scram_test(username: &str, password: &str, mechanisms: &[AuthMechanism]) { for mechanism in mechanisms { auth_test_uri(username, password, Some(mechanism.clone()), true).await; auth_test_uri(username, password, None, true).await; @@ -453,7 +436,7 @@ async fn scram_test( } // If only one scram mechanism is specified, verify the other doesn't work. - if mechanisms.len() == 1 && client.server_version_gte(4, 0) { + if mechanisms.len() == 1 { let other = match mechanisms[0] { AuthMechanism::ScramSha1 => AuthMechanism::ScramSha256, _ => AuthMechanism::ScramSha1, @@ -463,17 +446,15 @@ async fn scram_test( } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn scram_sha1() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - if !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping scram_sha1 due to missing authentication"); return; } + let client = Client::for_test().await; + client .create_user( "sha1", @@ -484,19 +465,17 @@ async fn scram_sha1() { ) .await .unwrap(); - scram_test(&client, "sha1", "sha1", &[AuthMechanism::ScramSha1]).await; + scram_test("sha1", "sha1", &[AuthMechanism::ScramSha1]).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn scram_sha256() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - if client.server_version_lt(4, 0) || !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping scram_sha256 due to test configuration"); return; } + + let client = Client::for_test().await; client .create_user( "sha256", @@ -507,19 +486,17 @@ async fn scram_sha256() { ) .await .unwrap(); - scram_test(&client, "sha256", "sha256", &[AuthMechanism::ScramSha256]).await; + scram_test("sha256", "sha256", &[AuthMechanism::ScramSha256]).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn scram_both() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - if client.server_version_lt(4, 0) || !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping scram_both due to test configuration"); return; } + + let client = Client::for_test().await; client .create_user( "both", @@ -531,7 +508,6 @@ async fn scram_both() { .await .unwrap(); scram_test( - &client, "both", "both", &[AuthMechanism::ScramSha1, AuthMechanism::ScramSha256], @@ -539,44 +515,33 @@ async fn scram_both() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn scram_missing_user_uri() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - if !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping scram_missing_user_uri due to missing authentication"); return; } auth_test_uri("adsfasdf", "ASsdfsadf", None, false).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn scram_missing_user_options() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - if !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping scram_missing_user_options due to missing authentication"); return; } auth_test_options("sadfasdf", "fsdadsfasdf", None, false).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn saslprep() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - - if client.server_version_lt(4, 0) || !client.auth_enabled() { + if !auth_enabled().await { log_uncaptured("skipping saslprep due to test configuration"); return; } + let client = Client::for_test().await; + client .create_user( "IX", @@ -609,21 +574,15 @@ async fn saslprep() { auth_test_uri("%E2%85%A8", "I%C2%ADV", None, true).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] -async fn x509_auth() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let username = match std::env::var("MONGO_X509_USER") { - Ok(user) => user, - Err(_) => return, - }; +async fn x509_auth_skip_ci() { + let username = std::env::var("MONGO_X509_USER").expect("MONGO_X509_USER"); - let client = TestClient::new().await; + let client = Client::for_test().await; let drop_user_result = client .database("$external") - .run_command(doc! { "dropUser": &username }, None) + .run_command(doc! { "dropUser": &username }) .await; match drop_user_result.map_err(|e| *e.kind) { @@ -644,146 +603,92 @@ async fn x509_auth() { .await .unwrap(); - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.credential = Some( Credential::builder() .mechanism(AuthMechanism::MongoDbX509) .build(), ); - let client = TestClient::with_options(Some(options)).await; + let client = Client::for_test().options(options).await; client .database(function_name!()) .collection::(function_name!()) - .find_one(None, None) + .find_one(doc! {}) .await .unwrap(); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn plain_auth() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - if std::env::var("MONGO_PLAIN_AUTH_TEST").is_err() { - log_uncaptured("skipping plain_auth due to environment variable MONGO_PLAIN_AUTH_TEST"); - return; - } - - let options = ClientOptions::builder() - .hosts(vec![ServerAddress::Tcp { - host: "ldaptest.10gen.cc".into(), - port: None, - }]) - .credential( - Credential::builder() - .mechanism(AuthMechanism::Plain) - .username("drivers-team".to_string()) - .password("mongor0x$xgen".to_string()) - .build(), - ) - .build(); - - let client = Client::with_options(options).unwrap(); - let coll = client.database("ldap").collection("test"); - - let doc = coll.find_one(None, None).await.unwrap().unwrap(); - - #[derive(Debug, Deserialize, PartialEq)] - struct TestDocument { - ldap: bool, - authenticated: String, - } - - let doc: TestDocument = bson::from_document(doc).unwrap(); - - assert_eq!( - doc, - TestDocument { - ldap: true, - authenticated: "yeah".into() - } - ); -} - /// Test verifies that retrying a commitTransaction operation after a checkOut /// failure works. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn retry_commit_txn_check_out() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let setup_client = TestClient::new().await; - if !setup_client.is_replica_set() { + if !topology_is_replica_set().await { log_uncaptured("skipping retry_commit_txn_check_out due to non-replicaset topology"); return; } - - if !setup_client.supports_transactions() { + if !transactions_supported().await { log_uncaptured("skipping retry_commit_txn_check_out due to lack of transaction support"); return; } - - if !setup_client.supports_fail_command_appname_initial_handshake() { + if !fail_command_appname_initial_handshake_supported().await { log_uncaptured( "skipping retry_commit_txn_check_out due to insufficient failCommand support", ); return; } - - if setup_client.supports_streaming_monitoring_protocol() { + if streaming_monitor_protocol_supported().await { log_uncaptured("skipping retry_commit_txn_check_out due to streaming protocol support"); return; } + let setup_client = Client::for_test().await; + // ensure namespace exists setup_client .database("retry_commit_txn_check_out") .collection("retry_commit_txn_check_out") - .insert_one(doc! {}, None) + .insert_one(doc! {}) .await .unwrap(); - let mut options = CLIENT_OPTIONS.get().await.clone(); - let handler = Arc::new(EventHandler::new()); - options.cmap_event_handler = Some(handler.clone()); - options.sdam_event_handler = Some(handler.clone()); + let mut options = get_client_options().await.clone(); + let buffer = EventBuffer::new(); + options.cmap_event_handler = Some(buffer.handler()); + options.sdam_event_handler = Some(buffer.handler()); options.heartbeat_freq = Some(Duration::from_secs(120)); options.app_name = Some("retry_commit_txn_check_out".to_string()); let client = Client::with_options(options).unwrap(); - let mut session = client.start_session(None).await.unwrap(); - session.start_transaction(None).await.unwrap(); + let mut session = client.start_session().await.unwrap(); + session.start_transaction().await.unwrap(); // transition transaction to "in progress" so that the commit // actually executes an operation. client .database("retry_commit_txn_check_out") .collection("retry_commit_txn_check_out") - .insert_one_with_session(doc! {}, None, &mut session) + .insert_one(doc! {}) + .session(&mut session) .await .unwrap(); - // enable a fail point that clears the connection pools so that - // commitTransaction will create a new connection during check out. - let fp = FailPoint::fail_command( - &["ping"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(11600).build(), - ); - let _guard = setup_client.enable_failpoint(fp, None).await.unwrap(); + // Enable a fail point that clears the connection pools so that commitTransaction will create a + // new connection during checkout. + let fail_point = FailPoint::fail_command(&["ping"], FailPointMode::Times(1)).error_code(11600); + let _guard = setup_client.enable_fail_point(fail_point).await.unwrap(); - let mut subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); client .database("foo") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await .unwrap_err(); // failing with a state change error will request an immediate check // wait for the mark unknown and subsequent succeeded heartbeat let mut primary = None; - subscriber - .wait_for_event(Duration::from_secs(1), |e| { + event_stream + .next_match(Duration::from_secs(1), |e| { if let Event::Sdam(SdamEvent::ServerDescriptionChanged(event)) = e { if event.is_marked_unknown_event() { primary = Some(event.address.clone()); @@ -799,8 +704,8 @@ async fn retry_commit_txn_check_out() { // This is because the monitors are waiting for the next heartbeat from the server for // heartbeatFrequencyMS (which is 2 minutes) and ignore the immediate check requests from the // ping command in the meantime due to already being in the middle of their checks. - subscriber - .wait_for_event(Duration::from_secs(1), |e| { + event_stream + .next_match(Duration::from_secs(1), |e| { if let Event::Sdam(SdamEvent::ServerDescriptionChanged(event)) = e { if &event.address == primary.as_ref().unwrap() && event.previous_description.server_type() == ServerType::Unknown @@ -813,35 +718,305 @@ async fn retry_commit_txn_check_out() { .await .expect("should see mark available event"); - // enable a failpoint on the handshake to cause check_out - // to fail with a retryable error - let fp = FailPoint::fail_command( + let fail_point = FailPoint::fail_command( &[LEGACY_HELLO_COMMAND_NAME, "hello"], FailPointMode::Times(1), - FailCommandOptions::builder() - .error_code(11600) - .app_name("retry_commit_txn_check_out".to_string()) - .build(), - ); - let _guard2 = setup_client.enable_failpoint(fp, None).await.unwrap(); + ) + .error_code(11600) + .app_name("retry_commit_txn_check_out"); + let _guard2 = setup_client.enable_fail_point(fail_point).await.unwrap(); // finally, attempt the commit. // this should succeed due to retry session.commit_transaction().await.unwrap(); // ensure the first check out attempt fails - subscriber - .wait_for_event(Duration::from_secs(1), |e| { + event_stream + .next_match(Duration::from_secs(1), |e| { matches!(e, Event::Cmap(CmapEvent::ConnectionCheckoutFailed(_))) }) .await .expect("should see check out failed event"); // ensure the second one succeeds - subscriber - .wait_for_event(Duration::from_secs(1), |e| { + event_stream + .next_match(Duration::from_secs(1), |e| { matches!(e, Event::Cmap(CmapEvent::ConnectionCheckedOut(_))) }) .await .expect("should see checked out event"); } + +/// Verifies that `Client::shutdown` succeeds. +#[tokio::test] +async fn manual_shutdown_with_nothing() { + let client = Client::for_test().await.into_client(); + client.shutdown().await; +} + +/// Verifies that `Client::shutdown` succeeds when resources have been dropped. +#[tokio::test] +async fn manual_shutdown_with_resources() { + if !transactions_supported().await { + log_uncaptured("Skipping manual_shutdown_with_resources: no transaction support"); + return; + } + + let client = Client::for_test().monitor_events().await; + let db = client.database("shutdown_test"); + db.drop().await.unwrap(); + let coll = db.collection::("test"); + coll.insert_many([doc! {}, doc! {}]).await.unwrap(); + let bucket = db.gridfs_bucket(None); + // Scope to force drop of resources + { + // Exhausted cursors don't need cleanup, so make sure there's more than one batch to fetch + let _cursor = coll.find(doc! {}).batch_size(1).await.unwrap(); + // Similarly, sessions need an in-progress transaction to have cleanup. + let mut session = client.start_session().await.unwrap(); + if session.start_transaction().await.is_err() { + // Transaction start can transiently fail; if so, just bail out of the test. + log_uncaptured("Skipping manual_shutdown_with_resources: transaction start failed"); + return; + } + if coll + .insert_one(doc! {}) + .session(&mut session) + .await + .is_err() + { + // Likewise for transaction operations. + log_uncaptured("Skipping manual_shutdown_with_resources: transaction operation failed"); + return; + } + let _stream = bucket.open_upload_stream("test").await.unwrap(); + } + let is_sharded = topology_is_sharded().await; + let events = client.events.clone(); + client.into_client().shutdown().await; + if !is_sharded { + // killCursors doesn't always execute on sharded clusters due to connection pinning + assert!(!events + .get_command_started_events(&["killCursors"]) + .is_empty()); + } + assert!(!events + .get_command_started_events(&["abortTransaction"]) + .is_empty()); + assert!(!events.get_command_started_events(&["delete"]).is_empty()); +} + +/// Verifies that `Client::shutdown_immediate` succeeds. +#[tokio::test] +async fn manual_shutdown_immediate_with_nothing() { + let client = Client::for_test().await.into_client(); + client.shutdown().immediate(true).await; +} + +/// Verifies that `Client::shutdown_immediate` succeeds without waiting for resources. +#[tokio::test] +async fn manual_shutdown_immediate_with_resources() { + if !transactions_supported().await { + log_uncaptured("Skipping manual_shutdown_immediate_with_resources: no transaction support"); + return; + } + + let client = Client::for_test().monitor_events().await; + let db = client.database("shutdown_test"); + db.drop().await.unwrap(); + let coll = db.collection::("test"); + coll.insert_many([doc! {}, doc! {}]).await.unwrap(); + let bucket = db.gridfs_bucket(None); + + // Resources are scoped to past the `shutdown_immediate`. + + // Exhausted cursors don't need cleanup, so make sure there's more than one batch to fetch + let _cursor = coll.find(doc! {}).batch_size(1).await.unwrap(); + // Similarly, sessions need an in-progress transaction to have cleanup. + let mut session = client.start_session().await.unwrap(); + session.start_transaction().await.unwrap(); + coll.insert_one(doc! {}) + .session(&mut session) + .await + .unwrap(); + let _stream = bucket.open_upload_stream("test").await.unwrap(); + + let events = client.events.clone(); + client.into_client().shutdown().immediate(true).await; + + assert!(events + .get_command_started_events(&["killCursors"]) + .is_empty()); + assert!(events + .get_command_started_events(&["abortTransaction"]) + .is_empty()); + assert!(events.get_command_started_events(&["delete"]).is_empty()); +} + +#[tokio::test] +async fn find_one_and_delete_serde_consistency() { + let client = Client::for_test().await; + + let coll = client + .database("find_one_and_delete_serde_consistency") + .collection("test"); + + #[derive(Debug, Serialize, Deserialize)] + struct Foo { + #[serde(with = "serde_hex::SerHexSeq::")] + problematic: Vec, + } + + let doc = Foo { + problematic: vec![0, 1, 2, 3, 4, 5, 6, 7], + }; + + coll.insert_one(&doc).await.unwrap(); + let rec: Foo = coll.find_one(doc! {}).await.unwrap().unwrap(); + assert_eq!(doc.problematic, rec.problematic); + let rec: Foo = coll.find_one_and_delete(doc! {}).await.unwrap().unwrap(); + assert_eq!(doc.problematic, rec.problematic); + + let nothing = coll.find_one_and_delete(doc! {}).await.unwrap(); + assert!(nothing.is_none()); +} + +// Verifies that `Client::warm_connection_pool` succeeds. +#[tokio::test] +async fn warm_connection_pool() { + let client = Client::for_test() + .options({ + let mut opts = get_client_options().await.clone(); + opts.min_pool_size = Some(10); + opts + }) + .await; + + client.warm_connection_pool().await; + // Validate that a command executes. + client.list_database_names().await.unwrap(); +} + +async fn get_end_session_event_count(event_stream: &mut EventStream<'_, Event>) -> usize { + // Use collect_successful_command_execution to assert that the call to endSessions succeeded. + event_stream + .collect_successful_command_execution(Duration::from_millis(500), "endSessions") + .await + .len() +} + +#[tokio::test] +async fn end_sessions_on_drop() { + let client1 = Client::for_test().monitor_events().await; + let client2 = client1.clone(); + let events = client1.events.clone(); + let mut event_stream = events.stream(); + + // Run an operation to populate the session pool. + client1 + .database("db") + .collection::("coll") + .find(doc! {}) + .await + .unwrap(); + + drop(client1); + assert_eq!(get_end_session_event_count(&mut event_stream).await, 0); + + drop(client2); + assert_eq!(get_end_session_event_count(&mut event_stream).await, 1); +} + +#[tokio::test] +async fn end_sessions_on_shutdown() { + let client1 = Client::for_test().monitor_events().await; + let client2 = client1.clone(); + let events = client1.events.clone(); + let mut event_stream = events.stream(); + + // Run an operation to populate the session pool. + client1 + .database("db") + .collection::("coll") + .find(doc! {}) + .await + .unwrap(); + + client1.into_client().shutdown().await; + assert_eq!(get_end_session_event_count(&mut event_stream).await, 1); + + client2.into_client().shutdown().await; + assert_eq!(get_end_session_event_count(&mut event_stream).await, 0); +} + +#[tokio::test] +async fn ipv6_connect() { + let ipv6_localhost = Ipv6Addr::LOCALHOST.to_string(); + + let client = Client::for_test().await; + // The hello command returns the hostname as "localhost". However, whatsmyuri returns an + // IP-literal, which allows us to detect whether we can re-construct the client with an IPv6 + // address. + let is_ipv6_localhost = client + .database("admin") + .run_command(doc! { "whatsmyuri": 1 }) + .await + .ok() + .and_then(|response| { + response + .get_str("you") + .ok() + .map(|you| you.contains(&ipv6_localhost)) + }) + .unwrap_or(false); + if !is_ipv6_localhost { + log_uncaptured("skipping ipv6_connect due to non-ipv6-localhost configuration"); + return; + } + + let mut options = get_client_options().await.clone(); + for address in options.hosts.iter_mut() { + if let ServerAddress::Tcp { host, .. } = address { + *host = ipv6_localhost.clone(); + } + } + let client = Client::with_options(options).unwrap(); + + let result = client + .database("admin") + .run_command(doc! { "ping": 1 }) + .await + .unwrap(); + assert_eq!(result.get_f64("ok").unwrap(), 1.0); +} + +#[test] +fn server_address_from_socket_addr_ipv4() { + let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 27017); + let server_address = ServerAddress::from(socket_addr); + + match server_address { + ServerAddress::Tcp { host, port } => { + assert_eq!(host, "127.0.0.1", "Host was not correctly converted"); + assert_eq!(port, Some(27017), "Port was not correctly converted"); + } + _ => panic!("ServerAddress should have been Tcp variant"), + } +} + +#[test] +fn server_address_from_socket_addr_ipv6() { + let socket_addr = SocketAddr::new( + IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)), + 27017, + ); + let server_address = ServerAddress::from(socket_addr); + + match server_address { + ServerAddress::Tcp { host, port } => { + assert_eq!(host, "2001:db8::1", "Host was not correctly converted"); + assert_eq!(port, Some(27017), "Port was not correctly converted"); + } + _ => panic!("ServerAddress should have been Tcp variant"), + } +} diff --git a/src/test/coll.rs b/src/test/coll.rs index 9ee16486b..b2477cb07 100644 --- a/src/test/coll.rs +++ b/src/test/coll.rs @@ -1,87 +1,83 @@ use std::{fmt::Debug, time::Duration}; -use crate::Namespace; use futures::stream::{StreamExt, TryStreamExt}; -use lazy_static::lazy_static; -use semver::VersionReq; +use once_cell::sync::Lazy; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use crate::{ - bson::{doc, to_document, Bson, Document}, + bson::{doc, rawdoc, serde_helpers::HumanReadable, Bson, Document, RawDocumentBuf}, + bson_compat::serialize_to_document, error::{ErrorKind, Result, WriteFailure}, options::{ Acknowledgment, - AggregateOptions, CollectionOptions, DeleteOptions, DropCollectionOptions, FindOneAndDeleteOptions, - FindOneOptions, FindOptions, Hint, IndexOptions, - InsertManyOptions, ReadConcern, ReadPreference, SelectionCriteria, - UpdateOptions, WriteConcern, }, results::DeleteResult, - runtime, test::{ + get_client_options, + get_max_bson_object_size, + get_max_message_size_bytes, log_uncaptured, - util::{drop_collection, EventClient, TestClient}, - CLIENT_OPTIONS, - LOCK, + server_version_eq, + server_version_lt, + topology_is_replica_set, + topology_is_standalone, + EventClient, }, + Client, Collection, + Cursor, IndexModel, + Namespace, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn insert_err_details() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; + if server_version_lt(4, 0).await || !topology_is_replica_set().await { + log_uncaptured("skipping insert_err_details due to test configuration"); + return; + } - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; - if client.server_version_lt(4, 0) || !client.is_replica_set() { - log_uncaptured("skipping insert_err_details due to test configuration"); - return; - } client .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "data": { - "failCommands": ["insert"], - "writeConcernError": { - "code": 100, - "codeName": "UnsatisfiableWriteConcern", - "errmsg": "Not enough data-bearing nodes", - "errInfo": { - "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } - } + .run_command(doc! { + "configureFailPoint": "failCommand", + "data": { + "failCommands": ["insert"], + "writeConcernError": { + "code": 100, + "codeName": "UnsatisfiableWriteConcern", + "errmsg": "Not enough data-bearing nodes", + "errInfo": { + "writeConcern": { + "w": 2, + "wtimeout": 0, + "provenance": "clientSupplied" + } } - }, - "mode": { "times": 1 } + } }, - None, - ) + "mode": { "times": 1 } + }) .await .unwrap(); - let wc_error_result = coll.insert_one(doc! { "test": 1 }, None).await; + let wc_error_result = coll.insert_one(doc! { "test": 1 }).await; match *wc_error_result.unwrap_err().kind { ErrorKind::Write(WriteFailure::WriteConcernError(ref wc_error)) => { match &wc_error.details { @@ -101,48 +97,42 @@ async fn insert_err_details() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn count() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; - assert_eq!(coll.estimated_document_count(None).await.unwrap(), 0); + assert_eq!(coll.estimated_document_count().await.unwrap(), 0); - let _ = coll.insert_one(doc! { "x": 1 }, None).await.unwrap(); - assert_eq!(coll.estimated_document_count(None).await.unwrap(), 1); + let _ = coll.insert_one(doc! { "x": 1 }).await.unwrap(); + assert_eq!(coll.estimated_document_count().await.unwrap(), 1); let result = coll - .insert_many((1..4).map(|i| doc! { "x": i }).collect::>(), None) + .insert_many((1..4).map(|i| doc! { "x": i }).collect::>()) .await .unwrap(); assert_eq!(result.inserted_ids.len(), 3); - assert_eq!(coll.estimated_document_count(None).await.unwrap(), 4); + assert_eq!(coll.estimated_document_count().await.unwrap(), 4); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn find() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let result = coll - .insert_many((0i32..5).map(|i| doc! { "x": i }).collect::>(), None) + .insert_many((0i32..5).map(|i| doc! { "x": i }).collect::>()) .await .unwrap(); assert_eq!(result.inserted_ids.len(), 5); - let mut cursor = coll.find(None, None).await.unwrap().enumerate(); + let mut cursor = coll.find(doc! {}).await.unwrap().enumerate(); while let Some((i, result)) = cursor.next().await { let doc = result.unwrap(); @@ -153,86 +143,77 @@ async fn find() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn update() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let result = coll - .insert_many((0i32..5).map(|_| doc! { "x": 3 }).collect::>(), None) + .insert_many((0i32..5).map(|_| doc! { "x": 3 }).collect::>()) .await .unwrap(); assert_eq!(result.inserted_ids.len(), 5); let update_one_results = coll - .update_one(doc! {"x": 3}, doc! {"$set": { "x": 5 }}, None) + .update_one(doc! {"x": 3}, doc! {"$set": { "x": 5 }}) .await .unwrap(); assert_eq!(update_one_results.modified_count, 1); assert!(update_one_results.upserted_id.is_none()); let update_many_results = coll - .update_many(doc! {"x": 3}, doc! {"$set": { "x": 4}}, None) + .update_many(doc! {"x": 3}, doc! {"$set": { "x": 4}}) .await .unwrap(); assert_eq!(update_many_results.modified_count, 4); assert!(update_many_results.upserted_id.is_none()); - let options = UpdateOptions::builder().upsert(true).build(); let upsert_results = coll - .update_one(doc! {"b": 7}, doc! {"$set": { "b": 7 }}, options) + .update_one(doc! {"b": 7}, doc! {"$set": { "b": 7 }}) + .upsert(true) .await .unwrap(); assert_eq!(upsert_results.modified_count, 0); assert!(upsert_results.upserted_id.is_some()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn delete() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let result = coll - .insert_many((0i32..5).map(|_| doc! { "x": 3 }).collect::>(), None) + .insert_many((0i32..5).map(|_| doc! { "x": 3 }).collect::>()) .await .unwrap(); assert_eq!(result.inserted_ids.len(), 5); - let delete_one_result = coll.delete_one(doc! {"x": 3}, None).await.unwrap(); + let delete_one_result = coll.delete_one(doc! {"x": 3}).await.unwrap(); assert_eq!(delete_one_result.deleted_count, 1); - assert_eq!(coll.count_documents(doc! {"x": 3}, None).await.unwrap(), 4); - let delete_many_result = coll.delete_many(doc! {"x": 3}, None).await.unwrap(); + assert_eq!(coll.count_documents(doc! {"x": 3}).await.unwrap(), 4); + let delete_many_result = coll.delete_many(doc! {"x": 3}).await.unwrap(); assert_eq!(delete_many_result.deleted_count, 4); - assert_eq!(coll.count_documents(doc! {"x": 3 }, None).await.unwrap(), 0); + assert_eq!(coll.count_documents(doc! {"x": 3 }).await.unwrap(), 0); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn aggregate_out() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); let coll = db.collection(function_name!()); - drop_collection(&coll).await; + coll.drop().await.unwrap(); let result = coll - .insert_many((0i32..5).map(|n| doc! { "x": n }).collect::>(), None) + .insert_many((0i32..5).map(|n| doc! { "x": n }).collect::>()) .await .unwrap(); assert_eq!(result.inserted_ids.len(), 5); @@ -246,23 +227,21 @@ async fn aggregate_out() { }, doc! {"$out": out_coll.name()}, ]; - drop_collection(&out_coll).await; + out_coll.drop().await.unwrap(); - coll.aggregate(pipeline.clone(), None).await.unwrap(); + coll.aggregate(pipeline.clone()).await.unwrap(); assert!(db - .list_collection_names(None) + .list_collection_names() .await .unwrap() .into_iter() .any(|name| name.as_str() == out_coll.name())); - drop_collection(&out_coll).await; + out_coll.drop().await.unwrap(); // check that even with a batch size of 0, a new collection is created. - coll.aggregate(pipeline, AggregateOptions::builder().batch_size(0).build()) - .await - .unwrap(); + coll.aggregate(pipeline).batch_size(0).await.unwrap(); assert!(db - .list_collection_names(None) + .list_collection_names() .await .unwrap() .into_iter() @@ -271,35 +250,30 @@ async fn aggregate_out() { fn kill_cursors_sent(client: &EventClient) -> bool { !client + .events .get_command_started_events(&["killCursors"]) .is_empty() } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn kill_cursors_on_drop() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); let coll = db.collection(function_name!()); - drop_collection(&coll).await; + coll.drop().await.unwrap(); - coll.insert_many(vec![doc! { "x": 1 }, doc! { "x": 2 }], None) + coll.insert_many(vec![doc! { "x": 1 }, doc! { "x": 2 }]) .await .unwrap(); - let event_client = EventClient::new().await; + let event_client = Client::for_test().monitor_events().await; let coll = event_client .database(function_name!()) .collection::(function_name!()); - let cursor = coll - .find(None, FindOptions::builder().batch_size(1).build()) - .await - .unwrap(); + let cursor = coll.find(doc! {}).batch_size(1).await.unwrap(); assert!(!kill_cursors_sent(&event_client)); @@ -308,49 +282,43 @@ async fn kill_cursors_on_drop() { // The `Drop` implementation for `Cursor' spawns a back tasks that emits certain events. If the // task hasn't been scheduled yet, we may not see the event here. To account for this, we wait // for a small amount of time before checking. - runtime::delay_for(Duration::from_millis(250)).await; + tokio::time::sleep(Duration::from_millis(250)).await; assert!(kill_cursors_sent(&event_client)); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn no_kill_cursors_on_exhausted() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); let coll = db.collection(function_name!()); - drop_collection(&coll).await; + coll.drop().await.unwrap(); - coll.insert_many(vec![doc! { "x": 1 }, doc! { "x": 2 }], None) + coll.insert_many(vec![doc! { "x": 1 }, doc! { "x": 2 }]) .await .unwrap(); - let event_client = EventClient::new().await; + let event_client = Client::for_test().monitor_events().await; let coll = event_client .database(function_name!()) .collection::(function_name!()); - let cursor = coll - .find(None, FindOptions::builder().build()) - .await - .unwrap(); + let cursor = coll.find(doc! {}).await.unwrap(); assert!(!kill_cursors_sent(&event_client)); std::mem::drop(cursor); // wait for any tasks to get spawned from `Cursor`'s `Drop`. - runtime::delay_for(Duration::from_millis(250)).await; + tokio::time::sleep(Duration::from_millis(250)).await; assert!(!kill_cursors_sent(&event_client)); } -lazy_static! { - #[allow(clippy::unreadable_literal)] - static ref LARGE_DOC: Document = doc! { +#[allow(clippy::unreadable_literal)] +static LARGE_DOC: Lazy = Lazy::new(|| { + doc! { "text": "the quick brown fox jumped over the lazy sheep dog", "in_reply_to_status_id": 22213321312i64, "retweet_count": Bson::Null, @@ -398,11 +366,10 @@ lazy_static! { "listed_count": 1, "lang": "en" } - }; -} + } +}); -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn large_insert() { if std::env::consts::OS != "linux" { @@ -410,20 +377,14 @@ async fn large_insert() { return; } - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let docs = vec![LARGE_DOC.clone(); 35000]; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; assert_eq!( - coll.insert_many(docs, None) - .await - .unwrap() - .inserted_ids - .len(), + coll.insert_many(docs).await.unwrap().inserted_ids.len(), 35000 ); } @@ -451,8 +412,7 @@ fn multibatch_documents_with_duplicate_keys() -> Vec { docs } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn large_insert_unordered_with_errors() { if std::env::consts::OS != "linux" { @@ -460,23 +420,21 @@ async fn large_insert_unordered_with_errors() { return; } - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let docs = multibatch_documents_with_duplicate_keys(); - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; - let options = InsertManyOptions::builder().ordered(false).build(); match *coll - .insert_many(docs, options) + .insert_many(docs) + .ordered(false) .await .expect_err("should get error") .kind { - ErrorKind::BulkWrite(ref failure) => { + ErrorKind::InsertMany(ref failure) => { let mut write_errors = failure .write_errors .clone() @@ -492,8 +450,7 @@ async fn large_insert_unordered_with_errors() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn large_insert_ordered_with_errors() { if std::env::consts::OS != "linux" { @@ -501,23 +458,21 @@ async fn large_insert_ordered_with_errors() { return; } - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let docs = multibatch_documents_with_duplicate_keys(); - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; - let options = InsertManyOptions::builder().ordered(true).build(); match *coll - .insert_many(docs, options) + .insert_many(docs) + .ordered(true) .await .expect_err("should get error") .kind { - ErrorKind::BulkWrite(ref failure) => { + ErrorKind::InsertMany(ref failure) => { let write_errors = failure .write_errors .clone() @@ -525,7 +480,7 @@ async fn large_insert_ordered_with_errors() { assert_eq!(write_errors.len(), 1); assert_eq!(write_errors[0].index, 7499); assert_eq!( - coll.count_documents(None, None) + coll.count_documents(doc! {}) .await .expect("count should succeed"), 7499 @@ -535,18 +490,15 @@ async fn large_insert_ordered_with_errors() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn empty_insert() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .database(function_name!()) .collection::(function_name!()); match *coll - .insert_many(Vec::::new(), None) + .insert_many(Vec::::new()) .await .expect_err("should get error") .kind @@ -556,22 +508,19 @@ async fn empty_insert() { }; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn find_allow_disk_use() { let find_opts = FindOptions::builder().allow_disk_use(true).build(); allow_disk_use_test(find_opts, Some(true)).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn find_do_not_allow_disk_use() { let find_opts = FindOptions::builder().allow_disk_use(false).build(); allow_disk_use_test(find_opts, Some(false)).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn find_allow_disk_use_not_specified() { let find_opts = FindOptions::builder().build(); allow_disk_use_test(find_opts, None).await; @@ -579,68 +528,62 @@ async fn find_allow_disk_use_not_specified() { #[function_name::named] async fn allow_disk_use_test(options: FindOptions, expected_value: Option) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let event_client = EventClient::new().await; - if event_client.server_version_lt(4, 3) { + if server_version_lt(4, 3).await { log_uncaptured("skipping allow_disk_use_test due to server version < 4.3"); return; } + + let event_client = Client::for_test().monitor_events().await; let coll = event_client .database(function_name!()) .collection::(function_name!()); - coll.find(None, options).await.unwrap(); + coll.find(doc! {}).with_options(options).await.unwrap(); - let events = event_client.get_command_started_events(&["find"]); + let events = event_client.events.get_command_started_events(&["find"]); assert_eq!(events.len(), 1); let allow_disk_use = events[0].command.get_bool("allowDiskUse").ok(); assert_eq!(allow_disk_use, expected_value); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn ns_not_found_suppression() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client.get_coll(function_name!(), function_name!()); - coll.drop(None).await.expect("drop should not fail"); - coll.drop(None).await.expect("drop should not fail"); + coll.drop().await.expect("drop should not fail"); + coll.drop().await.expect("drop should not fail"); } async fn delete_hint_test(options: Option, name: &str) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll = client.database(name).collection::(name); - let _: Result = coll.delete_many(doc! {}, options.clone()).await; + let _: Result = coll + .delete_many(doc! {}) + .with_options(options.clone()) + .await; - let events = client.get_command_started_events(&["delete"]); + let events = client.events.get_command_started_events(&["delete"]); assert_eq!(events.len(), 1); let event_hint = events[0].command.get_array("deletes").unwrap()[0] .as_document() .unwrap() - .get("hint"); - let expected_hint = match options { - Some(options) => options.hint.map(|hint| hint.to_bson()), - None => None, - }; - assert_eq!(event_hint, expected_hint.as_ref()); + .get("hint") + .cloned() + .map(|bson| crate::bson_compat::deserialize_from_bson(bson).unwrap()); + let expected_hint = options.and_then(|options| options.hint); + assert_eq!(event_hint, expected_hint); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn delete_hint_keys_specified() { let options = DeleteOptions::builder().hint(Hint::Keys(doc! {})).build(); delete_hint_test(Some(options), function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn delete_hint_string_specified() { let options = DeleteOptions::builder() @@ -649,36 +592,39 @@ async fn delete_hint_string_specified() { delete_hint_test(Some(options), function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn delete_hint_not_specified() { delete_hint_test(None, function_name!()).await; } async fn find_one_and_delete_hint_test(options: Option, name: &str) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = EventClient::new().await; - - let req = VersionReq::parse(">= 4.2").unwrap(); - if options.is_some() && !req.matches(&client.server_version) { + if options.is_some() && server_version_lt(4, 2).await { log_uncaptured("skipping find_one_and_delete_hint_test due to test configuration"); return; } + let client = Client::for_test().monitor_events().await; + let coll = client.database(name).collection(name); - let _: Result> = coll.find_one_and_delete(doc! {}, options.clone()).await; + let _: Result> = coll + .find_one_and_delete(doc! {}) + .with_options(options.clone()) + .await; - let events = client.get_command_started_events(&["findAndModify"]); + let events = client.events.get_command_started_events(&["findAndModify"]); assert_eq!(events.len(), 1); - let event_hint = events[0].command.get("hint").cloned(); - let expected_hint = options.and_then(|options| options.hint.map(|hint| hint.to_bson())); + let event_hint = events[0] + .command + .get("hint") + .cloned() + .map(|bson| crate::bson_compat::deserialize_from_bson(bson).unwrap()); + let expected_hint = options.and_then(|options| options.hint); assert_eq!(event_hint, expected_hint); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn find_one_and_delete_hint_keys_specified() { let options = FindOneAndDeleteOptions::builder() @@ -687,8 +633,7 @@ async fn find_one_and_delete_hint_keys_specified() { find_one_and_delete_hint_test(Some(options), function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn find_one_and_delete_hint_string_specified() { let options = FindOneAndDeleteOptions::builder() @@ -697,35 +642,29 @@ async fn find_one_and_delete_hint_string_specified() { find_one_and_delete_hint_test(Some(options), function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn find_one_and_delete_hint_not_specified() { find_one_and_delete_hint_test(None, function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn find_one_and_delete_hint_server_version() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll = client .database(function_name!()) .collection::("coll"); - let options = FindOneAndDeleteOptions::builder() + let res = coll + .find_one_and_delete(doc! {}) .hint(Hint::Name(String::new())) - .build(); - let res = coll.find_one_and_delete(doc! {}, options).await; + .await; - let req1 = VersionReq::parse("< 4.2").unwrap(); - let req2 = VersionReq::parse("4.2.*").unwrap(); - if req1.matches(&client.server_version) { + if server_version_lt(4, 2).await { let error = res.expect_err("find one and delete should fail"); assert!(matches!(*error.kind, ErrorKind::InvalidArgument { .. })); - } else if req2.matches(&client.server_version) { + } else if server_version_eq(4, 2).await { let error = res.expect_err("find one and delete should fail"); assert!(matches!(*error.kind, ErrorKind::Command { .. })); } else { @@ -733,35 +672,29 @@ async fn find_one_and_delete_hint_server_version() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn no_read_preference_to_standalone() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if !client.is_standalone() { + if !topology_is_standalone().await { log_uncaptured("skipping no_read_preference_to_standalone due to test topology"); return; } - let options = FindOneOptions::builder() + let client = Client::for_test().monitor_events().await; + + client + .database(function_name!()) + .collection::(function_name!()) + .find_one(doc! {}) .selection_criteria(SelectionCriteria::ReadPreference( ReadPreference::SecondaryPreferred { options: Default::default(), }, )) - .build(); - - client - .database(function_name!()) - .collection::(function_name!()) - .find_one(None, options) .await .unwrap(); - let command_started = client.get_successful_command_execution("find").0; + let command_started = client.events.get_successful_command_execution("find").0; assert!(!command_started.command.contains_key("$readPreference")); } @@ -772,12 +705,10 @@ struct UserType { str: String, } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn typed_insert_one() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_typed_coll(function_name!(), function_name!()) @@ -806,9 +737,9 @@ async fn insert_one_and_find(coll: &Collection, insert_data: T) where T: Serialize + DeserializeOwned + Clone + PartialEq + Debug + Unpin + Send + Sync, { - coll.insert_one(insert_data.clone(), None).await.unwrap(); + coll.insert_one(insert_data.clone()).await.unwrap(); let result = coll - .find_one(to_document(&insert_data).unwrap(), None) + .find_one(serialize_to_document(&insert_data).unwrap()) .await .unwrap(); match result { @@ -819,13 +750,10 @@ where } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn typed_insert_many() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_typed_coll(function_name!(), function_name!()) .await; @@ -840,11 +768,11 @@ async fn typed_insert_many() { str: "b".into(), }, ]; - coll.insert_many(insert_data.clone(), None).await.unwrap(); + coll.insert_many(insert_data.clone()).await.unwrap(); - let options = FindOptions::builder().sort(doc! { "x": 1 }).build(); let actual: Vec = coll - .find(doc! { "x": 2 }, options) + .find(doc! { "x": 2 }) + .sort(doc! { "x": 1 }) .await .unwrap() .try_collect() @@ -853,13 +781,10 @@ async fn typed_insert_many() { assert_eq!(actual, insert_data); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn typed_find_one_and_replace() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_typed_coll(function_name!(), function_name!()) .await; @@ -868,30 +793,27 @@ async fn typed_find_one_and_replace() { x: 1, str: "a".into(), }; - coll.insert_one(insert_data.clone(), None).await.unwrap(); + coll.insert_one(insert_data.clone()).await.unwrap(); let replacement = UserType { x: 2, str: "b".into(), }; let result = coll - .find_one_and_replace(doc! { "x": 1 }, replacement.clone(), None) + .find_one_and_replace(doc! { "x": 1 }, replacement.clone()) .await .unwrap() .unwrap(); assert_eq!(result, insert_data); - let result = coll.find_one(doc! { "x": 2 }, None).await.unwrap().unwrap(); + let result = coll.find_one(doc! { "x": 2 }).await.unwrap().unwrap(); assert_eq!(result, replacement); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn typed_replace_one() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_typed_coll(function_name!(), function_name!()) .await; @@ -904,22 +826,19 @@ async fn typed_replace_one() { x: 2, str: "b".into(), }; - coll.insert_one(insert_data, None).await.unwrap(); - coll.replace_one(doc! { "x": 1 }, replacement.clone(), None) + coll.insert_one(insert_data).await.unwrap(); + coll.replace_one(doc! { "x": 1 }, replacement.clone()) .await .unwrap(); - let result = coll.find_one(doc! { "x": 2 }, None).await.unwrap().unwrap(); + let result = coll.find_one(doc! { "x": 2 }).await.unwrap().unwrap(); assert_eq!(result, replacement); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn typed_returns() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_typed_coll(function_name!(), function_name!()) .await; @@ -928,17 +847,17 @@ async fn typed_returns() { x: 1, str: "a".into(), }; - coll.insert_one(insert_data.clone(), None).await.unwrap(); + coll.insert_one(insert_data.clone()).await.unwrap(); let result = coll - .find_one_and_update(doc! { "x": 1 }, doc! { "$inc": { "x": 1 } }, None) + .find_one_and_update(doc! { "x": 1 }, doc! { "$inc": { "x": 1 } }) .await .unwrap() .unwrap(); assert_eq!(result, insert_data); let result = coll - .find_one_and_delete(doc! { "x": 2 }, None) + .find_one_and_delete(doc! { "x": 2 }) .await .unwrap() .unwrap(); @@ -951,38 +870,32 @@ async fn typed_returns() { ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn count_documents_with_wc() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.write_concern = WriteConcern::builder() .w(Acknowledgment::Majority) .journal(true) .build() .into(); - let client = TestClient::with_options(Some(options)).await; + let client = Client::for_test().options(options).await; let coll = client .database(function_name!()) .collection(function_name!()); - coll.insert_one(doc! {}, None).await.unwrap(); + coll.insert_one(doc! {}).await.unwrap(); - coll.count_documents(doc! {}, None) + coll.count_documents(doc! {}) .await .expect("count_documents should succeed"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn collection_options_inherited() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let read_concern = ReadConcern::majority(); let selection_criteria = SelectionCriteria::ReadPreference(ReadPreference::Secondary { @@ -997,56 +910,50 @@ async fn collection_options_inherited() { .database(function_name!()) .collection_with_options::(function_name!(), options); - coll.find(None, None).await.unwrap(); + coll.find(doc! {}).await.unwrap(); assert_options_inherited(&client, "find").await; - coll.find_one(None, None).await.unwrap(); + coll.find_one(doc! {}).await.unwrap(); assert_options_inherited(&client, "find").await; - coll.count_documents(None, None).await.unwrap(); + coll.count_documents(doc! {}).await.unwrap(); assert_options_inherited(&client, "aggregate").await; } async fn assert_options_inherited(client: &EventClient, command_name: &str) { - let events = client.get_command_started_events(&[command_name]); + let events = client.events.get_command_started_events(&[command_name]); let event = events.iter().last().unwrap(); assert!(event.command.contains_key("readConcern")); assert_eq!( event.command.contains_key("$readPreference"), - !client.is_standalone() + !topology_is_standalone().await ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn drop_skip_serializing_none() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll: Collection = client .database(function_name!()) .collection(function_name!()); let options = DropCollectionOptions::builder().build(); - assert!(coll.drop(options).await.is_ok()); + assert!(coll.drop().with_options(options).await.is_ok()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn collection_generic_bounds() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - #[derive(Deserialize)] struct Foo; - let client = TestClient::new().await; + let client = Client::for_test().await; // ensure this code successfully compiles let coll: Collection = client .database(function_name!()) .collection(function_name!()); - let _result: Result> = coll.find_one(None, None).await; + let _result: Result> = coll.find_one(doc! {}).await; #[derive(Serialize)] struct Bar; @@ -1055,37 +962,36 @@ async fn collection_generic_bounds() { let coll: Collection = client .database(function_name!()) .collection(function_name!()); - let _result = coll.insert_one(Bar {}, None).await; + let _result = coll.insert_one(Bar {}).await; } /// Verify that a cursor with multiple batches whose last batch isn't full /// iterates without errors. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn cursor_batch_size() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll("cursor_batch_size", "cursor_batch_size") .await; let doc = Document::new(); - coll.insert_many(vec![&doc; 10], None).await.unwrap(); + coll.insert_many(vec![&doc; 10]).await.unwrap(); let opts = FindOptions::builder().batch_size(3).build(); - let cursor_no_session = coll.find(doc! {}, opts.clone()).await.unwrap(); + let cursor_no_session = coll.find(doc! {}).with_options(opts.clone()).await.unwrap(); let docs: Vec<_> = cursor_no_session.try_collect().await.unwrap(); assert_eq!(docs.len(), 10); // test session cursors - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured("skipping cursor_batch_size due to standalone topology"); return; } - let mut session = client.start_session(None).await.unwrap(); + let mut session = client.start_session().await.unwrap(); let mut cursor = coll - .find_with_session(doc! {}, opts.clone(), &mut session) + .find(doc! {}) + .with_options(opts.clone()) + .session(&mut session) .await .unwrap(); let mut docs = Vec::new(); @@ -1095,7 +1001,9 @@ async fn cursor_batch_size() { assert_eq!(docs.len(), 10); let mut cursor = coll - .find_with_session(doc! {}, opts, &mut session) + .find(doc! {}) + .with_options(opts) + .session(&mut session) .await .unwrap(); let docs: Vec<_> = cursor.stream(&mut session).try_collect().await.unwrap(); @@ -1104,12 +1012,9 @@ async fn cursor_batch_size() { /// Test that the driver gracefully handles cases where the server returns invalid UTF-8 in error /// messages. See SERVER-24007 and related tickets for details. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn invalid_utf8_response() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll("invalid_uft8_handling", "invalid_uft8_handling") .await; @@ -1118,20 +1023,20 @@ async fn invalid_utf8_response() { .keys(doc! {"name": 1}) .options(IndexOptions::builder().unique(true).build()) .build(); - coll.create_index(index_model, None) + coll.create_index(index_model) .await .expect("creating an index should succeed"); // a document containing a long string with multi-byte unicode characters. taken from a user // repro in RUBY-2560. let long_unicode_str_doc = doc! {"name": "(╯°□°)╯︵ ┻━┻(╯°□°)╯︵ ┻━┻(╯°□°)╯︵ ┻━┻(╯°□°)╯︵ ┻━┻(╯°□°)╯︵ ┻━┻(╯°□°)╯︵ ┻━┻"}; - coll.insert_one(&long_unicode_str_doc, None) + coll.insert_one(&long_unicode_str_doc) .await .expect("first insert of document should succeed"); // test triggering an invalid error message via an insert_one. let insert_err = coll - .insert_one(&long_unicode_str_doc, None) + .insert_one(&long_unicode_str_doc) .await .expect_err("second insert of document should fail") .kind; @@ -1139,19 +1044,19 @@ async fn invalid_utf8_response() { // test triggering an invalid error message via an insert_many. let insert_err = coll - .insert_many([&long_unicode_str_doc], None) + .insert_many([&long_unicode_str_doc]) .await .expect_err("second insert of document should fail") .kind; assert_duplicate_key_error_with_utf8_replacement(&insert_err); // test triggering an invalid error message via an update_one. - coll.insert_one(doc! {"x": 1}, None) + coll.insert_one(doc! {"x": 1}) .await .expect("inserting new document should succeed"); let update_err = coll - .update_one(doc! {"x": 1}, doc! {"$set": &long_unicode_str_doc}, None) + .update_one(doc! {"x": 1}, doc! {"$set": &long_unicode_str_doc}) .await .expect_err("update setting duplicate key should fail") .kind; @@ -1159,7 +1064,7 @@ async fn invalid_utf8_response() { // test triggering an invalid error message via an update_many. let update_err = coll - .update_many(doc! {"x": 1}, doc! {"$set": &long_unicode_str_doc}, None) + .update_many(doc! {"x": 1}, doc! {"$set": &long_unicode_str_doc}) .await .expect_err("update setting duplicate key should fail") .kind; @@ -1167,7 +1072,7 @@ async fn invalid_utf8_response() { // test triggering an invalid error message via a replace_one. let replace_err = coll - .replace_one(doc! {"x": 1}, &long_unicode_str_doc, None) + .replace_one(doc! {"x": 1}, &long_unicode_str_doc) .await .expect_err("replacement with duplicate key should fail") .kind; @@ -1185,7 +1090,7 @@ fn assert_duplicate_key_error_with_utf8_replacement(error: &ErrorKind) { } e => panic!("expected WriteFailure::WriteError, got {:?} instead", e), }, - ErrorKind::BulkWrite(ref failure) => match &failure.write_errors { + ErrorKind::InsertMany(ref failure) => match &failure.write_errors { Some(write_errors) => { assert_eq!(write_errors.len(), 1); assert_eq!(write_errors[0].code, 11000); @@ -1214,3 +1119,212 @@ fn test_namespace_fromstr() { assert_eq!(t.db, "something"); assert_eq!(t.coll, "something.else"); } + +#[tokio::test] +async fn configure_human_readable_serialization() { + #[derive(Deserialize)] + struct StringOrBytes(String); + + impl Serialize for StringOrBytes { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&self.0) + } else { + serializer.serialize_bytes(self.0.as_bytes()) + } + } + } + + #[derive(Deserialize, Serialize)] + struct Data { + id: u32, + s: StringOrBytes, + } + + let client = Client::for_test().await; + + let non_human_readable_collection: Collection = + client.database("db").collection("nonhumanreadable"); + non_human_readable_collection.drop().await.unwrap(); + + non_human_readable_collection + .insert_one(Data { + id: 0, + s: StringOrBytes("non human readable!".into()), + }) + .await + .unwrap(); + + // The inserted bytes will not deserialize to StringOrBytes properly, so find as a document + // instead. + let document_collection = non_human_readable_collection.clone_with_type::(); + let doc = document_collection + .find_one(doc! { "id": 0 }) + .await + .unwrap() + .unwrap(); + assert!(doc.get_binary_generic("s").is_ok()); + + non_human_readable_collection + .replace_one( + doc! { "id": 0 }, + Data { + id: 1, + s: StringOrBytes("non human readable!".into()), + }, + ) + .await + .unwrap(); + + let doc = document_collection + .find_one(doc! { "id": 1 }) + .await + .unwrap() + .unwrap(); + assert!(doc.get_binary_generic("s").is_ok()); + + let human_readable_collection: Collection> = + client.database("db").collection("humanreadable"); + human_readable_collection.drop().await.unwrap(); + + human_readable_collection + .insert_one(HumanReadable(Data { + id: 0, + s: StringOrBytes("human readable!".into()), + })) + .await + .unwrap(); + + // Proper deserialization to a string demonstrates that the data was correctly serialized as a + // string. + human_readable_collection + .find_one(doc! { "id": 0 }) + .await + .unwrap(); + + human_readable_collection + .replace_one( + doc! { "id": 0 }, + HumanReadable(Data { + id: 1, + s: StringOrBytes("human readable!".into()), + }), + ) + .await + .unwrap(); + + human_readable_collection + .find_one(doc! { "id": 1 }) + .await + .unwrap(); +} + +#[tokio::test] +async fn insert_many_document_sequences() { + if cfg!(feature = "in-use-encryption") { + log_uncaptured( + "skipping insert_many_document_sequences: auto-encryption does not support document \ + sequences", + ); + return; + } + + let client = Client::for_test().monitor_events().await; + + let mut event_stream = client.events.stream(); + + let max_object_size = get_max_bson_object_size().await; + let max_message_size = get_max_message_size_bytes().await; + + let collection = client + .database("insert_many_document_sequences") + .collection::("insert_many_document_sequences"); + collection.drop().await.unwrap(); + + // A payload with > max_bson_object_size bytes but < max_message_size bytes should require only + // one round trip + let docs = vec![ + rawdoc! { "s": "a".repeat(max_object_size / 2) }, + rawdoc! { "s": "b".repeat(max_object_size / 2) }, + ]; + collection.insert_many(docs).await.unwrap(); + + let (started, _) = event_stream + .next_successful_command_execution(Duration::from_millis(500), "insert") + .await + .expect("did not observe successful command events for insert"); + let insert_documents = started.command.get_array("documents").unwrap(); + assert_eq!(insert_documents.len(), 2); + + // Build up a list of documents that exceeds max_message_size + let mut docs = Vec::new(); + let mut size = 0; + while size <= max_message_size { + // Leave some room for key/metadata bytes in document + let string_length = max_object_size - 500; + let doc = rawdoc! { "s": "a".repeat(string_length) }; + size += doc.as_bytes().len(); + docs.push(doc); + } + let total_docs = docs.len(); + collection.insert_many(docs).await.unwrap(); + + let (first_started, _) = event_stream + .next_successful_command_execution(Duration::from_millis(500), "insert") + .await + .expect("did not observe successful command events for insert"); + let first_batch_len = first_started.command.get_array("documents").unwrap().len(); + assert!(first_batch_len < total_docs); + + let (second_started, _) = event_stream + .next_successful_command_execution(Duration::from_millis(500), "insert") + .await + .expect("did not observe successful command events for insert"); + let second_batch_len = second_started.command.get_array("documents").unwrap().len(); + assert_eq!(first_batch_len + second_batch_len, total_docs); +} + +#[tokio::test] +async fn aggregate_with_generics() { + #[derive(Serialize)] + struct A { + str: String, + } + + #[derive(Deserialize)] + struct B { + len: i32, + } + + let client = Client::for_test().await; + let collection = client + .database("aggregate_with_generics") + .collection::("aggregate_with_generics"); + + let a = A { + str: "hi".to_string(), + }; + let len = a.str.len(); + collection.insert_one(&a).await.unwrap(); + + // Assert at compile-time that the default cursor returned is a Cursor + let basic_pipeline = vec![doc! { "$match": { "a": 1 } }]; + let _: Cursor = collection.aggregate(basic_pipeline).await.unwrap(); + + // Assert that data is properly deserialized when using with_type + let project_pipeline = vec![doc! { "$project": { + "str": 1, + "len": { "$strLenBytes": "$str" } + } + }]; + let cursor = collection + .aggregate(project_pipeline) + .with_type::() + .await + .unwrap(); + let lens: Vec = cursor.try_collect().await.unwrap(); + assert_eq!(lens[0].len as usize, len); +} diff --git a/src/test/compression.rs b/src/test/compression.rs new file mode 100644 index 000000000..c81300abe --- /dev/null +++ b/src/test/compression.rs @@ -0,0 +1,28 @@ +use crate::{options::Compressor, test::get_client_options}; + +// Verifies that a compressor is properly set when a compression feature flag is enabled. Actual +// compression behavior is tested by running the driver test suite with a compressor configured; +// this test just makes sure our setup is correct. +#[tokio::test] +async fn test_compression_enabled() { + let options = get_client_options().await; + let compressors = options + .compressors + .as_ref() + .expect("compressors client option should be set when compression is enabled"); + + #[cfg(feature = "zstd-compression")] + assert!(compressors + .iter() + .any(|compressor| matches!(compressor, Compressor::Zstd { .. }))); + + #[cfg(feature = "zlib-compression")] + assert!(compressors + .iter() + .any(|compressor| matches!(compressor, Compressor::Zlib { .. }))); + + #[cfg(feature = "snappy-compression")] + assert!(compressors + .iter() + .any(|compressor| matches!(compressor, Compressor::Snappy))); +} diff --git a/src/test/csfle.rs b/src/test/csfle.rs index 3e772bcdb..a3510728c 100644 --- a/src/test/csfle.rs +++ b/src/test/csfle.rs @@ -1,970 +1,239 @@ -use std::{ - collections::{BTreeMap, HashMap}, - path::PathBuf, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - Mutex, - }, - time::Duration, -}; +#[cfg(feature = "azure-kms")] +#[path = "csfle/azure_imds.rs"] +mod azure_imds; // requires mock IMDS server +#[cfg(feature = "openssl-tls")] +#[path = "csfle/kmip.rs"] +mod kmip; // requires KMIP server +#[cfg(not(feature = "openssl-tls"))] +#[path = "csfle/kms_retry.rs"] +mod kms_retry; // requires mock HTTP server +#[cfg(feature = "aws-auth")] +#[path = "csfle/on_demand_aws.rs"] +mod on_demand_aws; // requires AWS credentials to be set or unset +#[cfg(feature = "gcp-kms")] +#[path = "csfle/on_demand_gcp.rs"] +mod on_demand_gcp; // requires GCP +#[path = "csfle/prose.rs"] +mod prose; // requires environment variables listed below +#[path = "csfle/spec.rs"] +mod spec; // requires environment variables listed below +use std::{env, path::PathBuf}; + +use crate::bson::{doc, Document, RawBson}; use anyhow::Context; -#[cfg(not(feature = "tokio-runtime"))] -use async_std::net::TcpListener; -use bson::{ - doc, - rawdoc, - spec::{BinarySubtype, ElementType}, - Binary, - Bson, - DateTime, - Document, - RawBson, - RawDocumentBuf, -}; -use futures_util::TryStreamExt; -use lazy_static::lazy_static; -use mongocrypt::ctx::{Algorithm, KmsProvider}; -#[cfg(feature = "tokio-runtime")] -use tokio::net::TcpListener; +use mongocrypt::ctx::{Algorithm, KmsProvider, KmsProviderType}; +use once_cell::sync::Lazy; use crate::{ - client_encryption::{ClientEncryption, EncryptKey, MasterKey, RangeOptions}, - error::{ErrorKind, WriteError, WriteFailure}, - event::command::{ - CommandEventHandler, - CommandFailedEvent, - CommandStartedEvent, - CommandSucceededEvent, - }, - options::{ - CollectionOptions, - CreateCollectionOptions, - CreateIndexOptions, - Credential, - DropCollectionOptions, - FindOptions, - IndexOptions, - InsertOneOptions, - ReadConcern, - TlsOptions, - WriteConcern, - }, - runtime, - test::{Event, EventHandler, SdamEvent}, + client_encryption::{ClientEncryption, EncryptKey}, + options::{CollectionOptions, ReadConcern, TlsOptions, WriteConcern}, Client, Collection, - IndexModel, Namespace, }; -use super::{ - log_uncaptured, - EventClient, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, - SERVERLESS, -}; +use super::{log_uncaptured, server_version_lt, topology_is_standalone, EventClient}; type Result = anyhow::Result; - -async fn init_client() -> Result<(EventClient, Collection)> { - let client = EventClient::new().await; - let datakeys = client - .database("keyvault") - .collection_with_options::( - "datakeys", - CollectionOptions::builder() - .read_concern(ReadConcern::MAJORITY) - .write_concern(WriteConcern::MAJORITY) - .build(), - ); - datakeys.drop(None).await?; - client - .database("db") - .collection::("coll") - .drop(None) - .await?; - Ok((client, datakeys)) -} - -pub(crate) type KmsProviderList = Vec<(KmsProvider, bson::Document, Option)>; - -lazy_static! { - static ref KMS_PROVIDERS: KmsProviderList = { - fn env(name: &str) -> String { - std::env::var(name).unwrap() - } - vec![ - ( - KmsProvider::Aws, - doc! { - "accessKeyId": env("AWS_ACCESS_KEY_ID"), - "secretAccessKey": env("AWS_SECRET_ACCESS_KEY"), - }, - None, - ), - ( - KmsProvider::Azure, - doc! { - "tenantId": env("AZURE_TENANT_ID"), - "clientId": env("AZURE_CLIENT_ID"), - "clientSecret": env("AZURE_CLIENT_SECRET"), - }, - None, - ), - ( - KmsProvider::Gcp, - doc! { - "email": env("GCP_EMAIL"), - "privateKey": env("GCP_PRIVATE_KEY"), - }, - None, - ), - ( - KmsProvider::Local, - doc! { - "key": bson::Binary { - subtype: bson::spec::BinarySubtype::Generic, - bytes: base64::decode(env("CSFLE_LOCAL_KEY")).unwrap(), - }, - }, - None, - ), - ( - KmsProvider::Kmip, - doc! { - "endpoint": "localhost:5698", - }, - { - let cert_dir = PathBuf::from(env("CSFLE_TLS_CERT_DIR")); - Some( - TlsOptions::builder() - .ca_file_path(cert_dir.join("ca.pem")) - .cert_key_file_path(cert_dir.join("client.pem")) - .build(), - ) - }, - ), - ] - }; - static ref LOCAL_KMS: KmsProviderList = KMS_PROVIDERS - .iter() - .filter(|(p, ..)| p == &KmsProvider::Local) - .cloned() - .collect(); - pub(crate) static ref KMS_PROVIDERS_MAP: HashMap< - mongocrypt::ctx::KmsProvider, - (bson::Document, Option), - > = { - let mut map = HashMap::new(); - for (prov, conf, tls) in KMS_PROVIDERS.clone() { - map.insert(prov, (conf, tls)); - } - map - }; - static ref EXTRA_OPTIONS: Document = - doc! { "cryptSharedLibPath": std::env::var("CRYPT_SHARED_LIB_PATH").unwrap() }; - static ref KV_NAMESPACE: Namespace = Namespace::from_str("keyvault.datakeys").unwrap(); - static ref DISABLE_CRYPT_SHARED: bool = - std::env::var("DISABLE_CRYPT_SHARED").map_or(false, |s| s == "true"); -} - -fn check_env(name: &str, kmip: bool) -> bool { - if std::env::var("CSFLE_LOCAL_KEY").is_err() { - log_uncaptured(format!( - "skipping csfle test {}: no kms providers configured", - name - )); - return false; - } - if kmip { - #[cfg(not(feature = "openssl-tls"))] - { - // rustls is incompatible with the driver-tools kmip server. - log_uncaptured(format!("skipping {}: KMIP requires openssl", name)); - return false; +pub(crate) type KmsInfo = (KmsProvider, Document, Option); +pub(crate) type KmsProviderList = Vec; + +// The environment variables needed to run the CSFLE tests. These values can be retrieved from the +// AWS secrets manager by running the setup-secrets.sh script in drivers-evergreen-tools. +static CSFLE_LOCAL_KEY: Lazy = Lazy::new(|| get_env_var("CSFLE_LOCAL_KEY")); +static FLE_AWS_KEY: Lazy = Lazy::new(|| get_env_var("FLE_AWS_KEY")); +static FLE_AWS_SECRET: Lazy = Lazy::new(|| get_env_var("FLE_AWS_SECRET")); +static FLE_AWS_TEMP_KEY: Lazy = Lazy::new(|| get_env_var("CSFLE_AWS_TEMP_ACCESS_KEY_ID")); +static FLE_AWS_TEMP_SECRET: Lazy = + Lazy::new(|| get_env_var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY")); +static FLE_AWS_TEMP_SESSION_TOKEN: Lazy = + Lazy::new(|| get_env_var("CSFLE_AWS_TEMP_SESSION_TOKEN")); +static FLE_AZURE_TENANTID: Lazy = Lazy::new(|| get_env_var("FLE_AZURE_TENANTID")); +static FLE_AZURE_CLIENTID: Lazy = Lazy::new(|| get_env_var("FLE_AZURE_CLIENTID")); +static FLE_AZURE_CLIENTSECRET: Lazy = Lazy::new(|| get_env_var("FLE_AZURE_CLIENTSECRET")); +static FLE_GCP_EMAIL: Lazy = Lazy::new(|| get_env_var("FLE_GCP_EMAIL")); +static FLE_GCP_PRIVATEKEY: Lazy = Lazy::new(|| get_env_var("FLE_GCP_PRIVATEKEY")); + +// Additional environment variables. These values should be set to the relevant local paths/ports. +#[cfg(feature = "azure-kms")] +static AZURE_IMDS_MOCK_PORT: Lazy = Lazy::new(|| { + get_env_var("AZURE_IMDS_MOCK_PORT") + .parse() + .expect("AZURE_IMDS_MOCK_PORT") +}); +static CSFLE_TLS_CERT_DIR: Lazy = Lazy::new(|| get_env_var("CSFLE_TLS_CERT_DIR")); +static CRYPT_SHARED_LIB_PATH: Lazy = Lazy::new(|| get_env_var("CRYPT_SHARED_LIB_PATH")); + +fn get_env_var(name: &str) -> String { + match std::env::var(name) { + Ok(v) if !v.is_empty() => v, + _ => { + panic!( + "Missing environment variable for {}. See src/test/csfle.rs for the list of \ + required variables and instructions for retrieving them.", + name + ) } } - true } -// Prose test 1. Custom Key Material Test -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_key_material() -> Result<()> { - if !check_env("custom_key_material", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let (client, datakeys) = init_client().await?; - let enc = ClientEncryption::new( - client.into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - - let key = base64::decode( - "xPTAjBRG5JiPm+d3fj6XLi2q5DMXUS/f1f+SMAlhhwkhDRL0kr8r9GDLIGTAGlvC+HVjSIgdL+RKw\ - ZCvpXSyxTICWSXTUYsWYPyu3IoHbuBZdmw2faM3WhcRIgbMReU5", +pub(crate) static AWS_KMS: Lazy = Lazy::new(|| { + ( + KmsProvider::aws(), + doc! { + "accessKeyId": &*FLE_AWS_KEY, + "secretAccessKey": &*FLE_AWS_SECRET + }, + None, ) - .unwrap(); - let id = enc - .create_data_key(MasterKey::Local) - .key_material(key) - .run() - .await?; - let mut key_doc = datakeys - .find_one(doc! { "_id": id.clone() }, None) - .await? - .unwrap(); - datakeys.delete_one(doc! { "_id": id}, None).await?; - let new_key_id = bson::Binary::from_uuid(bson::Uuid::from_bytes([0; 16])); - key_doc.insert("_id", new_key_id.clone()); - datakeys.insert_one(key_doc, None).await?; - - let encrypted = enc - .encrypt( - "test", - EncryptKey::Id(new_key_id), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - let expected = base64::decode( - "AQAAAAAAAAAAAAAAAAAAAAACz0ZOLuuhEYi807ZXTdhbqhLaS2/t9wLifJnnNYwiw79d75QYIZ6M/\ - aYC1h9nCzCjZ7pGUpAuNnkUhnIXM3PjrA==", +}); +static AWS_TEMP_KMS: Lazy = Lazy::new(|| { + ( + KmsProvider::aws(), + doc! { + "accessKeyId": &*FLE_AWS_TEMP_KEY, + "secretAccessKey": &*FLE_AWS_TEMP_SECRET, + "sessionToken": &*FLE_AWS_TEMP_SESSION_TOKEN, + }, + None, ) - .unwrap(); - assert_eq!(encrypted.bytes, expected); - - Ok(()) -} - -// Prose test 2. Data Key and Double Encryption -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn data_key_double_encryption() -> Result<()> { - if !check_env("data_key_double_encryption", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Setup: drop stale data. - let (client, _) = init_client().await?; - - // Setup: client with auto encryption. - let schema_map = [( - "db.coll", +}); +pub(crate) static AWS_KMS_NAME1: Lazy = Lazy::new(|| { + let aws_info = AWS_KMS.clone(); + (aws_info.0.with_name("name1"), aws_info.1, aws_info.2) +}); +pub(crate) static AWS_KMS_NAME2: Lazy = Lazy::new(|| { + ( + KmsProvider::aws().with_name("name2"), doc! { - "bsonType": "object", - "properties": { - "encrypted_placeholder": { - "encrypt": { - "keyId": "/placeholder", - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" - } - } - } + "accessKeyId": &*FLE_AWS_KEY, + "secretAccessKey": &*FLE_AWS_SECRET }, - )]; - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS.clone(), - )? - .schema_map(schema_map) - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - - // Setup: manual encryption. - let client_encryption = ClientEncryption::new( - client.clone().into_client(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS.clone(), - )?; - - // Testing each provider: - let mut events = client.subscribe_to_events(); - let provider_keys = [ - ( - KmsProvider::Aws, - MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: None, - }, - ), - ( - KmsProvider::Azure, - MasterKey::Azure { - key_vault_endpoint: "key-vault-csfle.vault.azure.net".to_string(), - key_name: "key-name-csfle".to_string(), - key_version: None, - }, - ), - ( - KmsProvider::Gcp, - MasterKey::Gcp { - project_id: "devprod-drivers".to_string(), - location: "global".to_string(), - key_ring: "key-ring-csfle".to_string(), - key_name: "key-name-csfle".to_string(), - key_version: None, - endpoint: None, - }, - ), - (KmsProvider::Local, MasterKey::Local), - ( - KmsProvider::Kmip, - MasterKey::Kmip { - key_id: None, - endpoint: None, - }, - ), - ]; - for (provider, master_key) in provider_keys { - // Create a data key - let datakey_id = client_encryption - .create_data_key(master_key) - .key_alt_names([format!("{}_altname", provider.name())]) - .run() - .await?; - assert_eq!(datakey_id.subtype, BinarySubtype::Uuid); - let docs: Vec<_> = client - .database("keyvault") - .collection::("datakeys") - .find(doc! { "_id": datakey_id.clone() }, None) - .await? - .try_collect() - .await?; - assert_eq!(docs.len(), 1); - assert_eq!( - docs[0].get_document("masterKey")?.get_str("provider")?, - provider.name() - ); - let found = events - .wait_for_event( - Duration::from_millis(500), - ok_pred(|ev| { - let ev = match ev.as_command_started_event() { - Some(e) => e, - None => return Ok(false), - }; - if ev.command_name != "insert" { - return Ok(false); - } - let cmd = &ev.command; - if cmd.get_document("writeConcern")?.get_str("w")? != "majority" { - return Ok(false); - } - Ok(cmd.get_array("documents")?.iter().any(|doc| { - matches!( - doc.as_document().and_then(|d| d.get("_id")), - Some(Bson::Binary(id)) if id == &datakey_id - ) - })) - }), - ) - .await; - assert!(found.is_some(), "no valid event found in {:?}", events); - - // Manually encrypt a value and automatically decrypt it. - let encrypted = client_encryption - .encrypt( - format!("hello {}", provider.name()), - EncryptKey::Id(datakey_id), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - assert_eq!(encrypted.subtype, BinarySubtype::Encrypted); - let coll = client_encrypted - .database("db") - .collection::("coll"); - coll.insert_one( - doc! { "_id": provider.name(), "value": encrypted.clone() }, - None, - ) - .await?; - let found = coll.find_one(doc! { "_id": provider.name() }, None).await?; - assert_eq!( - found.as_ref().and_then(|doc| doc.get("value")), - Some(&Bson::String(format!("hello {}", provider.name()))), - ); - - // Manually encrypt a value via key alt name. - let other_encrypted = client_encryption - .encrypt( - format!("hello {}", provider.name()), - EncryptKey::AltName(format!("{}_altname", provider.name())), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - assert_eq!(other_encrypted.subtype, BinarySubtype::Encrypted); - assert_eq!(other_encrypted.bytes, encrypted.bytes); - - // Attempt to auto-encrypt an already encrypted field. - let result = coll - .insert_one(doc! { "encrypted_placeholder": encrypted }, None) - .await; - let err = result.unwrap_err(); - assert!( - matches!(*err.kind, ErrorKind::Encryption(..)) || err.is_command_error(), - "unexpected error: {}", - err - ); - } - - Ok(()) -} - -fn ok_pred(mut f: impl FnMut(&Event) -> Result) -> impl FnMut(&Event) -> bool { - move |ev| f(ev).unwrap_or(false) -} - -// TODO RUST-1225: replace this with built-in BSON support. -fn base64_uuid(bytes: impl AsRef) -> Result { - Ok(bson::Binary { - subtype: BinarySubtype::Uuid, - bytes: base64::decode(bytes.as_ref())?, - }) -} - -// Prose test 3. External Key Vault Test -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn external_key_vault() -> Result<()> { - if !check_env("external_key_vault", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - for with_external_key_vault in [false, true] { - // Setup: initialize db. - let (client, datakeys) = init_client().await?; - datakeys - .insert_one(load_testdata("external/external-key.json")?, None) - .await?; - - // Setup: test options. - let kv_client = if with_external_key_vault { - let mut opts = CLIENT_OPTIONS.get().await.clone(); - opts.credential = Some( - Credential::builder() - .username("fake-user".to_string()) - .password("fake-pwd".to_string()) - .build(), - ); - Some(Client::with_options(opts)?) - } else { - None - }; - - // Setup: encrypted client. - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .key_vault_client(kv_client.clone()) - .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - // Setup: manual encryption. - let client_encryption = ClientEncryption::new( - kv_client.unwrap_or_else(|| client.into_client()), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - - // Test: encrypted client. - let result = client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "encrypted": "test" }, None) - .await; - if with_external_key_vault { - let err = result.unwrap_err(); - assert!(err.is_auth_error(), "unexpected error: {}", err); - } else { - assert!( - result.is_ok(), - "unexpected error: {}", - result.err().unwrap() - ); - } - // Test: manual encryption. - let result = client_encryption - .encrypt( - "test", - EncryptKey::Id(base64_uuid("LOCALAAAAAAAAAAAAAAAAA==")?), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await; - if with_external_key_vault { - let err = result.unwrap_err(); - assert!(err.is_auth_error(), "unexpected error: {}", err); - } else { - assert!( - result.is_ok(), - "unexpected error: {}", - result.err().unwrap() - ); - } - } - - Ok(()) -} - -fn load_testdata_raw(name: &str) -> Result { - let path: PathBuf = [ - env!("CARGO_MANIFEST_DIR"), - "src/test/spec/json/testdata/client-side-encryption", - name, - ] - .iter() - .collect(); - std::fs::read_to_string(path.clone()).context(path.to_string_lossy().into_owned()) -} - -fn load_testdata(name: &str) -> Result { - Ok(serde_json::from_str(&load_testdata_raw(name)?)?) -} - -// Prose test 4. BSON Size Limits and Batch Splitting -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bson_size_limits() -> Result<()> { - if !check_env("bson_size_limits", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Setup: db initialization. - let (client, datakeys) = init_client().await?; - client - .database("db") - .create_collection( - "coll", - CreateCollectionOptions::builder() - .validator(doc! { "$jsonSchema": load_testdata("limits/limits-schema.json")? }) - .build(), - ) - .await?; - datakeys - .insert_one(load_testdata("limits/limits-key.json")?, None) - .await?; - - // Setup: encrypted client. - let mut opts = CLIENT_OPTIONS.get().await.clone(); - let handler = Arc::new(EventHandler::new()); - let mut events = handler.subscribe(); - opts.command_event_handler = Some(handler.clone()); - let client_encrypted = - Client::encrypted_builder(opts, KV_NAMESPACE.clone(), LOCAL_KMS.clone())? - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - let coll = client_encrypted - .database("db") - .collection::("coll"); - - // Tests - // Test operation 1 - coll.insert_one( + None, + ) +}); +pub(crate) static AZURE_KMS: Lazy = Lazy::new(|| { + ( + KmsProvider::azure(), doc! { - "_id": "over_2mib_under_16mib", - "unencrypted": "a".repeat(2097152), + "tenantId": &*FLE_AZURE_TENANTID, + "clientId": &*FLE_AZURE_CLIENTID, + "clientSecret": &*FLE_AZURE_CLIENTSECRET, }, None, ) - .await?; - - // Test operation 2 - let mut doc: Document = load_testdata("limits/limits-doc.json")?; - doc.insert("_id", "encryption_exceeds_2mib"); - doc.insert("unencrypted", "a".repeat(2_097_152 - 2_000)); - coll.insert_one(doc, None).await?; - - // Test operation 3 - let value = "a".repeat(2_097_152); - events.clear_events(Duration::from_millis(500)).await; - coll.insert_many( - vec![ - doc! { - "_id": "over_2mib_1", - "unencrypted": value.clone(), - }, - doc! { - "_id": "over_2mib_2", - "unencrypted": value, +}); +pub(crate) static AZURE_KMS_NAME1: Lazy = Lazy::new(|| { + let azure_info = AZURE_KMS.clone(); + (azure_info.0.with_name("name1"), azure_info.1, azure_info.2) +}); +pub(crate) static GCP_KMS: Lazy = Lazy::new(|| { + ( + KmsProvider::gcp(), + doc! { + "email": &*FLE_GCP_EMAIL, + "privateKey": &*FLE_GCP_PRIVATEKEY, + }, + None, + ) +}); +pub(crate) static GCP_KMS_NAME1: Lazy = Lazy::new(|| { + let gcp_info = GCP_KMS.clone(); + (gcp_info.0.with_name("name1"), gcp_info.1, gcp_info.2) +}); +pub(crate) static LOCAL_KMS: Lazy = Lazy::new(|| { + ( + KmsProvider::local(), + doc! { + "key": crate::bson::Binary { + subtype: crate::bson::spec::BinarySubtype::Generic, + bytes: base64::decode(&*CSFLE_LOCAL_KEY).unwrap(), }, - ], + }, None, ) - .await?; - let inserts = events - .collect_events(Duration::from_millis(500), |ev| { - let ev = match ev.as_command_started_event() { - Some(e) => e, - None => return false, - }; - ev.command_name == "insert" - }) - .await; - assert_eq!(2, inserts.len()); - - // Test operation 4 - let mut doc = load_testdata("limits/limits-doc.json")?; - doc.insert("_id", "encryption_exceeds_2mib_1"); - doc.insert("unencrypted", "a".repeat(2_097_152 - 2_000)); - let mut doc2 = doc.clone(); - doc2.insert("_id", "encryption_exceeds_2mib_2"); - events.clear_events(Duration::from_millis(500)).await; - coll.insert_many(vec![doc, doc2], None).await?; - let inserts = events - .collect_events(Duration::from_millis(500), |ev| { - let ev = match ev.as_command_started_event() { - Some(e) => e, - None => return false, - }; - ev.command_name == "insert" - }) - .await; - assert_eq!(2, inserts.len()); - - // Test operation 5 - let doc = doc! { - "_id": "under_16mib", - "unencrypted": "a".repeat(16_777_216 - 2_000), - }; - coll.insert_one(doc, None).await?; - - // Test operation 6 - let mut doc: Document = load_testdata("limits/limits-doc.json")?; - doc.insert("_id", "encryption_exceeds_16mib"); - doc.insert("unencrypted", "a".repeat(16_777_216 - 2_000)); - let result = coll.insert_one(doc, None).await; - let err = result.unwrap_err(); - assert!( - matches!(*err.kind, ErrorKind::Write(_)), - "unexpected error: {}", - err - ); - - Ok(()) -} - -// Prose test 5. Views Are Prohibited -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn views_prohibited() -> Result<()> { - if !check_env("views_prohibited", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Setup: db initialization. - let (client, _) = init_client().await?; - client - .database("db") - .collection::("view") - .drop(None) - .await?; - client - .database("db") - .create_collection( - "view", - CreateCollectionOptions::builder() - .view_on("coll".to_string()) - .build(), - ) - .await?; - - // Setup: encrypted client. - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), +}); +pub(crate) static LOCAL_KMS_NAME1: Lazy = Lazy::new(|| { + let local_info = LOCAL_KMS.clone(); + (local_info.0.with_name("name1"), local_info.1, local_info.2) +}); +pub(crate) static KMIP_KMS: Lazy = Lazy::new(|| { + let cert_dir = PathBuf::from(&*CSFLE_TLS_CERT_DIR); + let tls_options = TlsOptions::builder() + .ca_file_path(cert_dir.join("ca.pem")) + .cert_key_file_path(cert_dir.join("client.pem")) + .build(); + ( + KmsProvider::kmip(), + doc! { + "endpoint": "localhost:5698", + }, + Some(tls_options), + ) +}); +pub(crate) static KMIP_KMS_NAME1: Lazy = Lazy::new(|| { + let kmip_info = KMIP_KMS.clone(); + (kmip_info.0.with_name("name1"), kmip_info.1, kmip_info.2) +}); + +pub(crate) static UNNAMED_KMS_PROVIDERS: Lazy = Lazy::new(|| { + vec![ + AWS_KMS.clone(), + AZURE_KMS.clone(), + GCP_KMS.clone(), LOCAL_KMS.clone(), - )? - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - - // Test: auto encryption fails on a view - let result = client_encrypted - .database("db") - .collection::("view") - .insert_one(doc! {}, None) - .await; - let err = result.unwrap_err(); - assert!( - err.to_string().contains("cannot auto encrypt a view"), - "unexpected error: {}", - err - ); - - Ok(()) -} - -macro_rules! failure { - ($($arg:tt)*) => {{ - crate::error::Error::internal(format!($($arg)*)).into() - }} -} - -// TODO RUST-36: use the full corpus with decimal128. -fn load_corpus_nodecimal128(name: &str) -> Result { - let json: serde_json::Value = serde_json::from_str(&load_testdata_raw(name)?)?; - let mut new_obj = serde_json::Map::new(); - let decimal = serde_json::Value::String("decimal".to_string()); - for (name, value) in json.as_object().expect("expected object") { - if value["type"] == decimal { - continue; - } - new_obj.insert(name.clone(), value.clone()); - } - let bson: bson::Bson = serde_json::Value::Object(new_obj).try_into()?; - match bson { - bson::Bson::Document(d) => Ok(d), - _ => Err(failure!("expected document, got {:?}", bson)), - } -} - -// Prose test 6. Corpus Test (collection schema) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn corpus_coll_schema() -> Result<()> { - if !check_env("corpus_coll_schema", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - run_corpus_test(false).await?; - Ok(()) -} - -// Prose test 6. Corpus Test (local schema) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn corpus_local_schema() -> Result<()> { - if !check_env("corpus_local_schema", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - run_corpus_test(true).await?; - Ok(()) -} + KMIP_KMS.clone(), + ] +}); +pub(crate) static NAME1_KMS_PROVIDERS: Lazy = Lazy::new(|| { + vec![ + AWS_KMS_NAME1.clone(), + AZURE_KMS_NAME1.clone(), + GCP_KMS_NAME1.clone(), + LOCAL_KMS_NAME1.clone(), + KMIP_KMS_NAME1.clone(), + ] +}); +pub(crate) static ALL_KMS_PROVIDERS: Lazy = Lazy::new(|| { + let mut providers = UNNAMED_KMS_PROVIDERS.clone(); + providers.extend(NAME1_KMS_PROVIDERS.clone()); + providers.push(AWS_KMS_NAME2.clone()); + providers +}); + +static EXTRA_OPTIONS: Lazy = + Lazy::new(|| doc! { "cryptSharedLibPath": &*CRYPT_SHARED_LIB_PATH }); +static KV_NAMESPACE: Lazy = + Lazy::new(|| Namespace::from_str("keyvault.datakeys").unwrap()); +static DISABLE_CRYPT_SHARED: Lazy = + Lazy::new(|| env::var("DISABLE_CRYPT_SHARED").is_ok_and(|s| s == "true")); -async fn run_corpus_test(local_schema: bool) -> Result<()> { - // Setup: db initialization. - let (client, datakeys) = init_client().await?; - let schema = load_testdata("corpus/corpus-schema.json")?; - let coll_opts = if local_schema { - None - } else { - Some( - CreateCollectionOptions::builder() - .validator(doc! { "$jsonSchema": schema.clone() }) +async fn init_client() -> Result<(EventClient, Collection)> { + let client = Client::for_test().monitor_events().await; + let datakeys = client + .database("keyvault") + .collection_with_options::( + "datakeys", + CollectionOptions::builder() + .read_concern(ReadConcern::majority()) + .write_concern(WriteConcern::majority()) .build(), - ) - }; + ); + datakeys.drop().await?; client - .database("db") - .create_collection("coll", coll_opts) - .await?; - for f in [ - "corpus/corpus-key-local.json", - "corpus/corpus-key-aws.json", - "corpus/corpus-key-azure.json", - "corpus/corpus-key-gcp.json", - "corpus/corpus-key-kmip.json", - ] { - datakeys.insert_one(load_testdata(f)?, None).await?; - } - - // Setup: encrypted client and manual encryption. - let client_encrypted = { - let mut enc_builder = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS.clone(), - )? - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED); - if local_schema { - enc_builder = enc_builder.schema_map([("db.coll", schema)]); - } - enc_builder.build().await? - }; - let client_encryption = ClientEncryption::new( - client.clone().into_client(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS.clone(), - )?; - - // Test: build corpus. - let corpus = load_corpus_nodecimal128("corpus/corpus.json")?; - let mut corpus_copied = doc! {}; - for (name, field) in &corpus { - // Copy simple fields - if [ - "_id", - "altname_aws", - "altname_local", - "altname_azure", - "altname_gcp", - "altname_kmip", - ] - .contains(&name.as_str()) - { - corpus_copied.insert(name, field); - continue; - } - // Encrypt `value` field in subdocuments. - let subdoc = match field.as_document() { - Some(d) => d, - None => { - return Err(failure!( - "unexpected field type for {:?}: {:?}", - name, - field.element_type() - )) - } - }; - let method = subdoc.get_str("method")?; - if method == "auto" { - corpus_copied.insert(name, subdoc); - continue; - } - if method != "explicit" { - return Err(failure!("Invalid method {:?}", method)); - } - let algo = match subdoc.get_str("algo")? { - "rand" => Algorithm::AeadAes256CbcHmacSha512Random, - "det" => Algorithm::AeadAes256CbcHmacSha512Deterministic, - s => return Err(failure!("Invalid algorithm {:?}", s)), - }; - let kms = KmsProvider::from_name(subdoc.get_str("kms")?); - let key = match subdoc.get_str("identifier")? { - "id" => EncryptKey::Id(base64_uuid(match kms { - KmsProvider::Local => "LOCALAAAAAAAAAAAAAAAAA==", - KmsProvider::Aws => "AWSAAAAAAAAAAAAAAAAAAA==", - KmsProvider::Azure => "AZUREAAAAAAAAAAAAAAAAA==", - KmsProvider::Gcp => "GCPAAAAAAAAAAAAAAAAAAA==", - KmsProvider::Kmip => "KMIPAAAAAAAAAAAAAAAAAA==", - _ => return Err(failure!("Invalid kms provider {:?}", kms)), - })?), - "altname" => EncryptKey::AltName(kms.name().to_string()), - s => return Err(failure!("Invalid identifier {:?}", s)), - }; - let value: RawBson = subdoc - .get("value") - .expect("no value to encrypt") - .clone() - .try_into()?; - let result = client_encryption.encrypt(value, key, algo).run().await; - let mut subdoc_copied = subdoc.clone(); - if subdoc.get_bool("allowed")? { - subdoc_copied.insert("value", result?); - } else { - result.expect_err("expected encryption to be disallowed"); - } - corpus_copied.insert(name, subdoc_copied); - } - - // Test: insert into and find from collection, with automatic encryption. - let coll = client_encrypted - .database("db") - .collection::("coll"); - let id = coll.insert_one(corpus_copied, None).await?.inserted_id; - let corpus_decrypted = coll - .find_one(doc! { "_id": id.clone() }, None) - .await? - .expect("document lookup failed"); - assert_eq!(corpus, corpus_decrypted); - - // Test: validate encrypted form. - let corpus_encrypted_expected = load_corpus_nodecimal128("corpus/corpus-encrypted.json")?; - let corpus_encrypted_actual = client .database("db") .collection::("coll") - .find_one(doc! { "_id": id }, None) - .await? - .expect("encrypted document lookup failed"); - for (name, field) in &corpus_encrypted_expected { - let subdoc = match field.as_document() { - Some(d) => d, - None => continue, - }; - let value = subdoc.get("value").expect("no expected value"); - let actual_value = corpus_encrypted_actual - .get_document(name)? - .get("value") - .expect("no actual value"); - let algo = subdoc.get_str("algo")?; - if algo == "det" { - assert_eq!(value, actual_value); - } - let allowed = subdoc.get_bool("allowed")?; - if algo == "rand" && allowed { - assert_ne!(value, actual_value); - } - if allowed { - let bin = match value { - bson::Bson::Binary(b) => b, - _ => { - return Err(failure!( - "expected value {:?} should be Binary, got {:?}", - name, - value - )) - } - }; - let actual_bin = match actual_value { - bson::Bson::Binary(b) => b, - _ => { - return Err(failure!( - "actual value {:?} should be Binary, got {:?}", - name, - actual_value - )) - } - }; - let dec = client_encryption.decrypt(bin.as_raw_binary()).await?; - let actual_dec = client_encryption - .decrypt(actual_bin.as_raw_binary()) - .await?; - assert_eq!(dec, actual_dec); - } else { - assert_eq!(Some(value), corpus.get_document(name)?.get("value")); - } - } - - Ok(()) + .drop() + .await?; + Ok((client, datakeys)) } async fn custom_endpoint_setup(valid: bool) -> Result { let update_provider = |(provider, mut conf, tls): (KmsProvider, Document, Option)| { - match provider { - KmsProvider::Azure => { + match provider.provider_type() { + KmsProviderType::Azure => { conf.insert( "identityPlatformEndpoint", if valid { @@ -974,7 +243,7 @@ async fn custom_endpoint_setup(valid: bool) -> Result { }, ); } - KmsProvider::Gcp => { + KmsProviderType::Gcp => { conf.insert( "endpoint", if valid { @@ -984,13 +253,13 @@ async fn custom_endpoint_setup(valid: bool) -> Result { }, ); } - KmsProvider::Kmip => { + KmsProviderType::Kmip => { conf.insert( "endpoint", if valid { "localhost:5698" } else { - "doesnotexist.local:5698" + "doesnotexist.invalid:5698" }, ); } @@ -998,13 +267,13 @@ async fn custom_endpoint_setup(valid: bool) -> Result { } (provider, conf, tls) }; - let kms_providers: KmsProviderList = KMS_PROVIDERS + let kms_providers: KmsProviderList = UNNAMED_KMS_PROVIDERS .clone() .into_iter() .map(update_provider) .collect(); Ok(ClientEncryption::new( - TestClient::new().await.into_client(), + Client::for_test().await.into_client(), KV_NAMESPACE.clone(), kms_providers, )?) @@ -1012,2687 +281,87 @@ async fn custom_endpoint_setup(valid: bool) -> Result { async fn validate_roundtrip( client_encryption: &ClientEncryption, - key_id: bson::Binary, + key_id: crate::bson::Binary, ) -> Result<()> { let value = RawBson::String("test".to_string()); let encrypted = client_encryption .encrypt( value.clone(), EncryptKey::Id(key_id), - Algorithm::AeadAes256CbcHmacSha512Deterministic, + Algorithm::Deterministic, ) - .run() .await?; let decrypted = client_encryption.decrypt(encrypted.as_raw_binary()).await?; assert_eq!(value, decrypted); Ok(()) } -async fn custom_endpoint_aws_ok(endpoint: Option) -> Result<()> { - let client_encryption = custom_endpoint_setup(true).await?; - - let key_id = client_encryption - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint, - }) - .run() - .await?; - validate_roundtrip(&client_encryption, key_id).await?; - - Ok(()) +fn load_testdata_raw(name: &str) -> Result { + let path: PathBuf = [ + env!("CARGO_MANIFEST_DIR"), + "src/test/spec/json/testdata/client-side-encryption", + name, + ] + .iter() + .collect(); + std::fs::read_to_string(path.clone()).context(path.to_string_lossy().into_owned()) } -// Prose test 7. Custom Endpoint Test (case 1. aws, no endpoint) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_no_endpoint() -> Result<()> { - if !check_env("custom_endpoint_aws_no_endpoint", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - custom_endpoint_aws_ok(None).await +fn load_testdata(name: &str) -> Result { + Ok(serde_json::from_str(&load_testdata_raw(name)?)?) } -// Prose test 7. Custom Endpoint Test (case 2. aws, endpoint without port) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_no_port() -> Result<()> { - if !check_env("custom_endpoint_aws_no_port", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - custom_endpoint_aws_ok(Some("kms.us-east-1.amazonaws.com".to_string())).await -} - -// Prose test 7. Custom Endpoint Test (case 3. aws, endpoint with port) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_with_port() -> Result<()> { - if !check_env("custom_endpoint_aws_with_port", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - custom_endpoint_aws_ok(Some("kms.us-east-1.amazonaws.com:443".to_string())).await -} - -// Prose test 7. Custom Endpoint Test (case 4. aws, endpoint with invalid port) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_invalid_port() -> Result<()> { - if !check_env("custom_endpoint_aws_invalid_port", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let client_encryption = custom_endpoint_setup(true).await?; - - let result = client_encryption - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: Some("kms.us-east-1.amazonaws.com:12345".to_string()), - }) - .run() - .await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 5. aws, invalid region) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_invalid_region() -> Result<()> { - if !check_env("custom_endpoint_aws_invalid_region", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let client_encryption = custom_endpoint_setup(true).await?; - - let result = client_encryption - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: Some("kms.us-east-2.amazonaws.com".to_string()), - }) - .run() - .await; - assert!(result.unwrap_err().is_csfle_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 6. aws, invalid domain) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_aws_invalid_domain() -> Result<()> { - if !check_env("custom_endpoint_aws_invalid_domain", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let client_encryption = custom_endpoint_setup(true).await?; - - let result = client_encryption - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: Some("doesnotexist.invalid".to_string()), - }) - .run() - .await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 7. azure) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_azure() -> Result<()> { - if !check_env("custom_endpoint_azure", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Azure { - key_vault_endpoint: "key-vault-csfle.vault.azure.net".to_string(), - key_name: "key-name-csfle".to_string(), - key_version: None, - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let key_id = client_encryption - .create_data_key(master_key.clone()) - .run() - .await?; - validate_roundtrip(&client_encryption, key_id).await?; - - let client_encryption_invalid = custom_endpoint_setup(false).await?; - let result = client_encryption_invalid - .create_data_key(master_key) - .run() - .await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 8. gcp) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_gcp_valid() -> Result<()> { - if !check_env("custom_endpoint_gcp_valid", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Gcp { - project_id: "devprod-drivers".to_string(), - location: "global".to_string(), - key_ring: "key-ring-csfle".to_string(), - key_name: "key-name-csfle".to_string(), - key_version: None, - endpoint: Some("cloudkms.googleapis.com:443".to_string()), - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let key_id = client_encryption - .create_data_key(master_key.clone()) - .run() - .await?; - validate_roundtrip(&client_encryption, key_id).await?; - - let client_encryption_invalid = custom_endpoint_setup(false).await?; - let result = client_encryption_invalid - .create_data_key(master_key) - .run() - .await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 9. gcp, invalid endpoint) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_gcp_invalid() -> Result<()> { - if !check_env("custom_endpoint_gcp_invalid", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Gcp { - project_id: "devprod-drivers".to_string(), - location: "global".to_string(), - key_ring: "key-ring-csfle".to_string(), - key_name: "key-name-csfle".to_string(), - key_version: None, - endpoint: Some("doesnotexist.invalid:443".to_string()), - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let result = client_encryption.create_data_key(master_key).run().await; - let err = result.unwrap_err(); - assert!(err.is_csfle_error()); - assert!( - err.to_string().contains("Invalid KMS response"), - "unexpected error: {}", - err - ); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 10. kmip, no endpoint) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_kmip_no_endpoint() -> Result<()> { - if !check_env("custom_endpoint_kmip_no_endpoint", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Kmip { - key_id: Some("1".to_string()), - endpoint: None, - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let key_id = client_encryption - .create_data_key(master_key.clone()) - .run() - .await?; - validate_roundtrip(&client_encryption, key_id).await?; - - let client_encryption_invalid = custom_endpoint_setup(false).await?; - let result = client_encryption_invalid - .create_data_key(master_key) - .run() - .await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 7. Custom Endpoint Test (case 11. kmip, valid endpoint) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_kmip_valid_endpoint() -> Result<()> { - if !check_env("custom_endpoint_kmip_valid_endpoint", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Kmip { - key_id: Some("1".to_string()), - endpoint: Some("localhost:5698".to_string()), - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let key_id = client_encryption.create_data_key(master_key).run().await?; - validate_roundtrip(&client_encryption, key_id).await -} - -// Prose test 7. Custom Endpoint Test (case 12. kmip, invalid endpoint) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn custom_endpoint_kmip_invalid_endpoint() -> Result<()> { - if !check_env("custom_endpoint_kmip_invalid_endpoint", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let master_key = MasterKey::Kmip { - key_id: Some("1".to_string()), - endpoint: Some("doesnotexist.local:5698".to_string()), - }; - - let client_encryption = custom_endpoint_setup(true).await?; - let result = client_encryption.create_data_key(master_key).run().await; - assert!(result.unwrap_err().is_network_error()); - - Ok(()) -} - -// Prose test 8. Bypass Spawning mongocryptd (Via loading shared library) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bypass_mongocryptd_via_shared_library() -> Result<()> { - if !check_env("bypass_mongocryptd_via_shared_library", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - if *DISABLE_CRYPT_SHARED { - log_uncaptured( - "Skipping bypass mongocryptd via shared library test: crypt_shared is disabled.", - ); - return Ok(()); - } - - // Setup: encrypted client. - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) - .extra_options(doc! { - "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", - "mongocryptdSpawnArgs": ["--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], - "cryptSharedLibPath": EXTRA_OPTIONS.get("cryptSharedLibPath").unwrap(), - "cryptSharedRequired": true, - }) - .build() - .await?; - - // Test: insert succeeds. - client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "unencrypted": "test" }, None) - .await?; - // Test: mongocryptd not spawned. - assert!(!client_encrypted.mongocryptd_spawned().await); - // Test: attempting to connect fails. - let client = - Client::with_uri_str("mongodb://localhost:27021/?serverSelectionTimeoutMS=1000").await?; - let result = client.list_database_names(None, None).await; - assert!(result.unwrap_err().is_server_selection_error()); - - Ok(()) -} - -// Prose test 8. Bypass Spawning mongocryptd (Via mongocryptdBypassSpawn) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bypass_mongocryptd_via_bypass_spawn() -> Result<()> { - if !check_env("bypass_mongocryptd_via_bypass_spawn", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Setup: encrypted client. - let extra_options = doc! { - "mongocryptdBypassSpawn": true, - "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", - "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], - }; - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) - .extra_options(extra_options) - .disable_crypt_shared(true) - .build() - .await?; - - // Test: insert fails. - let err = client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "encrypted": "test" }, None) - .await - .unwrap_err(); - assert!(err.is_server_selection_error(), "unexpected error: {}", err); - - Ok(()) -} - -enum Bypass { - AutoEncryption, - QueryAnalysis, -} - -async fn bypass_mongocryptd_unencrypted_insert(bypass: Bypass) -> Result<()> { - let _guard = LOCK.run_exclusively().await; - - // Setup: encrypted client. - let extra_options = doc! { - "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], - }; - let builder = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .extra_options(extra_options) - .disable_crypt_shared(true); - let builder = match bypass { - Bypass::AutoEncryption => builder.bypass_auto_encryption(true), - Bypass::QueryAnalysis => builder.bypass_query_analysis(true), - }; - let client_encrypted = builder.build().await?; - - // Test: insert succeeds. - client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "unencrypted": "test" }, None) - .await?; - // Test: mongocryptd not spawned. - assert!(!client_encrypted.mongocryptd_spawned().await); - // Test: attempting to connect fails. - let client = - Client::with_uri_str("mongodb://localhost:27021/?serverSelectionTimeoutMS=1000").await?; - let result = client.list_database_names(None, None).await; - assert!(result.unwrap_err().is_server_selection_error()); - - Ok(()) -} - -// Prose test 8. Bypass Spawning mongocryptd (Via bypassAutoEncryption) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bypass_mongocryptd_via_bypass_auto_encryption() -> Result<()> { - if !check_env("bypass_mongocryptd_via_bypass_auto_encryption", false) { - return Ok(()); - } - bypass_mongocryptd_unencrypted_insert(Bypass::AutoEncryption).await -} - -// Prose test 8. Bypass Spawning mongocryptd (Via bypassQueryAnalysis) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bypass_mongocryptd_via_bypass_query_analysis() -> Result<()> { - if !check_env("bypass_mongocryptd_via_bypass_query_analysis", false) { - return Ok(()); - } - bypass_mongocryptd_unencrypted_insert(Bypass::QueryAnalysis).await -} - -// Prose test 9. Deadlock Tests -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn deadlock() -> Result<()> { - if !check_env("deadlock", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Case 1 - DeadlockTestCase { - max_pool_size: 1, - bypass_auto_encryption: false, - set_key_vault_client: false, - expected_encrypted_commands: vec![ - DeadlockExpectation { - command: "listCollections", - db: "db", - }, - DeadlockExpectation { - command: "find", - db: "keyvault", - }, - DeadlockExpectation { - command: "insert", - db: "db", - }, - DeadlockExpectation { - command: "find", - db: "db", - }, - ], - expected_keyvault_commands: vec![], - expected_number_of_clients: 2, - } - .run() - .await?; - // Case 2 - DeadlockTestCase { - max_pool_size: 1, - bypass_auto_encryption: false, - set_key_vault_client: true, - expected_encrypted_commands: vec![ - DeadlockExpectation { - command: "listCollections", - db: "db", - }, - DeadlockExpectation { - command: "insert", - db: "db", - }, - DeadlockExpectation { - command: "find", - db: "db", - }, - ], - expected_keyvault_commands: vec![DeadlockExpectation { - command: "find", - db: "keyvault", - }], - expected_number_of_clients: 2, - } - .run() - .await?; - // Case 3 - DeadlockTestCase { - max_pool_size: 1, - bypass_auto_encryption: true, - set_key_vault_client: false, - expected_encrypted_commands: vec![ - DeadlockExpectation { - command: "find", - db: "db", - }, - DeadlockExpectation { - command: "find", - db: "keyvault", - }, - ], - expected_keyvault_commands: vec![], - expected_number_of_clients: 2, - } - .run() - .await?; - // Case 4 - DeadlockTestCase { - max_pool_size: 1, - bypass_auto_encryption: true, - set_key_vault_client: true, - expected_encrypted_commands: vec![DeadlockExpectation { - command: "find", - db: "db", - }], - expected_keyvault_commands: vec![DeadlockExpectation { - command: "find", - db: "keyvault", - }], - expected_number_of_clients: 1, - } - .run() - .await?; - // Case 5: skipped (unlimited max_pool_size not supported) - // Case 6: skipped (unlimited max_pool_size not supported) - // Case 7: skipped (unlimited max_pool_size not supported) - // Case 8: skipped (unlimited max_pool_size not supported) - - Ok(()) -} - -struct DeadlockTestCase { - max_pool_size: u32, - bypass_auto_encryption: bool, - set_key_vault_client: bool, - expected_encrypted_commands: Vec, - expected_keyvault_commands: Vec, - expected_number_of_clients: usize, -} - -impl DeadlockTestCase { - async fn run(&self) -> Result<()> { - // Setup - let client_test = TestClient::new().await; - let client_keyvault = EventClient::with_options({ - let mut opts = CLIENT_OPTIONS.get().await.clone(); - opts.max_pool_size = Some(1); - opts - }) - .await; - let mut keyvault_events = client_keyvault.subscribe_to_events(); - client_test - .database("keyvault") - .collection::("datakeys") - .drop(None) - .await?; - client_test - .database("db") - .collection::("coll") - .drop(None) - .await?; - client_keyvault - .database("keyvault") - .collection::("datakeys") - .insert_one( - load_testdata("external/external-key.json")?, - InsertOneOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(), - ) - .await?; - client_test - .database("db") - .create_collection( - "coll", - CreateCollectionOptions::builder() - .validator( - doc! { "$jsonSchema": load_testdata("external/external-schema.json")? }, - ) - .build(), - ) - .await?; - let client_encryption = ClientEncryption::new( - client_test.clone().into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - let ciphertext = client_encryption - .encrypt( - RawBson::String("string0".to_string()), - EncryptKey::AltName("local".to_string()), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - - // Run test case - let event_handler = Arc::new(EventHandler::new()); - let mut encrypted_events = event_handler.subscribe(); - let mut opts = CLIENT_OPTIONS.get().await.clone(); - opts.max_pool_size = Some(self.max_pool_size); - opts.command_event_handler = Some(event_handler.clone()); - opts.sdam_event_handler = Some(event_handler.clone()); - let client_encrypted = - Client::encrypted_builder(opts, KV_NAMESPACE.clone(), LOCAL_KMS.clone())? - .bypass_auto_encryption(self.bypass_auto_encryption) - .key_vault_client( - if self.set_key_vault_client { - Some(client_keyvault.clone().into_client()) - } else { - None - }, - ) - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - - if self.bypass_auto_encryption { - client_test - .database("db") - .collection::("coll") - .insert_one(doc! { "_id": 0, "encrypted": ciphertext }, None) - .await?; - } else { - client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "_id": 0, "encrypted": "string0" }, None) - .await?; - } - - let found = client_encrypted - .database("db") - .collection::("coll") - .find_one(doc! { "_id": 0 }, None) - .await?; - assert_eq!(found, Some(doc! { "_id": 0, "encrypted": "string0" })); - - let encrypted_events = encrypted_events - .collect_events(Duration::from_millis(500), |_| true) - .await; - let client_count = encrypted_events - .iter() - .filter(|ev| matches!(ev, Event::Sdam(SdamEvent::TopologyOpening(_)))) - .count(); - assert_eq!(self.expected_number_of_clients, client_count); - - let encrypted_commands: Vec<_> = encrypted_events - .into_iter() - .filter_map(|ev| ev.into_command_started_event()) - .collect(); - for expected in &self.expected_encrypted_commands { - expected.assert_matches_any("encrypted", &encrypted_commands); - } - - let keyvault_commands = keyvault_events - .collect_events_map(Duration::from_millis(500), |ev| { - ev.into_command_started_event() - }) - .await; - for expected in &self.expected_keyvault_commands { - expected.assert_matches_any("keyvault", &keyvault_commands); - } - - Ok(()) - } -} - -#[derive(Debug)] -struct DeadlockExpectation { - command: &'static str, - db: &'static str, -} - -impl DeadlockExpectation { - fn matches(&self, ev: &CommandStartedEvent) -> bool { - ev.command_name == self.command && ev.db == self.db - } - - fn assert_matches_any(&self, name: &str, commands: &[CommandStartedEvent]) { - for actual in commands { - if self.matches(actual) { - return; - } - } - panic!( - "No {} command matching {:?} found, events=\n{:?}", - name, self, commands - ); - } -} - -// Prose test 10. KMS TLS Tests -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn kms_tls() -> Result<()> { - if !check_env("kms_tls", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // Invalid KMS Certificate - let err = run_kms_tls_test("127.0.0.1:9000").await.unwrap_err(); - assert!( - err.to_string().contains("certificate verify failed"), - "unexpected error: {}", - err - ); - - // Invalid Hostname in KMS Certificate - let err = run_kms_tls_test("127.0.0.1:9001").await.unwrap_err(); - assert!( - err.to_string().contains("certificate verify failed"), - "unexpected error: {}", - err - ); - - Ok(()) -} - -async fn run_kms_tls_test(endpoint: impl Into) -> crate::error::Result<()> { - // Setup - let kv_client = TestClient::new().await; - let client_encryption = ClientEncryption::new( - kv_client.clone().into_client(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS.clone(), - )?; - - // Test - client_encryption - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: Some(endpoint.into()), - }) - .run() - .await - .map(|_| ()) -} - -// Prose test 11. KMS TLS Options Tests -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn kms_tls_options() -> Result<()> { - if !check_env("kms_tls_options", true) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - fn providers_with_tls( - mut providers: HashMap)>, - tls_opts: TlsOptions, - ) -> KmsProviderList { - for provider in [ - KmsProvider::Aws, - KmsProvider::Azure, - KmsProvider::Gcp, - KmsProvider::Kmip, - ] { - providers.get_mut(&provider).unwrap().1 = Some(tls_opts.clone()); - } - providers.into_iter().map(|(p, (o, t))| (p, o, t)).collect() - } - - // Setup - let mut base_providers = KMS_PROVIDERS_MAP.clone(); - base_providers - .get_mut(&KmsProvider::Azure) - .unwrap() - .0 - .insert("identityPlatformEndpoint", "127.0.0.1:9002"); - base_providers - .get_mut(&KmsProvider::Gcp) - .unwrap() - .0 - .insert("endpoint", "127.0.0.1:9002"); - - let cert_dir = PathBuf::from(std::env::var("CSFLE_TLS_CERT_DIR").unwrap()); - let ca_path = cert_dir.join("ca.pem"); - let key_path = cert_dir.join("client.pem"); - - let client_encryption_no_client_cert = ClientEncryption::new( - TestClient::new().await.into_client(), - KV_NAMESPACE.clone(), - providers_with_tls( - base_providers.clone(), - TlsOptions::builder().ca_file_path(ca_path.clone()).build(), - ), - )?; - - let client_encryption_with_tls = ClientEncryption::new( - TestClient::new().await.into_client(), - KV_NAMESPACE.clone(), - providers_with_tls( - base_providers.clone(), - TlsOptions::builder() - .ca_file_path(ca_path.clone()) - .cert_key_file_path(key_path.clone()) - .build(), - ), - )?; - - let client_encryption_expired = { - let mut providers = base_providers.clone(); - providers - .get_mut(&KmsProvider::Azure) - .unwrap() - .0 - .insert("identityPlatformEndpoint", "127.0.0.1:9000"); - providers - .get_mut(&KmsProvider::Gcp) - .unwrap() - .0 - .insert("endpoint", "127.0.0.1:9000"); - providers - .get_mut(&KmsProvider::Kmip) - .unwrap() - .0 - .insert("endpoint", "127.0.0.1:9000"); - - ClientEncryption::new( - TestClient::new().await.into_client(), - KV_NAMESPACE.clone(), - providers_with_tls( - providers, - TlsOptions::builder().ca_file_path(ca_path.clone()).build(), - ), - )? - }; - - let client_encryption_invalid_hostname = { - let mut providers = base_providers.clone(); - providers - .get_mut(&KmsProvider::Azure) - .unwrap() - .0 - .insert("identityPlatformEndpoint", "127.0.0.1:9001"); - providers - .get_mut(&KmsProvider::Gcp) - .unwrap() - .0 - .insert("endpoint", "127.0.0.1:9001"); - providers - .get_mut(&KmsProvider::Kmip) - .unwrap() - .0 - .insert("endpoint", "127.0.0.1:9001"); - - ClientEncryption::new( - TestClient::new().await.into_client(), - KV_NAMESPACE.clone(), - providers_with_tls( - providers, - TlsOptions::builder().ca_file_path(ca_path.clone()).build(), - ), - )? - }; - - async fn provider_test( - client_encryption: &ClientEncryption, - master_key: MasterKey, - expected_errs: &[&str], - ) -> Result<()> { - let err = client_encryption - .create_data_key(master_key) - .run() - .await - .unwrap_err(); - let err_str = err.to_string(); - if !expected_errs.iter().any(|s| err_str.contains(s)) { - Err(err)? - } - Ok(()) - } - - // Case 1: AWS - fn aws_key(endpoint: impl Into) -> MasterKey { - MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: Some(endpoint.into()), - } - } - - provider_test( - &client_encryption_no_client_cert, - aws_key("127.0.0.1:9002"), - &["SSL routines", "connection was forcibly closed"], - ) - .await?; - provider_test( - &client_encryption_with_tls, - aws_key("127.0.0.1:9002"), - &["parse error"], - ) - .await?; - provider_test( - &client_encryption_expired, - aws_key("127.0.0.1:9000"), - &["certificate verify failed"], - ) - .await?; - provider_test( - &client_encryption_invalid_hostname, - aws_key("127.0.0.1:9001"), - &["certificate verify failed"], - ) - .await?; - - // Case 2: Azure - let azure_key = MasterKey::Azure { - key_vault_endpoint: "doesnotexist.local".to_string(), - key_name: "foo".to_string(), - key_version: None, - }; - - provider_test( - &client_encryption_no_client_cert, - azure_key.clone(), - &["SSL routines", "connection was forcibly closed"], - ) - .await?; - provider_test( - &client_encryption_with_tls, - azure_key.clone(), - &["HTTP status=404"], - ) - .await?; - provider_test( - &client_encryption_expired, - azure_key.clone(), - &["certificate verify failed"], - ) - .await?; - provider_test( - &client_encryption_invalid_hostname, - azure_key.clone(), - &["certificate verify failed"], - ) - .await?; - - // Case 3: GCP - let gcp_key = MasterKey::Gcp { - project_id: "foo".to_string(), - location: "bar".to_string(), - key_ring: "baz".to_string(), - key_name: "foo".to_string(), - endpoint: None, - key_version: None, - }; - - provider_test( - &client_encryption_no_client_cert, - gcp_key.clone(), - &["SSL routines", "connection was forcibly closed"], - ) - .await?; - provider_test( - &client_encryption_with_tls, - gcp_key.clone(), - &["HTTP status=404"], - ) - .await?; - provider_test( - &client_encryption_expired, - gcp_key.clone(), - &["certificate verify failed"], - ) - .await?; - provider_test( - &client_encryption_invalid_hostname, - gcp_key.clone(), - &["certificate verify failed"], - ) - .await?; - - // Case 4: KMIP - let kmip_key = MasterKey::Kmip { - key_id: None, - endpoint: None, - }; - - provider_test( - &client_encryption_no_client_cert, - kmip_key.clone(), - &["SSL routines", "connection was forcibly closed"], - ) - .await?; - // This one succeeds! - client_encryption_with_tls - .create_data_key(kmip_key.clone()) - .run() - .await?; - provider_test( - &client_encryption_expired, - kmip_key.clone(), - &["certificate verify failed"], - ) - .await?; - provider_test( - &client_encryption_invalid_hostname, - kmip_key.clone(), - &["certificate verify failed"], - ) - .await?; - - Ok(()) +macro_rules! failure { + ($($arg:tt)*) => {{ + crate::error::Error::internal(format!($($arg)*)).into() + }} } +use failure; async fn fle2v2_ok(name: &str) -> bool { - if *SERVERLESS { - log_uncaptured(format!("Skipping {}: not supported on serverless", name)); - return false; - } - let setup_client = Client::test_builder().build().await; - if setup_client.server_version_lt(7, 0) { + if server_version_lt(7, 0).await { log_uncaptured(format!("Skipping {}: not supported on server < 7.0", name)); return false; } - if setup_client.is_standalone() { + if topology_is_standalone().await { log_uncaptured(format!("Skipping {}: not supported on standalone", name)); return false; } true } -// Prose test 12. Explicit Encryption (Case 1: can insert encrypted indexed and find) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn explicit_encryption_case_1() -> Result<()> { - if !check_env("explicit_encryption_case_1", false) { - return Ok(()); - } - if !fle2v2_ok("explicit_encryption_case_1").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - let testdata = match explicit_encryption_setup().await? { - Some(t) => t, - None => return Ok(()), - }; - let enc_coll = testdata - .encrypted_client - .database("db") - .collection::("explicit_encryption"); - - let insert_payload = testdata - .client_encryption - .encrypt( - "encrypted indexed value", - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Indexed, - ) - .contention_factor(0) - .run() - .await?; - enc_coll - .insert_one(doc! { "encryptedIndexed": insert_payload }, None) - .await?; - - let find_payload = testdata - .client_encryption - .encrypt( - "encrypted indexed value", - EncryptKey::Id(testdata.key1_id), - Algorithm::Indexed, - ) - .query_type("equality".to_string()) - .contention_factor(0) - .run() - .await?; - let found: Vec<_> = enc_coll - .find(doc! { "encryptedIndexed": find_payload }, None) - .await? - .try_collect() - .await?; - assert_eq!(1, found.len()); - assert_eq!( - "encrypted indexed value", - found[0].get_str("encryptedIndexed")? - ); - - Ok(()) -} +pub(crate) fn fill_kms_placeholders( + kms_provider_map: std::collections::HashMap, +) -> KmsProviderList { + use mongocrypt::ctx::KmsProviderType; -// Prose test 12. Explicit Encryption (Case 2: can insert encrypted indexed and find with non-zero -// contention) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn explicit_encryption_case_2() -> Result<()> { - if !check_env("explicit_encryption_case_2", false) { - return Ok(()); - } - if !fle2v2_ok("explicit_encryption_case_2").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; + let placeholder = doc! { "$$placeholder": 1 }; - let testdata = match explicit_encryption_setup().await? { - Some(t) => t, - None => return Ok(()), - }; - let enc_coll = testdata - .encrypted_client - .database("db") - .collection::("explicit_encryption"); + let mut kms_providers = Vec::new(); + for (provider, mut config) in kms_provider_map { + // AWS uses temp creds if the "sessionToken" key is present in the config + let test_kms_provider = if *provider.provider_type() == KmsProviderType::Aws + && config.contains_key("sessionToken") + { + Some(&*AWS_TEMP_KMS) + } else { + (*ALL_KMS_PROVIDERS).iter().find(|(p, ..)| p == &provider) + }; - for _ in 0..10 { - let insert_payload = testdata - .client_encryption - .encrypt( - "encrypted indexed value", - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Indexed, - ) - .contention_factor(10) - .run() - .await?; - enc_coll - .insert_one(doc! { "encryptedIndexed": insert_payload }, None) - .await?; - } + for (key, value) in config.iter_mut() { + if value.as_document() == Some(&placeholder) { + let test_kms_provider = test_kms_provider + .unwrap_or_else(|| panic!("missing config for {:?}", provider)); + let placeholder_value = test_kms_provider.1.get(key).unwrap_or_else(|| { + panic!("provider config {:?} missing key {:?}", provider, key) + }); + *value = placeholder_value.clone(); + } + } - let find_payload = testdata - .client_encryption - .encrypt( - "encrypted indexed value", - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Indexed, - ) - .query_type("equality".to_string()) - .contention_factor(0) - .run() - .await?; - let found: Vec<_> = enc_coll - .find(doc! { "encryptedIndexed": find_payload }, None) - .await? - .try_collect() - .await?; - assert!(found.len() < 10); - for doc in found { - assert_eq!("encrypted indexed value", doc.get_str("encryptedIndexed")?); + let tls_options = test_kms_provider.and_then(|(_, _, tls_options)| tls_options.clone()); + kms_providers.push((provider, config, tls_options)); } - let find_payload2 = testdata - .client_encryption - .encrypt( - "encrypted indexed value", - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Indexed, - ) - .query_type("equality") - .contention_factor(10) - .run() - .await?; - let found: Vec<_> = enc_coll - .find(doc! { "encryptedIndexed": find_payload2 }, None) - .await? - .try_collect() - .await?; - assert_eq!(10, found.len()); - for doc in found { - assert_eq!("encrypted indexed value", doc.get_str("encryptedIndexed")?); - } - - Ok(()) -} - -// Prose test 12. Explicit Encryption (Case 3: can insert encrypted unindexed) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn explicit_encryption_case_3() -> Result<()> { - if !check_env("explicit_encryption_case_3", false) { - return Ok(()); - } - if !fle2v2_ok("explicit_encryption_case_3").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let testdata = match explicit_encryption_setup().await? { - Some(t) => t, - None => return Ok(()), - }; - let enc_coll = testdata - .encrypted_client - .database("db") - .collection::("explicit_encryption"); - - let insert_payload = testdata - .client_encryption - .encrypt( - "encrypted unindexed value", - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Unindexed, - ) - .run() - .await?; - enc_coll - .insert_one( - doc! { "_id": 1, "encryptedUnindexed": insert_payload }, - None, - ) - .await?; - - let found: Vec<_> = enc_coll - .find(doc! { "_id": 1 }, None) - .await? - .try_collect() - .await?; - assert_eq!(1, found.len()); - assert_eq!( - "encrypted unindexed value", - found[0].get_str("encryptedUnindexed")? - ); - - Ok(()) -} - -// Prose test 12. Explicit Encryption (Case 4: can roundtrip encrypted indexed) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn explicit_encryption_case_4() -> Result<()> { - if !check_env("explicit_encryption_case_4", false) { - return Ok(()); - } - if !fle2v2_ok("explicit_encryption_case_4").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let testdata = match explicit_encryption_setup().await? { - Some(t) => t, - None => return Ok(()), - }; - - let raw_value = RawBson::String("encrypted indexed value".to_string()); - let payload = testdata - .client_encryption - .encrypt( - raw_value.clone(), - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Indexed, - ) - .contention_factor(0) - .run() - .await?; - let roundtrip = testdata - .client_encryption - .decrypt(payload.as_raw_binary()) - .await?; - assert_eq!(raw_value, roundtrip); - - Ok(()) -} - -// Prose test 12. Explicit Encryption (Case 5: can roundtrip encrypted unindexed) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn explicit_encryption_case_5() -> Result<()> { - if !check_env("explicit_encryption_case_5", false) { - return Ok(()); - } - if !fle2v2_ok("explicit_encryption_case_5").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let testdata = match explicit_encryption_setup().await? { - Some(t) => t, - None => return Ok(()), - }; - - let raw_value = RawBson::String("encrypted unindexed value".to_string()); - let payload = testdata - .client_encryption - .encrypt( - raw_value.clone(), - EncryptKey::Id(testdata.key1_id.clone()), - Algorithm::Unindexed, - ) - .run() - .await?; - let roundtrip = testdata - .client_encryption - .decrypt(payload.as_raw_binary()) - .await?; - assert_eq!(raw_value, roundtrip); - - Ok(()) -} - -struct ExplicitEncryptionTestData { - key1_id: Binary, - client_encryption: ClientEncryption, - encrypted_client: Client, -} - -async fn explicit_encryption_setup() -> Result> { - let key_vault_client = TestClient::new().await; - if key_vault_client.server_version_lt(6, 0) { - log_uncaptured("skipping explicit encryption test: server below 6.0"); - return Ok(None); - } - if key_vault_client.is_standalone() { - log_uncaptured("skipping explicit encryption test: cannot run on standalone"); - return Ok(None); - } - - let encrypted_fields = load_testdata("data/encryptedFields.json")?; - let key1_document = load_testdata("data/keys/key1-document.json")?; - let key1_id = match key1_document.get("_id").unwrap() { - Bson::Binary(b) => b.clone(), - v => return Err(failure!("expected binary _id, got {:?}", v)), - }; - - let db = key_vault_client.database("db"); - db.collection::("explicit_encryption") - .drop( - DropCollectionOptions::builder() - .encrypted_fields(encrypted_fields.clone()) - .build(), - ) - .await?; - db.create_collection( - "explicit_encryption", - CreateCollectionOptions::builder() - .encrypted_fields(encrypted_fields) - .build(), - ) - .await?; - let keyvault = key_vault_client.database("keyvault"); - keyvault - .collection::("datakeys") - .drop(None) - .await?; - keyvault.create_collection("datakeys", None).await?; - keyvault - .collection::("datakeys") - .insert_one( - key1_document, - InsertOneOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(), - ) - .await?; - - let client_encryption = ClientEncryption::new( - key_vault_client.into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - let encrypted_client = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .bypass_query_analysis(true) - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - - Ok(Some(ExplicitEncryptionTestData { - key1_id, - client_encryption, - encrypted_client, - })) -} - -// Prose test 13. Unique Index on keyAltNames (Case 1: createDataKey()) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn unique_index_keyaltnames_create_data_key() -> Result<()> { - if !check_env("unique_index_keyaltnames_create_data_key", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let (client_encryption, _) = unique_index_keyaltnames_setup().await?; - - // Succeeds - client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(vec!["abc".to_string()]) - .run() - .await?; - // Fails: duplicate key - let err = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(vec!["abc".to_string()]) - .run() - .await - .unwrap_err(); - assert_eq!( - Some(11000), - write_err_code(&err), - "unexpected error: {}", - err - ); - // Fails: duplicate key - let err = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(vec!["def".to_string()]) - .run() - .await - .unwrap_err(); - assert_eq!( - Some(11000), - write_err_code(&err), - "unexpected error: {}", - err - ); - - Ok(()) -} - -// Prose test 13. Unique Index on keyAltNames (Case 2: addKeyAltName()) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn unique_index_keyaltnames_add_key_alt_name() -> Result<()> { - if !check_env("unique_index_keyaltnames_add_key_alt_name", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let (client_encryption, key) = unique_index_keyaltnames_setup().await?; - - // Succeeds - let new_key = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - client_encryption.add_key_alt_name(&new_key, "abc").await?; - // Still succeeds, has alt name - let prev_key = client_encryption - .add_key_alt_name(&new_key, "abc") - .await? - .unwrap(); - assert_eq!("abc", prev_key.get_array("keyAltNames")?.get_str(0)?); - // Fails: adding alt name used for `key` to `new_key` - let err = client_encryption - .add_key_alt_name(&new_key, "def") - .await - .unwrap_err(); - assert_eq!( - Some(11000), - write_err_code(&err), - "unexpected error: {}", - err - ); - // Succeds: re-adding alt name to `new_key` - let prev_key = client_encryption - .add_key_alt_name(&key, "def") - .await? - .unwrap(); - assert_eq!("def", prev_key.get_array("keyAltNames")?.get_str(0)?); - - Ok(()) -} - -// `Error::code` skips write errors per the SDAM spec, but we need those. -fn write_err_code(err: &crate::error::Error) -> Option { - if let Some(code) = err.sdam_code() { - return Some(code); - } - match *err.kind { - ErrorKind::Write(WriteFailure::WriteError(WriteError { code, .. })) => Some(code), - _ => None, - } -} - -async fn unique_index_keyaltnames_setup() -> Result<(ClientEncryption, Binary)> { - let client = TestClient::new().await; - let datakeys = client - .database("keyvault") - .collection::("datakeys"); - datakeys.drop(None).await?; - datakeys - .create_index( - IndexModel { - keys: doc! { "keyAltNames": 1 }, - options: Some( - IndexOptions::builder() - .name("keyAltNames_1".to_string()) - .unique(true) - .partial_filter_expression(doc! { "keyAltNames": { "$exists": true } }) - .build(), - ), - }, - CreateIndexOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(), - ) - .await?; - let client_encryption = ClientEncryption::new( - client.into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - let key = client_encryption - .create_data_key(MasterKey::Local) - .key_alt_names(vec!["def".to_string()]) - .run() - .await?; - Ok((client_encryption, key)) -} - -// Prose test 14. Decryption Events (Case 1: Command Error) -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn decryption_events_command_error() -> Result<()> { - if !check_env("decryption_events_command_error", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let td = match DecryptionEventsTestdata::setup().await? { - Some(v) => v, - None => return Ok(()), - }; - - let fp = FailPoint::fail_command( - &["aggregate"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(123).build(), - ); - let _guard = fp.enable(&td.setup_client, None).await?; - let err = td - .decryption_events - .aggregate(vec![doc! { "$count": "total" }], None) - .await - .unwrap_err(); - assert_eq!(Some(123), err.sdam_code()); - assert!(td.ev_handler.failed.lock().unwrap().is_some()); - - Ok(()) -} - -// Prose test 14. Decryption Events (Case 2: Network Error) -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn decryption_events_network_error() -> Result<()> { - if !check_env("decryption_events_network_error", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let td = match DecryptionEventsTestdata::setup().await? { - Some(v) => v, - None => return Ok(()), - }; - - let fp = FailPoint::fail_command( - &["aggregate"], - FailPointMode::Times(1), - FailCommandOptions::builder() - .error_code(123) - .close_connection(true) - .build(), - ); - let _guard = fp.enable(&td.setup_client, None).await?; - let err = td - .decryption_events - .aggregate(vec![doc! { "$count": "total" }], None) - .await - .unwrap_err(); - assert!(err.is_network_error(), "unexpected error: {}", err); - assert!(td.ev_handler.failed.lock().unwrap().is_some()); - - Ok(()) -} - -// Prose test 14. Decryption Events (Case 3: Decrypt Error) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn decryption_events_decrypt_error() -> Result<()> { - if !check_env("decryption_events_decrypt_error", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let td = match DecryptionEventsTestdata::setup().await? { - Some(v) => v, - None => return Ok(()), - }; - td.decryption_events - .insert_one(doc! { "encrypted": td.malformed_ciphertext }, None) - .await?; - let err = td - .decryption_events - .aggregate(vec![], None) - .await - .unwrap_err(); - assert!(err.is_csfle_error()); - let guard = td.ev_handler.succeeded.lock().unwrap(); - let ev = guard.as_ref().unwrap(); - assert_eq!( - ElementType::Binary, - ev.reply.get_document("cursor")?.get_array("firstBatch")?[0] - .as_document() - .unwrap() - .get("encrypted") - .unwrap() - .element_type() - ); - - Ok(()) -} - -// Prose test 14. Decryption Events (Case 4: Decrypt Success) -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn decryption_events_decrypt_success() -> Result<()> { - if !check_env("decryption_events_decrypt_success", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let td = match DecryptionEventsTestdata::setup().await? { - Some(v) => v, - None => return Ok(()), - }; - td.decryption_events - .insert_one(doc! { "encrypted": td.ciphertext }, None) - .await?; - td.decryption_events.aggregate(vec![], None).await?; - let guard = td.ev_handler.succeeded.lock().unwrap(); - let ev = guard.as_ref().unwrap(); - assert_eq!( - ElementType::Binary, - ev.reply.get_document("cursor")?.get_array("firstBatch")?[0] - .as_document() - .unwrap() - .get("encrypted") - .unwrap() - .element_type() - ); - - Ok(()) -} - -struct DecryptionEventsTestdata { - setup_client: TestClient, - decryption_events: Collection, - ev_handler: Arc, - ciphertext: Binary, - malformed_ciphertext: Binary, -} - -impl DecryptionEventsTestdata { - async fn setup() -> Result> { - let setup_client = TestClient::new().await; - if !setup_client.is_standalone() { - log_uncaptured("skipping decryption events test: requires standalone topology"); - return Ok(None); - } - let db = setup_client.database("db"); - db.collection::("decryption_events") - .drop(None) - .await?; - db.create_collection("decryption_events", None).await?; - - let client_encryption = ClientEncryption::new( - setup_client.clone().into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - let key_id = client_encryption - .create_data_key(MasterKey::Local) - .run() - .await?; - let ciphertext = client_encryption - .encrypt( - "hello", - EncryptKey::Id(key_id), - Algorithm::AeadAes256CbcHmacSha512Deterministic, - ) - .run() - .await?; - let mut malformed_ciphertext = ciphertext.clone(); - let last = malformed_ciphertext.bytes.last_mut().unwrap(); - *last = last.wrapping_add(1); - - let ev_handler = DecryptionEventsHandler::new(); - let mut opts = CLIENT_OPTIONS.get().await.clone(); - opts.retry_reads = Some(false); - opts.command_event_handler = Some(ev_handler.clone()); - let encrypted_client = - Client::encrypted_builder(opts, KV_NAMESPACE.clone(), LOCAL_KMS.clone())? - .extra_options(EXTRA_OPTIONS.clone()) - .disable_crypt_shared(*DISABLE_CRYPT_SHARED) - .build() - .await?; - let decryption_events = encrypted_client - .database("db") - .collection("decryption_events"); - - Ok(Some(Self { - setup_client, - decryption_events, - ev_handler, - ciphertext, - malformed_ciphertext, - })) - } -} - -#[derive(Debug)] -struct DecryptionEventsHandler { - succeeded: Mutex>, - failed: Mutex>, -} - -impl DecryptionEventsHandler { - fn new() -> Arc { - Arc::new(Self { - succeeded: Mutex::new(None), - failed: Mutex::new(None), - }) - } -} - -impl CommandEventHandler for DecryptionEventsHandler { - fn handle_command_succeeded_event(&self, event: CommandSucceededEvent) { - if event.command_name == "aggregate" { - *self.succeeded.lock().unwrap() = Some(event); - } - } - - fn handle_command_failed_event(&self, event: CommandFailedEvent) { - if event.command_name == "aggregate" { - *self.failed.lock().unwrap() = Some(event); - } - } -} - -// Prose test 15. On-demand AWS Credentials (failure) -#[cfg(feature = "aws-auth")] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn on_demand_aws_failure() -> Result<()> { - if !check_env("on_demand_aws_failure", false) { - return Ok(()); - } - if std::env::var("AWS_ACCESS_KEY_ID").is_ok() && std::env::var("AWS_SECRET_ACCESS_KEY").is_ok() - { - log_uncaptured("Skipping on_demand_aws_failure: credentials set"); - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let ce = ClientEncryption::new( - Client::test_builder().build().await.into_client(), - KV_NAMESPACE.clone(), - [(KmsProvider::Aws, doc! {}, None)], - )?; - let result = ce - .create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: None, - }) - .run() - .await; - assert!(result.is_err(), "Expected error, got {:?}", result); - - Ok(()) -} - -// Prose test 15. On-demand AWS Credentials (success) -#[cfg(feature = "aws-auth")] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn on_demand_aws_success() -> Result<()> { - if !check_env("on_demand_aws_success", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let ce = ClientEncryption::new( - Client::test_builder().build().await.into_client(), - KV_NAMESPACE.clone(), - [(KmsProvider::Aws, doc! {}, None)], - )?; - ce.create_data_key(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: None, - }) - .run() - .await?; - - Ok(()) -} - -// TODO RUST-1441: implement prose test 16. Rewrap - -// Prose test 17. On-demand GCP Credentials -#[cfg(feature = "gcp-kms")] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn on_demand_gcp_credentials() -> Result<()> { - let _guard = LOCK.run_exclusively().await; - - let util_client = TestClient::new().await.into_client(); - let client_encryption = ClientEncryption::new( - util_client, - KV_NAMESPACE.clone(), - [(KmsProvider::Gcp, doc! {}, None)], - )?; - - let result = client_encryption - .create_data_key(MasterKey::Gcp { - project_id: "devprod-drivers".into(), - location: "global".into(), - key_ring: "key-ring-csfle".into(), - key_name: "key-name-csfle".into(), - key_version: None, - endpoint: None, - }) - .run() - .await; - - if std::env::var("ON_DEMAND_GCP_CREDS_SHOULD_SUCCEED").is_ok() { - result.unwrap(); - } else { - let error = result.unwrap_err(); - match *error.kind { - ErrorKind::Encryption(e) => { - assert!(matches!(e.kind, mongocrypt::error::ErrorKind::Kms)); - assert!(e.message.unwrap().contains("GCP credentials")); - } - other => panic!("Expected encryption error, got {:?}", other), - } - } - - Ok(()) -} - -// Prose test 18. Azure IMDS Credentials -#[cfg(feature = "azure-kms")] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn azure_imds() -> Result<()> { - if !check_env("azure_imds", false) { - return Ok(()); - } - let _guard = LOCK.run_concurrently().await; - - let mut azure_exec = crate::client::csfle::state_machine::azure::ExecutorState::new()?; - azure_exec.test_host = Some(( - "localhost", - std::env::var("AZURE_IMDS_MOCK_PORT") - .unwrap() - .parse() - .unwrap(), - )); - - // Case 1: Success - { - let now = std::time::Instant::now(); - let token = azure_exec.get_token().await?; - assert_eq!(token, rawdoc! { "accessToken": "magic-cookie" }); - let cached = azure_exec.take_cached().await.expect("cached token"); - assert_eq!(cached.server_response.expires_in, "70"); - assert_eq!(cached.server_response.resource, "https://vault.azure.net"); - assert!((65..75).contains(&cached.expire_time.duration_since(now).as_secs())); - } - - // Case 2: Empty JSON - { - azure_exec.test_param = Some("case=empty-json"); - let result = azure_exec.get_token().await; - assert!(result.is_err(), "expected err got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - } - - // Case 3: Bad JSON - { - azure_exec.test_param = Some("case=bad-json"); - let result = azure_exec.get_token().await; - assert!(result.is_err(), "expected err got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - } - - // Case 4: HTTP 404 - { - azure_exec.test_param = Some("case=404"); - let result = azure_exec.get_token().await; - assert!(result.is_err(), "expected err got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - } - - // Case 5: HTTP 500 - { - azure_exec.test_param = Some("case=500"); - let result = azure_exec.get_token().await; - assert!(result.is_err(), "expected err got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - } - - // Case 6: Slow Response - { - azure_exec.test_param = Some("case=slow"); - let result = azure_exec.get_token().await; - assert!(result.is_err(), "expected err got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - } - - Ok(()) -} - -// Prose test 19. Azure IMDS Credentials Integration Test (case 1: failure) -#[cfg(feature = "azure-kms")] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn azure_imds_integration_failure() -> Result<()> { - if !check_env("azure_imds_integration_failure", false) { - return Ok(()); - } - let _guard = LOCK.run_concurrently().await; - - let c = ClientEncryption::new( - Client::test_builder().build().await.into_client(), - KV_NAMESPACE.clone(), - [(KmsProvider::Azure, doc! {}, None)], - )?; - - let result = c - .create_data_key(MasterKey::Azure { - key_vault_endpoint: "https://keyvault-drivers-2411.vault.azure.net/keys/".to_string(), - key_name: "KEY-NAME".to_string(), - key_version: None, - }) - .run() - .await; - - assert!(result.is_err(), "expected error, got {:?}", result); - assert!(result.unwrap_err().is_auth_error()); - - Ok(()) -} - -// Prose test 20. Bypass creating mongocryptd client when shared library is loaded -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn bypass_mongocryptd_client() -> Result<()> { - if !check_env("bypass_mongocryptd_client", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - if *DISABLE_CRYPT_SHARED { - log_uncaptured("Skipping bypass mongocryptd client test: crypt_shared is disabled."); - return Ok(()); - } - - let connected = Arc::new(AtomicBool::new(false)); - { - let connected = Arc::clone(&connected); - let listener = bind("127.0.0.1:27021").await?; - runtime::spawn(async move { - let _ = listener.accept().await; - log_uncaptured("test failure: connection accepted"); - connected.store(true, Ordering::SeqCst); - }) - }; - - let client_encrypted = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .extra_options({ - let mut extra_options = EXTRA_OPTIONS.clone(); - extra_options.insert("mongocryptdURI", "mongodb://localhost:27021"); - extra_options - }) - .build() - .await?; - client_encrypted - .database("db") - .collection::("coll") - .insert_one(doc! { "unencrypted": "test" }, None) - .await?; - - assert!(!client_encrypted.has_mongocryptd_client().await); - assert!(!connected.load(Ordering::SeqCst)); - - Ok(()) -} - -// Prost test 21. Automatic Data Encryption Keys -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn auto_encryption_keys_local() -> Result<()> { - auto_encryption_keys(MasterKey::Local).await -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn auto_encryption_keys_aws() -> Result<()> { - auto_encryption_keys(MasterKey::Aws { - region: "us-east-1".to_string(), - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - .to_string(), - endpoint: None, - }) - .await -} - -async fn auto_encryption_keys(master_key: MasterKey) -> Result<()> { - if !check_env("custom_key_material", false) { - return Ok(()); - } - if !fle2v2_ok("auto_encryption_keys").await { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - let client = Client::test_builder().build().await; - if client.server_version_lt(6, 0) { - log_uncaptured("Skipping auto_encryption_key test: server < 6.0"); - return Ok(()); - } - if client.is_standalone() { - log_uncaptured("Skipping auto_encryption_key test: standalone server"); - return Ok(()); - } - let db = client.database("test_auto_encryption_keys"); - db.drop(None).await?; - let ce = ClientEncryption::new( - client.into_client(), - KV_NAMESPACE.clone(), - KMS_PROVIDERS - .iter() - .filter(|(p, ..)| p == &KmsProvider::Local || p == &KmsProvider::Aws) - .cloned() - .collect::>(), - )?; - - // Case 1: Simple Creation and Validation - let options = CreateCollectionOptions::builder() - .encrypted_fields(doc! { - "fields": [{ - "path": "ssn", - "bsonType": "string", - "keyId": Bson::Null, - }], - }) - .build(); - ce.create_encrypted_collection(&db, "case_1", master_key.clone(), options) - .await - .1?; - let coll = db.collection::("case_1"); - let result = coll.insert_one(doc! { "ssn": "123-45-6789" }, None).await; - assert!( - result.as_ref().unwrap_err().code() == Some(121), - "Expected error 121 (failed validation), got {:?}", - result - ); - - // Case 2: Missing encryptedFields - let result = ce - .create_encrypted_collection( - &db, - "case_2", - master_key.clone(), - CreateCollectionOptions::default(), - ) - .await - .1; - assert!( - result.as_ref().unwrap_err().is_invalid_argument(), - "Expected invalid argument error, got {:?}", - result - ); - - // Case 3: Invalid keyId - let options = CreateCollectionOptions::builder() - .encrypted_fields(doc! { - "fields": [{ - "path": "ssn", - "bsonType": "string", - "keyId": false, - }], - }) - .build(); - let result = ce - .create_encrypted_collection(&db, "case_1", master_key.clone(), options) - .await - .1; - assert!( - result.as_ref().unwrap_err().code() == Some(14), - "Expected error 14 (type mismatch), got {:?}", - result - ); - - // Case 4: Insert encrypted value - let options = CreateCollectionOptions::builder() - .encrypted_fields(doc! { - "fields": [{ - "path": "ssn", - "bsonType": "string", - "keyId": Bson::Null, - }], - }) - .build(); - let (ef, result) = ce - .create_encrypted_collection(&db, "case_4", master_key.clone(), options) - .await; - result?; - let key = match ef.get_array("fields")?[0] - .as_document() - .unwrap() - .get("keyId") - .unwrap() - { - Bson::Binary(bin) => bin.clone(), - v => panic!("invalid keyId {:?}", v), - }; - let encrypted_payload = ce - .encrypt("123-45-6789", key, Algorithm::Unindexed) - .run() - .await?; - let coll = db.collection::("case_1"); - coll.insert_one(doc! { "ssn": encrypted_payload }, None) - .await?; - - Ok(()) -} - -// Prose test 22. Range explicit encryption -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn range_explicit_encryption() -> Result<()> { - if !fle2v2_ok("range_explicit_encryption").await { - return Ok(()); - } - let client = TestClient::new().await; - if client.server_version_lt(6, 2) || client.is_standalone() { - log_uncaptured("Skipping range_explicit_encryption due to unsupported topology"); - return Ok(()); - } - - range_explicit_encryption_test( - "DecimalNoPrecision", - RangeOptions::builder().sparsity(1).build(), - ) - .await?; - range_explicit_encryption_test( - "DecimalPrecision", - RangeOptions::builder() - .sparsity(1) - .min(Bson::Decimal128("0".parse()?)) - .max(Bson::Decimal128("200".parse()?)) - .precision(2) - .build(), - ) - .await?; - range_explicit_encryption_test( - "DoubleNoPrecision", - RangeOptions::builder().sparsity(1).build(), - ) - .await?; - range_explicit_encryption_test( - "DoublePrecision", - RangeOptions::builder() - .sparsity(1) - .min(Bson::Double(0.0)) - .max(Bson::Double(200.0)) - .precision(2) - .build(), - ) - .await?; - range_explicit_encryption_test( - "Date", - RangeOptions::builder() - .sparsity(1) - .min(Bson::DateTime(DateTime::from_millis(0))) - .max(Bson::DateTime(DateTime::from_millis(200))) - .build(), - ) - .await?; - range_explicit_encryption_test( - "Int", - RangeOptions::builder() - .sparsity(1) - .min(Bson::Int32(0)) - .max(Bson::Int32(200)) - .build(), - ) - .await?; - range_explicit_encryption_test( - "Long", - RangeOptions::builder() - .sparsity(1) - .min(Bson::Int64(0)) - .max(Bson::Int64(200)) - .build(), - ) - .await?; - - Ok(()) -} - -async fn range_explicit_encryption_test( - bson_type: &str, - range_options: RangeOptions, -) -> Result<()> { - let _guard = LOCK.run_exclusively().await; - let util_client = TestClient::new().await; - - let encrypted_fields = - load_testdata(&format!("data/range-encryptedFields-{}.json", bson_type))?; - - let key1_document = load_testdata("data/keys/key1-document.json")?; - let key1_id = match key1_document.get("_id").unwrap() { - Bson::Binary(binary) => binary, - _ => unreachable!(), - } - .clone(); - - let explicit_encryption_collection = util_client - .database("db") - .collection::("explicit_encryption"); - explicit_encryption_collection - .drop( - DropCollectionOptions::builder() - .encrypted_fields(encrypted_fields.clone()) - .build(), - ) - .await?; - util_client - .database("db") - .create_collection( - "explicit_encryption", - CreateCollectionOptions::builder() - .encrypted_fields(encrypted_fields.clone()) - .build(), - ) - .await?; - - let datakeys_collection = util_client - .database("keyvault") - .collection::("datakeys"); - datakeys_collection.drop(None).await?; - util_client - .database("keyvault") - .create_collection("datakeys", None) - .await?; - - datakeys_collection - .insert_one( - key1_document, - InsertOneOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(), - ) - .await?; - - let key_vault_client = TestClient::new().await; - - let client_encryption = ClientEncryption::new( - key_vault_client.into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - - let encrypted_client = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .bypass_query_analysis(true) - .build() - .await?; - - let key = format!("encrypted{}", bson_type); - let bson_numbers: BTreeMap = [0, 6, 30, 200] - .iter() - .map(|num| (*num, get_raw_bson_from_num(bson_type, *num))) - .collect(); - let explicit_encryption_collection = encrypted_client - .database("db") - .collection("explicit_encryption"); - - for (id, num) in bson_numbers.keys().enumerate() { - let encrypted_value = client_encryption - .encrypt( - bson_numbers[num].clone(), - key1_id.clone(), - Algorithm::RangePreview, - ) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - explicit_encryption_collection - .insert_one( - doc! { - &key: encrypted_value, - "_id": id as i32, - }, - None, - ) - .await?; - } - - // Case 1: Decrypt a payload - let insert_payload = client_encryption - .encrypt( - bson_numbers[&6].clone(), - key1_id.clone(), - Algorithm::RangePreview, - ) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - let decrypted = client_encryption - .decrypt(insert_payload.as_raw_binary()) - .await?; - assert_eq!(decrypted, bson_numbers[&6]); - - // Utilities for cases 2-5 - let explicit_encryption_collection = - explicit_encryption_collection.clone_with_type::(); - let find_options = FindOptions::builder().sort(doc! { "_id": 1 }).build(); - let assert_success = |actual: Vec, expected: &[i32]| { - assert_eq!(actual.len(), expected.len()); - for (idx, num) in expected.iter().enumerate() { - assert_eq!( - actual[idx].get(&key), - Ok(Some(bson_numbers[num].as_raw_bson_ref())) - ); - } - }; - - // Case 2: Find encrypted range and return the maximum - let query = rawdoc! { - "$and": [ - { &key: { "$gte": bson_numbers[&6].clone() } }, - { &key: { "$lte": bson_numbers[&200].clone() } }, - ] - }; - let find_payload = client_encryption - .encrypt_expression(query, key1_id.clone()) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - let docs: Vec = explicit_encryption_collection - .find(find_payload, find_options.clone()) - .await? - .try_collect() - .await?; - assert_success(docs, &[6, 30, 200]); - - // Case 3: Find encrypted range and return the minimum - let query = rawdoc! { - "$and": [ - { &key: { "$gte": bson_numbers[&0].clone() } }, - { &key: { "$lte": bson_numbers[&6].clone() } }, - ] - }; - let find_payload = client_encryption - .encrypt_expression(query, key1_id.clone()) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - let docs: Vec = encrypted_client - .database("db") - .collection("explicit_encryption") - .find(find_payload, find_options.clone()) - .await? - .try_collect() - .await?; - assert_success(docs, &[0, 6]); - - // Case 4: Find encrypted range with an open range query - let query = rawdoc! { - "$and": [ - { &key: { "$gt": bson_numbers[&30].clone() } }, - ] - }; - let find_payload = client_encryption - .encrypt_expression(query, key1_id.clone()) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - let docs: Vec = encrypted_client - .database("db") - .collection("explicit_encryption") - .find(find_payload, find_options.clone()) - .await? - .try_collect() - .await?; - assert_success(docs, &[200]); - - // Case 5: Run an aggregation expression inside $expr - let query = rawdoc! { "$and": [ { "$lt": [ format!("${key}"), get_raw_bson_from_num(bson_type, 30) ] } ] }; - let find_payload = client_encryption - .encrypt_expression(query, key1_id.clone()) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await?; - - let docs: Vec = encrypted_client - .database("db") - .collection("explicit_encryption") - .find(doc! { "$expr": find_payload }, find_options.clone()) - .await? - .try_collect() - .await?; - assert_success(docs, &[0, 6]); - - // Case 6: Encrypting a document greater than the maximum errors - if bson_type != "DoubleNoPrecision" && bson_type != "DecimalNoPrecision" { - let num = get_raw_bson_from_num(bson_type, 201); - let error = client_encryption - .encrypt(num, key1_id.clone(), Algorithm::RangePreview) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await - .unwrap_err(); - assert!(matches!(*error.kind, ErrorKind::Encryption(_))); - } - - // Case 7: Encrypting a document of a different type errors - if bson_type != "DoubleNoPrecision" && bson_type != "DecimalNoPrecision" { - let value = if bson_type == "Int" { - rawdoc! { &key: { "$numberDouble": "6" } } - } else { - rawdoc! { &key: { "$numberInt": "6" } } - }; - let error = client_encryption - .encrypt(value, key1_id.clone(), Algorithm::RangePreview) - .contention_factor(0) - .range_options(range_options.clone()) - .run() - .await - .unwrap_err(); - assert!(matches!(*error.kind, ErrorKind::Encryption(_))); - } - - // Case 8: Setting precision errors if the type is not a double - if !bson_type.contains("Double") && !bson_type.contains("Decimal") { - let range_options = RangeOptions::builder() - .sparsity(1) - .min(get_bson_from_num(bson_type, 0)) - .max(get_bson_from_num(bson_type, 200)) - .precision(2) - .build(); - let error = client_encryption - .encrypt( - bson_numbers[&6].clone(), - key1_id.clone(), - Algorithm::RangePreview, - ) - .contention_factor(0) - .range_options(range_options) - .run() - .await - .unwrap_err(); - assert!(matches!(*error.kind, ErrorKind::Encryption(_))); - } - - Ok(()) -} - -fn get_bson_from_num(bson_type: &str, num: i32) -> Bson { - match bson_type { - "DecimalNoPrecision" | "DecimalPrecision" => { - Bson::Decimal128(num.to_string().parse().unwrap()) - } - "DoubleNoPrecision" | "DoublePrecision" => Bson::Double(num as f64), - "Date" => Bson::DateTime(DateTime::from_millis(num as i64)), - "Int" => Bson::Int32(num), - "Long" => Bson::Int64(num as i64), - _ => unreachable!(), - } -} - -fn get_raw_bson_from_num(bson_type: &str, num: i32) -> RawBson { - match bson_type { - "DecimalNoPrecision" | "DecimalPrecision" => { - RawBson::Decimal128(num.to_string().parse().unwrap()) - } - "DoubleNoPrecision" | "DoublePrecision" => RawBson::Double(num as f64), - "Date" => RawBson::DateTime(DateTime::from_millis(num as i64)), - "Int" => RawBson::Int32(num), - "Long" => RawBson::Int64(num as i64), - _ => unreachable!(), - } -} - -async fn bind(addr: &str) -> Result { - #[cfg(feature = "tokio-runtime")] - { - Ok(TcpListener::bind(addr.parse::()?).await?) - } - #[cfg(not(feature = "tokio-runtime"))] - { - Ok(TcpListener::bind(addr).await?) - } -} - -// FLE 2.0 Documentation Example -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn fle2_example() -> Result<()> { - if !check_env("fle2_example", false) { - return Ok(()); - } - let _guard = LOCK.run_exclusively().await; - - // FLE 2 is not supported on Standalone topology. - let test_client = Client::test_builder().build().await; - if test_client.server_version_lt(7, 0) { - log_uncaptured("skipping fle2 example: server below 7.0"); - return Ok(()); - } - if test_client.is_standalone() { - log_uncaptured("skipping fle2 example: cannot run on standalone"); - return Ok(()); - } - if *SERVERLESS { - log_uncaptured("skipping fle2 example: cannot run on serverless"); - return Ok(()); - } - - // Drop data from prior test runs. - test_client - .database("keyvault") - .collection::("datakeys") - .drop(None) - .await?; - test_client.database("docsExamples").drop(None).await?; - - // Create two data keys. - let ce = ClientEncryption::new( - test_client.clone().into_client(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )?; - let key1_id = ce.create_data_key(MasterKey::Local).run().await?; - let key2_id = ce.create_data_key(MasterKey::Local).run().await?; - - // Create an encryptedFieldsMap. - let encrypted_fields_map = [( - "docsExamples.encrypted", - doc! { - "fields": [ - { - "path": "encryptedIndexed", - "bsonType": "string", - "keyId": key1_id, - "queries": { "queryType": "equality" }, - }, - { - "path": "encryptedUnindexed", - "bsonType": "string", - "keyId": key2_id, - }, - ] - }, - )]; - - // Create an FLE 2 collection. - let encrypted_client = Client::encrypted_builder( - CLIENT_OPTIONS.get().await.clone(), - KV_NAMESPACE.clone(), - LOCAL_KMS.clone(), - )? - .encrypted_fields_map(encrypted_fields_map) - .build() - .await?; - let db = encrypted_client.database("docsExamples"); - db.create_collection("encrypted", None).await?; - let encrypted_coll = db.collection::("encrypted"); - - // Auto encrypt an insert and find. - - // Encrypt an insert. - encrypted_coll - .insert_one( - doc! { - "_id": 1, - "encryptedIndexed": "indexedValue", - "encryptedUnindexed": "unindexedValue", - }, - None, - ) - .await?; - - // Encrypt a find. - let found = encrypted_coll - .find_one( - doc! { - "encryptedIndexed": "indexedValue", - }, - None, - ) - .await? - .unwrap(); - assert_eq!("indexedValue", found.get_str("encryptedIndexed")?); - assert_eq!("unindexedValue", found.get_str("encryptedUnindexed")?); - - // Find documents without decryption. - let unencrypted_coll = test_client - .database("docsExamples") - .collection::("encrypted"); - let found = unencrypted_coll - .find_one(doc! { "_id": 1 }, None) - .await? - .unwrap(); - assert_eq!( - Some(ElementType::Binary), - found.get("encryptedIndexed").map(Bson::element_type) - ); - assert_eq!( - Some(ElementType::Binary), - found.get("encryptedUnindexed").map(Bson::element_type) - ); - - Ok(()) + kms_providers } diff --git a/src/test/csfle/azure_imds.rs b/src/test/csfle/azure_imds.rs new file mode 100644 index 000000000..e0b8e41a8 --- /dev/null +++ b/src/test/csfle/azure_imds.rs @@ -0,0 +1,65 @@ +use std::time::Instant; + +use crate::{bson::rawdoc, client::csfle::state_machine::azure::ExecutorState}; + +use super::{Result, AZURE_IMDS_MOCK_PORT}; + +// Prose test 18. Azure IMDS Credentials +#[tokio::test] +async fn azure_imds() -> Result<()> { + let mut azure_exec = ExecutorState::new()?; + azure_exec.test_host = Some(("localhost", *AZURE_IMDS_MOCK_PORT)); + + // Case 1: Success + { + let now = Instant::now(); + let token = azure_exec.get_token().await?; + assert_eq!(token, rawdoc! { "accessToken": "magic-cookie" }); + let cached = azure_exec.take_cached().await.expect("cached token"); + assert_eq!(cached.server_response.expires_in, "70"); + assert_eq!(cached.server_response.resource, "https://vault.azure.net"); + assert!((65..75).contains(&cached.expire_time.duration_since(now).as_secs())); + } + + // Case 2: Empty JSON + { + azure_exec.test_param = Some("case=empty-json"); + let result = azure_exec.get_token().await; + assert!(result.is_err(), "expected err got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + } + + // Case 3: Bad JSON + { + azure_exec.test_param = Some("case=bad-json"); + let result = azure_exec.get_token().await; + assert!(result.is_err(), "expected err got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + } + + // Case 4: HTTP 404 + { + azure_exec.test_param = Some("case=404"); + let result = azure_exec.get_token().await; + assert!(result.is_err(), "expected err got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + } + + // Case 5: HTTP 500 + { + azure_exec.test_param = Some("case=500"); + let result = azure_exec.get_token().await; + assert!(result.is_err(), "expected err got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + } + + // Case 6: Slow Response + { + azure_exec.test_param = Some("case=slow"); + let result = azure_exec.get_token().await; + assert!(result.is_err(), "expected err got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + } + + Ok(()) +} diff --git a/src/test/csfle/kmip.rs b/src/test/csfle/kmip.rs new file mode 100644 index 000000000..a4231c54f --- /dev/null +++ b/src/test/csfle/kmip.rs @@ -0,0 +1,963 @@ +use std::{path::PathBuf, time::Duration}; + +use futures_util::TryStreamExt; +use mongocrypt::ctx::{Algorithm, KmsProvider, KmsProviderType}; + +use crate::{ + action::Action, + bson::{doc, spec::BinarySubtype, Binary, Bson, Document, RawBson}, + client_encryption::{ + AwsMasterKey, + AzureMasterKey, + ClientEncryption, + EncryptKey, + GcpMasterKey, + KmipMasterKey, + LocalMasterKey, + MasterKey, + }, + error::ErrorKind, + options::{Credential, TlsOptions}, + test::{get_client_options, util::Event}, + Client, +}; + +use super::{ + custom_endpoint_setup, + failure, + init_client, + load_testdata, + load_testdata_raw, + validate_roundtrip, + KmsInfo, + KmsProviderList, + Result, + CSFLE_TLS_CERT_DIR, + DISABLE_CRYPT_SHARED, + EXTRA_OPTIONS, + KV_NAMESPACE, + LOCAL_KMS, + UNNAMED_KMS_PROVIDERS, +}; + +const KMS_EXPIRED: &str = "127.0.0.1:9000"; +const KMS_WRONG_HOST: &str = "127.0.0.1:9001"; +const KMS_CORRECT: &str = "127.0.0.1:9002"; + +// Prose test 2. Data Key and Double Encryption +#[tokio::test] +async fn data_key_double_encryption() -> Result<()> { + fn ok_pred(mut f: impl FnMut(&Event) -> Result) -> impl FnMut(&Event) -> bool { + move |ev| f(ev).unwrap_or(false) + } + + // Setup: drop stale data. + let (client, _) = init_client().await?; + + // Setup: client with auto encryption. + let schema_map = [( + "db.coll", + doc! { + "bsonType": "object", + "properties": { + "encrypted_placeholder": { + "encrypt": { + "keyId": "/placeholder", + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + } + } + }, + )]; + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + UNNAMED_KMS_PROVIDERS.clone(), + )? + .schema_map(schema_map) + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + + // Setup: manual encryption. + let client_encryption = ClientEncryption::new( + client.clone().into_client(), + KV_NAMESPACE.clone(), + UNNAMED_KMS_PROVIDERS.clone(), + )?; + + // Testing each provider: + + let mut events = client.events.stream(); + let provider_keys: [(KmsProvider, MasterKey); 5] = [ + ( + KmsProvider::aws(), + AwsMasterKey::builder() + .region("us-east-1") + .key("arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0") + .build() + .into(), + ), + ( + KmsProvider::azure(), + AzureMasterKey::builder() + .key_vault_endpoint("key-vault-csfle.vault.azure.net") + .key_name("key-name-csfle") + .build() + .into(), + ), + ( + KmsProvider::gcp(), + GcpMasterKey::builder() + .project_id("devprod-drivers") + .location("global") + .key_ring("key-ring-csfle") + .key_name("key-name-csfle") + .build() + .into(), + ), + ( + KmsProvider::local(), + LocalMasterKey::builder().build().into(), + ), + (KmsProvider::kmip(), KmipMasterKey::builder().build().into()), + ]; + for (provider, master_key) in provider_keys { + // Create a data key + let datakey_id = client_encryption + .create_data_key(master_key) + .key_alt_names([format!("{}_altname", provider.as_string())]) + .await?; + assert_eq!(datakey_id.subtype, BinarySubtype::Uuid); + let docs: Vec<_> = client + .database("keyvault") + .collection::("datakeys") + .find(doc! { "_id": datakey_id.clone() }) + .await? + .try_collect() + .await?; + assert_eq!(docs.len(), 1); + assert_eq!( + docs[0].get_document("masterKey")?.get_str("provider")?, + provider.as_string() + ); + let found = events + .next_match( + Duration::from_millis(500), + ok_pred(|ev| { + let ev = match ev.as_command_started_event() { + Some(e) => e, + None => return Ok(false), + }; + if ev.command_name != "insert" { + return Ok(false); + } + let cmd = &ev.command; + if cmd.get_document("writeConcern")?.get_str("w")? != "majority" { + return Ok(false); + } + Ok(cmd.get_array("documents")?.iter().any(|doc| { + matches!( + doc.as_document().and_then(|d| d.get("_id")), + Some(Bson::Binary(id)) if id == &datakey_id + ) + })) + }), + ) + .await; + assert!(found.is_some(), "no valid event found"); + + // Manually encrypt a value and automatically decrypt it. + let encrypted = client_encryption + .encrypt( + format!("hello {}", provider.as_string()), + EncryptKey::Id(datakey_id), + Algorithm::Deterministic, + ) + .await?; + assert_eq!(encrypted.subtype, BinarySubtype::Encrypted); + let coll = client_encrypted + .database("db") + .collection::("coll"); + coll.insert_one(doc! { "_id": provider.as_string(), "value": encrypted.clone() }) + .await?; + let found = coll.find_one(doc! { "_id": provider.as_string() }).await?; + assert_eq!( + found.as_ref().and_then(|doc| doc.get("value")), + Some(&Bson::String(format!("hello {}", provider.as_string()))), + ); + + // Manually encrypt a value via key alt name. + let other_encrypted = client_encryption + .encrypt( + format!("hello {}", provider.as_string()), + EncryptKey::AltName(format!("{}_altname", provider.as_string())), + Algorithm::Deterministic, + ) + .await?; + assert_eq!(other_encrypted.subtype, BinarySubtype::Encrypted); + assert_eq!(other_encrypted.bytes, encrypted.bytes); + + // Attempt to auto-encrypt an already encrypted field. + let result = coll + .insert_one(doc! { "encrypted_placeholder": encrypted }) + .await; + let err = result.unwrap_err(); + assert!( + matches!(*err.kind, ErrorKind::Encryption(..)) || err.is_command_error(), + "unexpected error: {}", + err + ); + } + + Ok(()) +} + +// Prose test 3. External Key Vault Test +#[tokio::test] +async fn external_key_vault() -> Result<()> { + for with_external_key_vault in [false, true] { + // Setup: initialize db. + let (client, datakeys) = init_client().await?; + datakeys + .insert_one(load_testdata("external/external-key.json")?) + .await?; + + // Setup: test options. + let kv_client = if with_external_key_vault { + let mut opts = get_client_options().await.clone(); + opts.credential = Some( + Credential::builder() + .username("fake-user".to_string()) + .password("fake-pwd".to_string()) + .build(), + ); + Some(Client::with_options(opts)?) + } else { + None + }; + + // Setup: encrypted client. + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .key_vault_client(kv_client.clone()) + .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + // Setup: manual encryption. + let client_encryption = ClientEncryption::new( + kv_client.unwrap_or_else(|| client.into_client()), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + + // Test: encrypted client. + let result = client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "encrypted": "test" }) + .await; + if with_external_key_vault { + let err = result.unwrap_err(); + assert!(err.is_auth_error(), "unexpected error: {}", err); + } else { + assert!( + result.is_ok(), + "unexpected error: {}", + result.err().unwrap() + ); + } + // Test: manual encryption. + let result = client_encryption + .encrypt( + "test", + EncryptKey::Id(Binary::from_base64( + "LOCALAAAAAAAAAAAAAAAAA==", + BinarySubtype::Uuid, + )?), + Algorithm::Deterministic, + ) + .await; + if with_external_key_vault { + let err = result.unwrap_err(); + assert!(err.is_auth_error(), "unexpected error: {}", err); + } else { + assert!( + result.is_ok(), + "unexpected error: {}", + result.err().unwrap() + ); + } + } + + Ok(()) +} + +// Prose test 6. Corpus +mod corpus { + use super::*; + + async fn run_corpus_test(local_schema: bool) -> Result<()> { + // Setup: db initialization. + let (client, datakeys) = init_client().await?; + let schema = load_testdata("corpus/corpus-schema.json")?; + let validator = if local_schema { + None + } else { + Some(doc! { "$jsonSchema": schema.clone() }) + }; + client + .database("db") + .create_collection("coll") + .optional(validator, |b, v| b.validator(v)) + .await?; + for f in [ + "corpus/corpus-key-local.json", + "corpus/corpus-key-aws.json", + "corpus/corpus-key-azure.json", + "corpus/corpus-key-gcp.json", + "corpus/corpus-key-kmip.json", + ] { + datakeys.insert_one(load_testdata(f)?).await?; + } + + // Setup: encrypted client and manual encryption. + let client_encrypted = { + let mut enc_builder = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + UNNAMED_KMS_PROVIDERS.clone(), + )? + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED); + if local_schema { + enc_builder = enc_builder.schema_map([("db.coll", schema)]); + } + enc_builder.build().await? + }; + let client_encryption = ClientEncryption::new( + client.clone().into_client(), + KV_NAMESPACE.clone(), + UNNAMED_KMS_PROVIDERS.clone(), + )?; + + // Test: build corpus. + let corpus = load_corpus_nodecimal128("corpus/corpus.json")?; + let mut corpus_copied = doc! {}; + for (name, field) in &corpus { + // Copy simple fields + if [ + "_id", + "altname_aws", + "altname_local", + "altname_azure", + "altname_gcp", + "altname_kmip", + ] + .contains(&name.as_str()) + { + corpus_copied.insert(name, field); + continue; + } + // Encrypt `value` field in subdocuments. + let subdoc = match field.as_document() { + Some(d) => d, + None => { + return Err(failure!( + "unexpected field type for {:?}: {:?}", + name, + field.element_type() + )) + } + }; + let method = subdoc.get_str("method")?; + if method == "auto" { + corpus_copied.insert(name, subdoc); + continue; + } + if method != "explicit" { + return Err(failure!("Invalid method {:?}", method)); + } + let algo = match subdoc.get_str("algo")? { + "rand" => Algorithm::Random, + "det" => Algorithm::Deterministic, + s => return Err(failure!("Invalid algorithm {:?}", s)), + }; + let kms = KmsProvider::from_string(subdoc.get_str("kms")?); + let key = match subdoc.get_str("identifier")? { + "id" => EncryptKey::Id(Binary::from_base64( + match kms.provider_type() { + KmsProviderType::Local => "LOCALAAAAAAAAAAAAAAAAA==", + KmsProviderType::Aws => "AWSAAAAAAAAAAAAAAAAAAA==", + KmsProviderType::Azure => "AZUREAAAAAAAAAAAAAAAAA==", + KmsProviderType::Gcp => "GCPAAAAAAAAAAAAAAAAAAA==", + KmsProviderType::Kmip => "KMIPAAAAAAAAAAAAAAAAAA==", + _ => return Err(failure!("Invalid kms provider {:?}", kms)), + }, + BinarySubtype::Uuid, + )?), + "altname" => EncryptKey::AltName(kms.as_string()), + s => return Err(failure!("Invalid identifier {:?}", s)), + }; + let value: RawBson = subdoc + .get("value") + .expect("no value to encrypt") + .clone() + .try_into()?; + let result = client_encryption.encrypt(value, key, algo).await; + let mut subdoc_copied = subdoc.clone(); + if subdoc.get_bool("allowed")? { + subdoc_copied.insert("value", result?); + } else { + result.expect_err("expected encryption to be disallowed"); + } + corpus_copied.insert(name, subdoc_copied); + } + + // Test: insert into and find from collection, with automatic encryption. + let coll = client_encrypted + .database("db") + .collection::("coll"); + let id = coll.insert_one(corpus_copied).await?.inserted_id; + let corpus_decrypted = coll + .find_one(doc! { "_id": id.clone() }) + .await? + .expect("document lookup failed"); + assert_eq!(corpus, corpus_decrypted); + + // Test: validate encrypted form. + let corpus_encrypted_expected = load_corpus_nodecimal128("corpus/corpus-encrypted.json")?; + let corpus_encrypted_actual = client + .database("db") + .collection::("coll") + .find_one(doc! { "_id": id }) + .await? + .expect("encrypted document lookup failed"); + for (name, field) in &corpus_encrypted_expected { + let subdoc = match field.as_document() { + Some(d) => d, + None => continue, + }; + let value = subdoc.get("value").expect("no expected value"); + let actual_value = corpus_encrypted_actual + .get_document(name)? + .get("value") + .expect("no actual value"); + let algo = subdoc.get_str("algo")?; + if algo == "det" { + assert_eq!(value, actual_value); + } + let allowed = subdoc.get_bool("allowed")?; + if algo == "rand" && allowed { + assert_ne!(value, actual_value); + } + if allowed { + let bin = match value { + crate::bson::Bson::Binary(b) => b, + _ => { + return Err(failure!( + "expected value {:?} should be Binary, got {:?}", + name, + value + )) + } + }; + let actual_bin = match actual_value { + crate::bson::Bson::Binary(b) => b, + _ => { + return Err(failure!( + "actual value {:?} should be Binary, got {:?}", + name, + actual_value + )) + } + }; + let dec = client_encryption.decrypt(bin.as_raw_binary()).await?; + let actual_dec = client_encryption + .decrypt(actual_bin.as_raw_binary()) + .await?; + assert_eq!(dec, actual_dec); + } else { + assert_eq!(Some(value), corpus.get_document(name)?.get("value")); + } + } + + Ok(()) + } + + // TODO RUST-36: use the full corpus with decimal128. + fn load_corpus_nodecimal128(name: &str) -> Result { + let json: serde_json::Value = serde_json::from_str(&load_testdata_raw(name)?)?; + let mut new_obj = serde_json::Map::new(); + let decimal = serde_json::Value::String("decimal".to_string()); + for (name, value) in json.as_object().expect("expected object") { + if value["type"] == decimal { + continue; + } + new_obj.insert(name.clone(), value.clone()); + } + let bson: crate::bson::Bson = serde_json::Value::Object(new_obj).try_into()?; + match bson { + crate::bson::Bson::Document(d) => Ok(d), + _ => Err(failure!("expected document, got {:?}", bson)), + } + } + + #[tokio::test] + async fn coll_schema() -> Result<()> { + run_corpus_test(false).await?; + Ok(()) + } + + #[tokio::test] + async fn local_schema() -> Result<()> { + run_corpus_test(true).await?; + Ok(()) + } +} + +// Prose test 7. Custom Endpoint +mod custom_endpoint { + use super::*; + + // case 10 + #[tokio::test] + async fn kmip_no_endpoint() -> Result<()> { + let master_key = KmipMasterKey::builder() + .key_id(Some("1".to_string())) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption + .create_data_key(master_key.clone()) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + let client_encryption_invalid = custom_endpoint_setup(false).await?; + let result = client_encryption_invalid.create_data_key(master_key).await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 11 + #[tokio::test] + async fn kmip_valid_endpoint() -> Result<()> { + let master_key = KmipMasterKey::builder() + .key_id(Some("1".to_string())) + .endpoint(Some("localhost:5698".to_string())) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption.create_data_key(master_key).await?; + validate_roundtrip(&client_encryption, key_id).await + } + + // case 12 + #[tokio::test] + async fn kmip_invalid_endpoint() -> Result<()> { + let master_key = KmipMasterKey::builder() + .key_id(Some("1".to_string())) + .endpoint(Some("doesnotexist.local:5698".to_string())) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let result = client_encryption.create_data_key(master_key).await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } +} + +// Prose test 10. KMS TLS Tests +mod kms_tls { + use super::*; + + async fn run_kms_tls_test(endpoint: impl Into) -> crate::error::Result<()> { + // Setup + let kv_client = Client::for_test().await; + let client_encryption = ClientEncryption::new( + kv_client.clone().into_client(), + KV_NAMESPACE.clone(), + UNNAMED_KMS_PROVIDERS.clone(), + )?; + + // Test + client_encryption + .create_data_key( + AwsMasterKey::builder() + .region("us-east-1") + .key( + "arn:aws:kms:us-east-1:579766882180:key/\ + 89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + ) + .endpoint(Some(endpoint.into())) + .build(), + ) + .await + .map(|_| ()) + } + + #[tokio::test] + async fn invalid_certificate() { + let err = run_kms_tls_test(KMS_EXPIRED).await.unwrap_err(); + assert!( + err.to_string().contains("certificate verify failed"), + "unexpected error: {}", + err + ); + } + + #[tokio::test] + async fn invalid_hostname() { + let err = run_kms_tls_test(KMS_WRONG_HOST).await.unwrap_err(); + assert!( + err.to_string().contains("certificate verify failed"), + "unexpected error: {}", + err + ); + } +} + +// Prose test 11. KMS TLS Options Tests +#[tokio::test] +async fn kms_tls_options() -> Result<()> { + fn update_providers( + mut base_providers: KmsProviderList, + new_tls_options: TlsOptions, + mut update_credentials: impl FnMut(&KmsProvider, &mut Document), + ) -> KmsProviderList { + for (provider, credentials, tls_options) in base_providers.iter_mut() { + if provider != &KmsProvider::local() { + *tls_options = Some(new_tls_options.clone()); + } + update_credentials(provider, credentials); + } + base_providers + } + + fn add_name_to_info(kms_info: KmsInfo, name: &str) -> KmsInfo { + (kms_info.0.with_name(name), kms_info.1, kms_info.2) + } + + let cert_dir = PathBuf::from(&*CSFLE_TLS_CERT_DIR); + let ca_path = cert_dir.join("ca.pem"); + let key_path = cert_dir.join("client.pem"); + + let add_correct_credentials = + |provider: &KmsProvider, credentials: &mut Document| match provider.provider_type() { + KmsProviderType::Azure => { + credentials.insert("identityPlatformEndpoint", KMS_CORRECT); + } + KmsProviderType::Gcp => { + credentials.insert("endpoint", KMS_CORRECT); + } + _ => {} + }; + let add_expired_credentials = + |provider: &KmsProvider, credentials: &mut Document| match provider.provider_type() { + KmsProviderType::Azure => { + credentials.insert("identityPlatformEndpoint", KMS_EXPIRED); + } + KmsProviderType::Gcp | KmsProviderType::Kmip => { + credentials.insert("endpoint", KMS_EXPIRED); + } + _ => {} + }; + let add_wrong_host_credentials = + |provider: &KmsProvider, credentials: &mut Document| match provider.provider_type() { + KmsProviderType::Azure => { + credentials.insert("identityPlatformEndpoint", KMS_WRONG_HOST); + } + KmsProviderType::Gcp | KmsProviderType::Kmip => { + credentials.insert("endpoint", KMS_WRONG_HOST); + } + _ => {} + }; + + let providers_no_client_cert = update_providers( + UNNAMED_KMS_PROVIDERS.clone(), + TlsOptions::builder().ca_file_path(ca_path.clone()).build(), + add_correct_credentials, + ); + let client_encryption_no_client_cert = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + providers_no_client_cert.clone(), + )?; + + let providers_with_tls = update_providers( + UNNAMED_KMS_PROVIDERS.clone(), + TlsOptions::builder() + .ca_file_path(ca_path.clone()) + .cert_key_file_path(key_path.clone()) + .build(), + add_correct_credentials, + ); + let client_encryption_with_tls = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + providers_with_tls.clone(), + )?; + + let client_encryption_expired = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + update_providers( + UNNAMED_KMS_PROVIDERS.clone(), + TlsOptions::builder().ca_file_path(ca_path.clone()).build(), + add_expired_credentials, + ), + )?; + + let client_encryption_invalid_hostname = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + update_providers( + UNNAMED_KMS_PROVIDERS.clone(), + TlsOptions::builder().ca_file_path(ca_path.clone()).build(), + add_wrong_host_credentials, + ), + )?; + + let mut named_providers = providers_no_client_cert + .into_iter() + .filter_map(|info| { + if !matches!(info.0.provider_type(), KmsProviderType::Local) { + Some(add_name_to_info(info, "no_client_cert")) + } else { + None + } + }) + .collect::>(); + named_providers.extend(providers_with_tls.into_iter().filter_map(|info| { + if !matches!(info.0.provider_type(), KmsProviderType::Local) { + Some(add_name_to_info(info, "with_tls")) + } else { + None + } + })); + let client_encryption_with_names = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + named_providers, + )?; + + async fn provider_test( + client_encryption: &ClientEncryption, + master_key: impl Into, + expected_errs: &[&str], + ) -> Result<()> { + let err = client_encryption + .create_data_key(master_key) + .await + .unwrap_err(); + let err_str = err.to_string(); + if !expected_errs.iter().any(|s| err_str.contains(s)) { + Err(err)? + } + Ok(()) + } + + // Case 1: AWS + fn aws_key(endpoint: impl Into) -> AwsMasterKey { + AwsMasterKey::builder() + .region("us-east-1") + .key("arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0") + .endpoint(Some(endpoint.into())) + .build() + } + + provider_test( + &client_encryption_no_client_cert, + aws_key(KMS_CORRECT), + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + provider_test( + &client_encryption_with_tls, + aws_key(KMS_CORRECT), + &["parse error"], + ) + .await?; + provider_test( + &client_encryption_expired, + aws_key(KMS_EXPIRED), + &["certificate verify failed"], + ) + .await?; + provider_test( + &client_encryption_invalid_hostname, + aws_key(KMS_WRONG_HOST), + &["certificate verify failed"], + ) + .await?; + + // Case 2: Azure + let azure_key = AzureMasterKey::builder() + .key_vault_endpoint("doesnotexist.local") + .key_name("foo") + .build(); + + provider_test( + &client_encryption_no_client_cert, + azure_key.clone(), + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + provider_test( + &client_encryption_with_tls, + azure_key.clone(), + &["HTTP status=404"], + ) + .await?; + provider_test( + &client_encryption_expired, + azure_key.clone(), + &["certificate verify failed"], + ) + .await?; + provider_test( + &client_encryption_invalid_hostname, + azure_key.clone(), + &["certificate verify failed"], + ) + .await?; + + // Case 3: GCP + let gcp_key = GcpMasterKey::builder() + .project_id("foo") + .location("bar") + .key_ring("baz") + .key_name("foo") + .build(); + + provider_test( + &client_encryption_no_client_cert, + gcp_key.clone(), + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + provider_test( + &client_encryption_with_tls, + gcp_key.clone(), + &["HTTP status=404"], + ) + .await?; + provider_test( + &client_encryption_expired, + gcp_key.clone(), + &["certificate verify failed"], + ) + .await?; + provider_test( + &client_encryption_invalid_hostname, + gcp_key.clone(), + &["certificate verify failed"], + ) + .await?; + + // Case 4: KMIP + let kmip_key = KmipMasterKey::builder().build(); + + provider_test( + &client_encryption_no_client_cert, + kmip_key.clone(), + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + // This one succeeds! + client_encryption_with_tls + .create_data_key(kmip_key.clone()) + .await?; + provider_test( + &client_encryption_expired, + kmip_key.clone(), + &["certificate verify failed"], + ) + .await?; + provider_test( + &client_encryption_invalid_hostname, + kmip_key.clone(), + &["certificate verify failed"], + ) + .await?; + + // Case 6: named KMS providers apply TLS options + // Named AWS + let mut master_key = aws_key("127.0.0.1:9002"); + master_key.name = Some("no_client_cert".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + + let mut master_key = aws_key("127.0.0.1:9002"); + master_key.name = Some("with_tls".to_string()); + provider_test(&client_encryption_with_names, master_key, &["parse error"]).await?; + + // Named Azure + let mut master_key = azure_key.clone(); + master_key.name = Some("no_client_cert".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + + let mut master_key = azure_key.clone(); + master_key.name = Some("with_tls".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["HTTP status=404"], + ) + .await?; + + // Named GCP + let mut master_key = gcp_key.clone(); + master_key.name = Some("no_client_cert".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + + let mut master_key = gcp_key.clone(); + master_key.name = Some("with_tls".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["HTTP status=404"], + ) + .await?; + + // Named KMIP + let mut master_key = kmip_key.clone(); + master_key.name = Some("no_client_cert".to_string()); + provider_test( + &client_encryption_with_names, + master_key, + &["SSL routines", "connection was forcibly closed"], + ) + .await?; + + let mut master_key = kmip_key.clone(); + master_key.name = Some("with_tls".to_string()); + client_encryption_with_names + .create_data_key(master_key) + .await?; + + Ok(()) +} diff --git a/src/test/csfle/kms_retry.rs b/src/test/csfle/kms_retry.rs new file mode 100644 index 000000000..40b57b73a --- /dev/null +++ b/src/test/csfle/kms_retry.rs @@ -0,0 +1,171 @@ +//! Prose Test 24. KMS Retry Tests + +use std::path::PathBuf; + +use mongocrypt::ctx::Algorithm; +use reqwest::{Certificate, Client as HttpClient}; + +use crate::{ + client_encryption::{AwsMasterKey, AzureMasterKey, ClientEncryption, GcpMasterKey}, + test::get_client_options, + Client, + Namespace, +}; + +use super::{AWS_KMS, AZURE_KMS, CSFLE_TLS_CERT_DIR, GCP_KMS}; + +#[tokio::test] +async fn kms_retry() { + let endpoint = "127.0.0.1:9003"; + + let mut certificate_file_path = PathBuf::from(&*CSFLE_TLS_CERT_DIR); + certificate_file_path.push("ca.pem"); + let certificate_file = std::fs::read(&certificate_file_path).unwrap(); + + let set_failpoint = |kind: &str, count: u8| { + // create a fresh client for each request to avoid hangs + let http_client = HttpClient::builder() + .add_root_certificate(Certificate::from_pem(&certificate_file).unwrap()) + .build() + .unwrap(); + let url = format!("https://localhost:9003/set_failpoint/{}", kind); + let body = format!("{{\"count\":{}}}", count); + http_client.post(url).body(body).send() + }; + + let aws_kms = AWS_KMS.clone(); + let mut azure_kms = AZURE_KMS.clone(); + azure_kms.1.insert("identityPlatformEndpoint", endpoint); + let mut gcp_kms = GCP_KMS.clone(); + gcp_kms.1.insert("endpoint", endpoint); + let mut kms_providers = vec![aws_kms, azure_kms, gcp_kms]; + + let tls_options = get_client_options().await.tls_options(); + for kms_provider in kms_providers.iter_mut() { + kms_provider.2 = tls_options.clone(); + } + + let key_vault_client = Client::for_test().await.into_client(); + let client_encryption = ClientEncryption::new( + key_vault_client, + Namespace::new("keyvault", "datakeys"), + kms_providers, + ) + .unwrap(); + + let aws_master_key = AwsMasterKey::builder() + .region("foo") + .key("bar") + .endpoint(endpoint.to_string()) + .build(); + let azure_master_key = AzureMasterKey::builder() + .key_vault_endpoint(endpoint) + .key_name("foo") + .build(); + let gcp_master_key = GcpMasterKey::builder() + .project_id("foo") + .location("bar") + .key_ring("baz") + .key_name("qux") + .endpoint(endpoint.to_string()) + .build(); + + // Case 1: createDataKey and encrypt with TCP retry + + // AWS + set_failpoint("network", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(aws_master_key.clone()) + .await + .unwrap(); + set_failpoint("network", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // Azure + set_failpoint("network", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(azure_master_key.clone()) + .await + .unwrap(); + set_failpoint("network", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // GCP + set_failpoint("network", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(gcp_master_key.clone()) + .await + .unwrap(); + set_failpoint("network", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // Case 2: createDataKey and encrypt with HTTP retry + + // AWS + set_failpoint("http", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(aws_master_key.clone()) + .await + .unwrap(); + set_failpoint("http", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // Azure + set_failpoint("http", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(azure_master_key.clone()) + .await + .unwrap(); + set_failpoint("http", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // GCP + set_failpoint("http", 1).await.unwrap(); + let key_id = client_encryption + .create_data_key(gcp_master_key.clone()) + .await + .unwrap(); + set_failpoint("http", 1).await.unwrap(); + client_encryption + .encrypt(123, key_id, Algorithm::Deterministic) + .await + .unwrap(); + + // Case 3: createDataKey fails after too many retries + + // AWS + set_failpoint("network", 4).await.unwrap(); + client_encryption + .create_data_key(aws_master_key) + .await + .unwrap_err(); + + // Azure + set_failpoint("network", 4).await.unwrap(); + client_encryption + .create_data_key(azure_master_key) + .await + .unwrap_err(); + + // GCP + set_failpoint("network", 4).await.unwrap(); + client_encryption + .create_data_key(gcp_master_key) + .await + .unwrap_err(); +} diff --git a/src/test/csfle/on_demand_aws.rs b/src/test/csfle/on_demand_aws.rs new file mode 100644 index 000000000..4641fb6ac --- /dev/null +++ b/src/test/csfle/on_demand_aws.rs @@ -0,0 +1,42 @@ +//! Prose test 15. On-demand AWS Credentials + +use mongocrypt::ctx::KmsProvider; + +use crate::{ + bson::doc, + client_encryption::{AwsMasterKey, ClientEncryption}, + error::Result, + Client, +}; + +use super::KV_NAMESPACE; + +async fn try_create_data_key() -> Result<()> { + let ce = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + [(KmsProvider::aws(), doc! {}, None)], + )?; + ce.create_data_key( + AwsMasterKey::builder() + .region("us-east-1") + .key("arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0") + .build(), + ) + .await + .map(|_| ()) +} + +#[tokio::test] +async fn success() { + assert!(std::env::var("AWS_ACCESS_KEY_ID").is_ok()); + assert!(std::env::var("AWS_SECRET_ACCESS_KEY").is_ok()); + try_create_data_key().await.unwrap(); +} + +#[tokio::test] +async fn failure() { + assert!(std::env::var("AWS_ACCESS_KEY_ID").is_err()); + assert!(std::env::var("AWS_SECRET_ACCESS_KEY").is_err()); + try_create_data_key().await.unwrap_err(); +} diff --git a/src/test/csfle/on_demand_gcp.rs b/src/test/csfle/on_demand_gcp.rs new file mode 100644 index 000000000..cbbea9552 --- /dev/null +++ b/src/test/csfle/on_demand_gcp.rs @@ -0,0 +1,50 @@ +//! Prose test 17. On-demand GCP Credentials + +use mongocrypt::ctx::KmsProvider; + +use crate::{ + bson::doc, + client_encryption::{ClientEncryption, GcpMasterKey}, + error::{ErrorKind, Result}, + Client, +}; + +use super::KV_NAMESPACE; + +async fn try_create_data_key() -> Result<()> { + let util_client = Client::for_test().await.into_client(); + let client_encryption = ClientEncryption::new( + util_client, + KV_NAMESPACE.clone(), + [(KmsProvider::gcp(), doc! {}, None)], + )?; + + client_encryption + .create_data_key( + GcpMasterKey::builder() + .project_id("devprod-drivers") + .location("global") + .key_ring("key-ring-csfle") + .key_name("key-name-csfle") + .build(), + ) + .await + .map(|_| ()) +} + +#[tokio::test] +async fn success_skip_ci() { + try_create_data_key().await.unwrap(); +} + +#[tokio::test] +async fn failure() { + let error = try_create_data_key().await.unwrap_err(); + match *error.kind { + ErrorKind::Encryption(e) => { + assert!(matches!(e.kind, mongocrypt::error::ErrorKind::Kms)); + assert!(e.message.unwrap().contains("GCP credentials")); + } + other => panic!("Expected encryption error, got {:?}", other), + } +} diff --git a/src/test/csfle/prose.rs b/src/test/csfle/prose.rs new file mode 100644 index 000000000..693199878 --- /dev/null +++ b/src/test/csfle/prose.rs @@ -0,0 +1,2247 @@ +use std::{ + collections::BTreeMap, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + Mutex, + }, + time::Duration, +}; + +use futures_util::TryStreamExt; +use mongocrypt::ctx::Algorithm; +use tokio::net::TcpListener; + +use crate::{ + bson::{ + doc, + rawdoc, + spec::ElementType, + Binary, + Bson, + DateTime, + Document, + RawBson, + RawDocumentBuf, + }, + client_encryption::{ + AwsMasterKey, + AzureMasterKey, + ClientEncryption, + EncryptKey, + GcpMasterKey, + LocalMasterKey, + MasterKey, + RangeOptions, + }, + error::{ErrorKind, WriteError, WriteFailure}, + event::{ + command::{CommandFailedEvent, CommandStartedEvent, CommandSucceededEvent}, + sdam::SdamEvent, + }, + options::{EncryptOptions, FindOptions, IndexOptions, WriteConcern}, + runtime, + test::{ + get_client_options, + log_uncaptured, + server_version_lt, + topology_is_standalone, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, + Event, + TestClient, + }, + Client, + Collection, + IndexModel, +}; + +use super::{ + custom_endpoint_setup, + failure, + fle2v2_ok, + init_client, + load_testdata, + validate_roundtrip, + Result, + AWS_KMS, + DISABLE_CRYPT_SHARED, + EXTRA_OPTIONS, + KV_NAMESPACE, + LOCAL_KMS, +}; + +// Prose test 1. Custom Key Material Test +#[tokio::test] +async fn custom_key_material() -> Result<()> { + let (client, datakeys) = init_client().await?; + let enc = ClientEncryption::new( + client.into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + + let key = base64::decode( + "xPTAjBRG5JiPm+d3fj6XLi2q5DMXUS/f1f+SMAlhhwkhDRL0kr8r9GDLIGTAGlvC+HVjSIgdL+RKw\ + ZCvpXSyxTICWSXTUYsWYPyu3IoHbuBZdmw2faM3WhcRIgbMReU5", + ) + .unwrap(); + let id = enc + .create_data_key(LocalMasterKey::builder().build()) + .key_material(key) + .await?; + let mut key_doc = datakeys + .find_one(doc! { "_id": id.clone() }) + .await? + .unwrap(); + datakeys.delete_one(doc! { "_id": id}).await?; + let new_key_id = crate::bson::Binary::from_uuid(crate::bson::Uuid::from_bytes([0; 16])); + key_doc.insert("_id", new_key_id.clone()); + datakeys.insert_one(key_doc).await?; + + let encrypted = enc + .encrypt("test", EncryptKey::Id(new_key_id), Algorithm::Deterministic) + .await?; + let expected = base64::decode( + "AQAAAAAAAAAAAAAAAAAAAAACz0ZOLuuhEYi807ZXTdhbqhLaS2/t9wLifJnnNYwiw79d75QYIZ6M/\ + aYC1h9nCzCjZ7pGUpAuNnkUhnIXM3PjrA==", + ) + .unwrap(); + assert_eq!(encrypted.bytes, expected); + + Ok(()) +} + +// Prose test 4. BSON Size Limits and Batch Splitting +#[tokio::test] +async fn bson_size_limits() -> Result<()> { + // Setup: db initialization. + let (client, datakeys) = init_client().await?; + client + .database("db") + .create_collection("coll") + .validator(doc! { "$jsonSchema": load_testdata("limits/limits-schema.json")? }) + .await?; + datakeys + .insert_one(load_testdata("limits/limits-key.json")?) + .await?; + + // Setup: encrypted client. + let mut opts = get_client_options().await.clone(); + let buffer = EventBuffer::::new(); + + opts.command_event_handler = Some(buffer.handler()); + let client_encrypted = + Client::encrypted_builder(opts, KV_NAMESPACE.clone(), vec![LOCAL_KMS.clone()])? + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + let coll = client_encrypted + .database("db") + .collection::("coll"); + + // Tests + // Test operation 1 + coll.insert_one(doc! { + "_id": "over_2mib_under_16mib", + "unencrypted": "a".repeat(2097152), + }) + .await?; + + // Test operation 2 + let mut doc: Document = load_testdata("limits/limits-doc.json")?; + doc.insert("_id", "encryption_exceeds_2mib"); + doc.insert("unencrypted", "a".repeat(2_097_152 - 2_000)); + coll.insert_one(doc).await?; + + // Test operation 3 + let value = "a".repeat(2_097_152); + let mut events = buffer.stream(); + coll.insert_many(vec![ + doc! { + "_id": "over_2mib_1", + "unencrypted": value.clone(), + }, + doc! { + "_id": "over_2mib_2", + "unencrypted": value, + }, + ]) + .await?; + let inserts = events + .collect(Duration::from_millis(500), |ev| { + let ev = match ev.as_command_started_event() { + Some(e) => e, + None => return false, + }; + ev.command_name == "insert" + }) + .await; + assert_eq!(2, inserts.len()); + + // Test operation 4 + let mut doc = load_testdata("limits/limits-doc.json")?; + doc.insert("_id", "encryption_exceeds_2mib_1"); + doc.insert("unencrypted", "a".repeat(2_097_152 - 2_000)); + let mut doc2 = doc.clone(); + doc2.insert("_id", "encryption_exceeds_2mib_2"); + let mut events = buffer.stream(); + coll.insert_many(vec![doc, doc2]).await?; + let inserts = events + .collect(Duration::from_millis(500), |ev| { + let ev = match ev.as_command_started_event() { + Some(e) => e, + None => return false, + }; + ev.command_name == "insert" + }) + .await; + assert_eq!(2, inserts.len()); + + // Test operation 5 + let doc = doc! { + "_id": "under_16mib", + "unencrypted": "a".repeat(16_777_216 - 2_000), + }; + coll.insert_one(doc).await?; + + // Test operation 6 + let mut doc: Document = load_testdata("limits/limits-doc.json")?; + doc.insert("_id", "encryption_exceeds_16mib"); + doc.insert("unencrypted", "a".repeat(16_777_216 - 2_000)); + let result = coll.insert_one(doc).await; + let err = result.unwrap_err(); + assert!( + matches!(*err.kind, ErrorKind::Write(_)), + "unexpected error: {}", + err + ); + + Ok(()) +} + +// Prose test 5. Views Are Prohibited +#[tokio::test] +async fn views_prohibited() -> Result<()> { + // Setup: db initialization. + let (client, _) = init_client().await?; + client + .database("db") + .collection::("view") + .drop() + .await?; + client + .database("db") + .create_collection("view") + .view_on("coll".to_string()) + .await?; + + // Setup: encrypted client. + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + + // Test: auto encryption fails on a view + let result = client_encrypted + .database("db") + .collection::("view") + .insert_one(doc! {}) + .await; + let err = result.unwrap_err(); + assert!( + err.to_string().contains("cannot auto encrypt a view"), + "unexpected error: {}", + err + ); + + Ok(()) +} + +// Prose test 7. Custom Endpoint +mod custom_endpoint { + use crate::client_encryption::KmipMasterKey; + + use super::*; + + async fn custom_endpoint_aws_ok(endpoint: Option) -> Result<()> { + let client_encryption = custom_endpoint_setup(true).await?; + + let key_id = client_encryption + .create_data_key( + AwsMasterKey::builder() + .region("us-east-1") + .key( + "arn:aws:kms:us-east-1:579766882180:key/\ + 89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + ) + .endpoint(endpoint) + .build(), + ) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + Ok(()) + } + + // case 1 + #[tokio::test] + async fn aws_no_endpoint() -> Result<()> { + custom_endpoint_aws_ok(None).await + } + + // case 2 + #[tokio::test] + async fn aws_no_port() -> Result<()> { + custom_endpoint_aws_ok(Some("kms.us-east-1.amazonaws.com".to_string())).await + } + + // case 3 + #[tokio::test] + async fn aws_with_port() -> Result<()> { + custom_endpoint_aws_ok(Some("kms.us-east-1.amazonaws.com:443".to_string())).await + } + + // case 4 + #[tokio::test] + async fn kmip_invalid_port() -> Result<()> { + let client_encryption = custom_endpoint_setup(true).await?; + + let result = client_encryption + .create_data_key( + KmipMasterKey::builder() + .key_id("1".to_owned()) + .endpoint("localhost:12345".to_owned()) + .build(), + ) + .await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 5 + #[tokio::test] + async fn aws_invalid_region() -> Result<()> { + let client_encryption = custom_endpoint_setup(true).await?; + + let result = client_encryption + .create_data_key( + AwsMasterKey::builder() + .region("us-east-1") + .key( + "arn:aws:kms:us-east-1:579766882180:key/\ + 89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + ) + .endpoint(Some("kms.us-east-2.amazonaws.com".to_string())) + .build(), + ) + .await; + assert!(result.unwrap_err().is_csfle_error()); + + Ok(()) + } + + // case 6 + #[tokio::test] + async fn aws_invalid_domain() -> Result<()> { + let client_encryption = custom_endpoint_setup(true).await?; + + let result = client_encryption + .create_data_key( + AwsMasterKey::builder() + .region("us-east-1") + .key( + "arn:aws:kms:us-east-1:579766882180:key/\ + 89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + ) + .endpoint(Some("doesnotexist.invalid".to_string())) + .build(), + ) + .await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 7 + #[tokio::test] + async fn azure() -> Result<()> { + let master_key = AzureMasterKey::builder() + .key_vault_endpoint("key-vault-csfle.vault.azure.net") + .key_name("key-name-csfle") + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption + .create_data_key(master_key.clone()) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + let client_encryption_invalid = custom_endpoint_setup(false).await?; + let result = client_encryption_invalid.create_data_key(master_key).await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 8 + #[tokio::test] + async fn gcp_valid() -> Result<()> { + let master_key = GcpMasterKey::builder() + .project_id("devprod-drivers") + .location("global") + .key_ring("key-ring-csfle") + .key_name("key-name-csfle") + .endpoint(Some("cloudkms.googleapis.com:443".to_string())) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption + .create_data_key(master_key.clone()) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + let client_encryption_invalid = custom_endpoint_setup(false).await?; + let result = client_encryption_invalid.create_data_key(master_key).await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 9 + #[tokio::test] + async fn gcp_invalid() -> Result<()> { + let master_key = GcpMasterKey::builder() + .project_id("devprod-drivers") + .location("global") + .key_ring("key-ring-csfle") + .key_name("key-name-csfle") + .endpoint(Some("doesnotexist.invalid:443".to_string())) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let result = client_encryption.create_data_key(master_key).await; + let err = result.unwrap_err(); + assert!(err.is_csfle_error()); + assert!( + err.to_string().contains("Invalid KMS response"), + "unexpected error: {}", + err + ); + + Ok(()) + } + + // case 10 + #[cfg(feature = "openssl-tls")] + #[tokio::test] + async fn kmip_valid() -> Result<()> { + let master_key = KmipMasterKey::builder().key_id("1".to_owned()).build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption + .create_data_key(master_key.clone()) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + let client_encryption_invalid = custom_endpoint_setup(false).await?; + let result = client_encryption_invalid.create_data_key(master_key).await; + assert!(result.unwrap_err().is_network_error()); + + Ok(()) + } + + // case 11 + #[cfg(feature = "openssl-tls")] + #[tokio::test] + async fn kmip_valid_endpoint() -> Result<()> { + let master_key = KmipMasterKey::builder() + .key_id("1".to_owned()) + .endpoint("localhost:5698".to_owned()) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let key_id = client_encryption + .create_data_key(master_key.clone()) + .await?; + validate_roundtrip(&client_encryption, key_id).await?; + + Ok(()) + } + + // case 12 + #[tokio::test] + async fn kmip_invalid() -> Result<()> { + let master_key = KmipMasterKey::builder() + .key_id("1".to_owned()) + .endpoint("doesnotexist.invalid:5698".to_owned()) + .build(); + + let client_encryption = custom_endpoint_setup(true).await?; + let result = client_encryption.create_data_key(master_key).await; + let err = result.unwrap_err(); + assert!(err.is_network_error()); + + Ok(()) + } +} + +// Prose test 8. Bypass Spawning mongocryptd +mod bypass_spawning_mongocryptd { + use super::*; + + async fn bypass_mongocryptd_unencrypted_insert(bypass: Bypass) -> Result<()> { + // Setup: encrypted client. + let extra_options = doc! { + "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], + }; + let builder = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .extra_options(extra_options) + .disable_crypt_shared(true); + let builder = match bypass { + Bypass::AutoEncryption => builder.bypass_auto_encryption(true), + Bypass::QueryAnalysis => builder.bypass_query_analysis(true), + }; + let client_encrypted = builder.build().await?; + + // Test: insert succeeds. + client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "unencrypted": "test" }) + .await?; + // Test: mongocryptd not spawned. + assert!(!client_encrypted.mongocryptd_spawned().await); + // Test: attempting to connect fails. + let client = + Client::with_uri_str("mongodb://localhost:27021/?serverSelectionTimeoutMS=1000") + .await?; + let result = client.list_database_names().await; + assert!(result.unwrap_err().is_server_selection_error()); + + Ok(()) + } + + enum Bypass { + AutoEncryption, + QueryAnalysis, + } + + #[tokio::test] + async fn shared_library() -> Result<()> { + if *DISABLE_CRYPT_SHARED { + log_uncaptured( + "Skipping bypass mongocryptd via shared library test: crypt_shared is disabled.", + ); + return Ok(()); + } + + // Setup: encrypted client. + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) + .extra_options(doc! { + "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", + "mongocryptdSpawnArgs": ["--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], + "cryptSharedLibPath": EXTRA_OPTIONS.get("cryptSharedLibPath").unwrap(), + "cryptSharedRequired": true, + }) + .build() + .await?; + + // Test: insert succeeds. + client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "unencrypted": "test" }) + .await?; + // Test: mongocryptd not spawned. + assert!(!client_encrypted.mongocryptd_spawned().await); + // Test: attempting to connect fails. + let client = + Client::with_uri_str("mongodb://localhost:27021/?serverSelectionTimeoutMS=1000") + .await?; + let result = client.list_database_names().await; + assert!(result.unwrap_err().is_server_selection_error()); + + Ok(()) + } + + #[tokio::test] + async fn bypass_spawn() -> Result<()> { + // Setup: encrypted client. + let extra_options = doc! { + "mongocryptdBypassSpawn": true, + "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", + "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], + }; + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .schema_map([("db.coll", load_testdata("external/external-schema.json")?)]) + .extra_options(extra_options) + .disable_crypt_shared(true) + .build() + .await?; + + // Test: insert fails. + let err = client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "encrypted": "test" }) + .await + .unwrap_err(); + assert!(err.is_server_selection_error(), "unexpected error: {}", err); + + Ok(()) + } + + #[tokio::test] + async fn auto_encryption() -> Result<()> { + bypass_mongocryptd_unencrypted_insert(Bypass::AutoEncryption).await + } + + #[tokio::test] + async fn bypass_query_analysis() -> Result<()> { + bypass_mongocryptd_unencrypted_insert(Bypass::QueryAnalysis).await + } +} + +// Prose test 9. Deadlock Tests +mod deadlock { + use super::*; + + struct DeadlockTestCase { + max_pool_size: u32, + bypass_auto_encryption: bool, + set_key_vault_client: bool, + expected_encrypted_commands: Vec, + expected_keyvault_commands: Vec, + expected_number_of_clients: usize, + } + + impl DeadlockTestCase { + async fn run(&self) -> Result<()> { + // Setup + let client_test = Client::for_test().await; + let client_keyvault = Client::for_test() + .options({ + let mut opts = get_client_options().await.clone(); + opts.max_pool_size = Some(1); + opts + }) + .monitor_events() + .await; + + let mut keyvault_events = client_keyvault.events.stream(); + client_test + .database("keyvault") + .collection::("datakeys") + .drop() + .await?; + client_test + .database("db") + .collection::("coll") + .drop() + .await?; + client_keyvault + .database("keyvault") + .collection::("datakeys") + .insert_one(load_testdata("external/external-key.json")?) + .write_concern(WriteConcern::majority()) + .await?; + client_test + .database("db") + .create_collection("coll") + .validator(doc! { "$jsonSchema": load_testdata("external/external-schema.json")? }) + .await?; + let client_encryption = ClientEncryption::new( + client_test.clone().into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let ciphertext = client_encryption + .encrypt( + RawBson::String("string0".to_string()), + EncryptKey::AltName("local".to_string()), + Algorithm::Deterministic, + ) + .await?; + + // Run test case + let event_buffer = EventBuffer::new(); + + let mut encrypted_events = event_buffer.stream(); + let mut opts = get_client_options().await.clone(); + opts.max_pool_size = Some(self.max_pool_size); + opts.command_event_handler = Some(event_buffer.handler()); + opts.sdam_event_handler = Some(event_buffer.handler()); + let client_encrypted = + Client::encrypted_builder(opts, KV_NAMESPACE.clone(), vec![LOCAL_KMS.clone()])? + .bypass_auto_encryption(self.bypass_auto_encryption) + .key_vault_client( + if self.set_key_vault_client { + Some(client_keyvault.clone().into_client()) + } else { + None + }, + ) + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + + if self.bypass_auto_encryption { + client_test + .database("db") + .collection::("coll") + .insert_one(doc! { "_id": 0, "encrypted": ciphertext }) + .await?; + } else { + client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "_id": 0, "encrypted": "string0" }) + .await?; + } + + let found = client_encrypted + .database("db") + .collection::("coll") + .find_one(doc! { "_id": 0 }) + .await?; + assert_eq!(found, Some(doc! { "_id": 0, "encrypted": "string0" })); + + let encrypted_events = encrypted_events + .collect(Duration::from_millis(500), |_| true) + .await; + let client_count = encrypted_events + .iter() + .filter(|ev| matches!(ev, Event::Sdam(SdamEvent::TopologyOpening(_)))) + .count(); + assert_eq!(self.expected_number_of_clients, client_count); + + let encrypted_commands: Vec<_> = encrypted_events + .into_iter() + .filter_map(|ev| ev.into_command_started_event()) + .collect(); + for expected in &self.expected_encrypted_commands { + expected.assert_matches_any("encrypted", &encrypted_commands); + } + + let keyvault_commands = keyvault_events + .collect_map(Duration::from_millis(500), |ev| { + ev.into_command_started_event() + }) + .await; + for expected in &self.expected_keyvault_commands { + expected.assert_matches_any("keyvault", &keyvault_commands); + } + + Ok(()) + } + } + + #[derive(Debug)] + struct DeadlockExpectation { + command: &'static str, + db: &'static str, + } + + impl DeadlockExpectation { + fn matches(&self, ev: &CommandStartedEvent) -> bool { + ev.command_name == self.command && ev.db == self.db + } + + fn assert_matches_any(&self, name: &str, commands: &[CommandStartedEvent]) { + for actual in commands { + if self.matches(actual) { + return; + } + } + panic!( + "No {} command matching {:?} found, events=\n{:?}", + name, self, commands + ); + } + } + + #[tokio::test] + async fn deadlock() -> Result<()> { + // Case 1 + DeadlockTestCase { + max_pool_size: 1, + bypass_auto_encryption: false, + set_key_vault_client: false, + expected_encrypted_commands: vec![ + DeadlockExpectation { + command: "listCollections", + db: "db", + }, + DeadlockExpectation { + command: "find", + db: "keyvault", + }, + DeadlockExpectation { + command: "insert", + db: "db", + }, + DeadlockExpectation { + command: "find", + db: "db", + }, + ], + expected_keyvault_commands: vec![], + expected_number_of_clients: 2, + } + .run() + .await?; + // Case 2 + DeadlockTestCase { + max_pool_size: 1, + bypass_auto_encryption: false, + set_key_vault_client: true, + expected_encrypted_commands: vec![ + DeadlockExpectation { + command: "listCollections", + db: "db", + }, + DeadlockExpectation { + command: "insert", + db: "db", + }, + DeadlockExpectation { + command: "find", + db: "db", + }, + ], + expected_keyvault_commands: vec![DeadlockExpectation { + command: "find", + db: "keyvault", + }], + expected_number_of_clients: 2, + } + .run() + .await?; + // Case 3 + DeadlockTestCase { + max_pool_size: 1, + bypass_auto_encryption: true, + set_key_vault_client: false, + expected_encrypted_commands: vec![ + DeadlockExpectation { + command: "find", + db: "db", + }, + DeadlockExpectation { + command: "find", + db: "keyvault", + }, + ], + expected_keyvault_commands: vec![], + expected_number_of_clients: 2, + } + .run() + .await?; + // Case 4 + DeadlockTestCase { + max_pool_size: 1, + bypass_auto_encryption: true, + set_key_vault_client: true, + expected_encrypted_commands: vec![DeadlockExpectation { + command: "find", + db: "db", + }], + expected_keyvault_commands: vec![DeadlockExpectation { + command: "find", + db: "keyvault", + }], + expected_number_of_clients: 1, + } + .run() + .await?; + // Case 5: skipped (unlimited max_pool_size not supported) + // Case 6: skipped (unlimited max_pool_size not supported) + // Case 7: skipped (unlimited max_pool_size not supported) + // Case 8: skipped (unlimited max_pool_size not supported) + + Ok(()) + } +} + +// Prose test 12. Explicit Encryption +mod explicit_encryption { + use super::*; + + struct ExplicitEncryptionTestData { + key1_id: Binary, + client_encryption: ClientEncryption, + encrypted_client: Client, + } + + async fn explicit_encryption_setup() -> Result> { + if server_version_lt(6, 0).await { + log_uncaptured("skipping explicit encryption test: server below 6.0"); + return Ok(None); + } + if topology_is_standalone().await { + log_uncaptured("skipping explicit encryption test: cannot run on standalone"); + return Ok(None); + } + + let key_vault_client = Client::for_test().await; + + let encrypted_fields = load_testdata("data/encryptedFields.json")?; + let key1_document = load_testdata("data/keys/key1-document.json")?; + let key1_id = match key1_document.get("_id").unwrap() { + Bson::Binary(b) => b.clone(), + v => return Err(failure!("expected binary _id, got {:?}", v)), + }; + + let db = key_vault_client.database("db"); + db.collection::("explicit_encryption") + .drop() + .encrypted_fields(encrypted_fields.clone()) + .await?; + db.create_collection("explicit_encryption") + .encrypted_fields(encrypted_fields) + .await?; + let keyvault = key_vault_client.database("keyvault"); + keyvault.collection::("datakeys").drop().await?; + keyvault.create_collection("datakeys").await?; + keyvault + .collection::("datakeys") + .insert_one(key1_document) + .write_concern(WriteConcern::majority()) + .await?; + + let client_encryption = ClientEncryption::new( + key_vault_client.into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let encrypted_client = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .bypass_query_analysis(true) + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + + Ok(Some(ExplicitEncryptionTestData { + key1_id, + client_encryption, + encrypted_client, + })) + } + + // can insert encrypted indexed and find + #[tokio::test] + async fn case_1() -> Result<()> { + if !fle2v2_ok("explicit_encryption_case_1").await { + return Ok(()); + } + + let testdata = match explicit_encryption_setup().await? { + Some(t) => t, + None => return Ok(()), + }; + let enc_coll = testdata + .encrypted_client + .database("db") + .collection::("explicit_encryption"); + + let insert_payload = testdata + .client_encryption + .encrypt( + "encrypted indexed value", + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Indexed, + ) + .contention_factor(0) + .await?; + enc_coll + .insert_one(doc! { "encryptedIndexed": insert_payload }) + .await?; + + let find_payload = testdata + .client_encryption + .encrypt( + "encrypted indexed value", + EncryptKey::Id(testdata.key1_id), + Algorithm::Indexed, + ) + .query_type("equality".to_string()) + .contention_factor(0) + .await?; + let found: Vec<_> = enc_coll + .find(doc! { "encryptedIndexed": find_payload }) + .await? + .try_collect() + .await?; + assert_eq!(1, found.len()); + assert_eq!( + "encrypted indexed value", + found[0].get_str("encryptedIndexed")? + ); + + Ok(()) + } + + // can insert encrypted indexed and find with non-zero contention + #[tokio::test] + async fn case_2() -> Result<()> { + if !fle2v2_ok("explicit_encryption_case_2").await { + return Ok(()); + } + + let testdata = match explicit_encryption_setup().await? { + Some(t) => t, + None => return Ok(()), + }; + let enc_coll = testdata + .encrypted_client + .database("db") + .collection::("explicit_encryption"); + + for _ in 0..10 { + let insert_payload = testdata + .client_encryption + .encrypt( + "encrypted indexed value", + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Indexed, + ) + .contention_factor(10) + .await?; + enc_coll + .insert_one(doc! { "encryptedIndexed": insert_payload }) + .await?; + } + + let find_payload = testdata + .client_encryption + .encrypt( + "encrypted indexed value", + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Indexed, + ) + .query_type("equality".to_string()) + .contention_factor(0) + .await?; + let found: Vec<_> = enc_coll + .find(doc! { "encryptedIndexed": find_payload }) + .await? + .try_collect() + .await?; + assert!(found.len() < 10); + for doc in found { + assert_eq!("encrypted indexed value", doc.get_str("encryptedIndexed")?); + } + + let find_payload2 = testdata + .client_encryption + .encrypt( + "encrypted indexed value", + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Indexed, + ) + .query_type("equality") + .contention_factor(10) + .await?; + let found: Vec<_> = enc_coll + .find(doc! { "encryptedIndexed": find_payload2 }) + .await? + .try_collect() + .await?; + assert_eq!(10, found.len()); + for doc in found { + assert_eq!("encrypted indexed value", doc.get_str("encryptedIndexed")?); + } + + Ok(()) + } + + // can insert encrypted unindexed + #[tokio::test] + async fn case_3() -> Result<()> { + if !fle2v2_ok("explicit_encryption_case_3").await { + return Ok(()); + } + + let testdata = match explicit_encryption_setup().await? { + Some(t) => t, + None => return Ok(()), + }; + let enc_coll = testdata + .encrypted_client + .database("db") + .collection::("explicit_encryption"); + + let insert_payload = testdata + .client_encryption + .encrypt( + "encrypted unindexed value", + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Unindexed, + ) + .await?; + enc_coll + .insert_one(doc! { "_id": 1, "encryptedUnindexed": insert_payload }) + .await?; + + let found: Vec<_> = enc_coll + .find(doc! { "_id": 1 }) + .await? + .try_collect() + .await?; + assert_eq!(1, found.len()); + assert_eq!( + "encrypted unindexed value", + found[0].get_str("encryptedUnindexed")? + ); + + Ok(()) + } + + // can roundtrip encrypted indexed + #[tokio::test] + async fn case_4() -> Result<()> { + if !fle2v2_ok("explicit_encryption_case_4").await { + return Ok(()); + } + + let testdata = match explicit_encryption_setup().await? { + Some(t) => t, + None => return Ok(()), + }; + + let raw_value = RawBson::String("encrypted indexed value".to_string()); + let payload = testdata + .client_encryption + .encrypt( + raw_value.clone(), + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Indexed, + ) + .contention_factor(0) + .await?; + let roundtrip = testdata + .client_encryption + .decrypt(payload.as_raw_binary()) + .await?; + assert_eq!(raw_value, roundtrip); + + Ok(()) + } + + //can roundtrip encrypted unindexed) + #[tokio::test] + async fn case_5() -> Result<()> { + if !fle2v2_ok("explicit_encryption_case_5").await { + return Ok(()); + } + + let testdata = match explicit_encryption_setup().await? { + Some(t) => t, + None => return Ok(()), + }; + + let raw_value = RawBson::String("encrypted unindexed value".to_string()); + let payload = testdata + .client_encryption + .encrypt( + raw_value.clone(), + EncryptKey::Id(testdata.key1_id.clone()), + Algorithm::Unindexed, + ) + .await?; + let roundtrip = testdata + .client_encryption + .decrypt(payload.as_raw_binary()) + .await?; + assert_eq!(raw_value, roundtrip); + + Ok(()) + } +} + +// Prose test 13. Unique Index on keyAltNames +mod unique_index_on_key_alt_names { + use super::*; + + async fn unique_index_keyaltnames_setup() -> Result<(ClientEncryption, Binary)> { + let client = Client::for_test().await; + let datakeys = client + .database("keyvault") + .collection::("datakeys"); + datakeys.drop().await?; + datakeys + .create_index(IndexModel { + keys: doc! { "keyAltNames": 1 }, + options: Some( + IndexOptions::builder() + .name("keyAltNames_1".to_string()) + .unique(true) + .partial_filter_expression(doc! { "keyAltNames": { "$exists": true } }) + .build(), + ), + }) + .write_concern(WriteConcern::majority()) + .await?; + let client_encryption = ClientEncryption::new( + client.into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let key = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .key_alt_names(vec!["def".to_string()]) + .await?; + Ok((client_encryption, key)) + } + + // `Error::code` skips write errors per the SDAM spec, but we need those. + fn write_err_code(err: &crate::error::Error) -> Option { + if let Some(code) = err.sdam_code() { + return Some(code); + } + match *err.kind { + ErrorKind::Write(WriteFailure::WriteError(WriteError { code, .. })) => Some(code), + _ => None, + } + } + + // createDataKey + #[tokio::test] + async fn case_1() -> Result<()> { + let (client_encryption, _) = unique_index_keyaltnames_setup().await?; + + // Succeeds + client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .key_alt_names(vec!["abc".to_string()]) + .await?; + // Fails: duplicate key + let err = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .key_alt_names(vec!["abc".to_string()]) + .await + .unwrap_err(); + assert_eq!( + Some(11000), + write_err_code(&err), + "unexpected error: {}", + err + ); + // Fails: duplicate key + let err = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .key_alt_names(vec!["def".to_string()]) + .await + .unwrap_err(); + assert_eq!( + Some(11000), + write_err_code(&err), + "unexpected error: {}", + err + ); + + Ok(()) + } + + // add_key_alt_name + #[tokio::test] + async fn case_2() -> Result<()> { + let (client_encryption, key) = unique_index_keyaltnames_setup().await?; + + // Succeeds + let new_key = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .await?; + client_encryption.add_key_alt_name(&new_key, "abc").await?; + // Still succeeds, has alt name + let prev_key = client_encryption + .add_key_alt_name(&new_key, "abc") + .await? + .unwrap(); + assert_eq!("abc", prev_key.get_array("keyAltNames")?.get_str(0)?); + // Fails: adding alt name used for `key` to `new_key` + let err = client_encryption + .add_key_alt_name(&new_key, "def") + .await + .unwrap_err(); + assert_eq!( + Some(11000), + write_err_code(&err), + "unexpected error: {}", + err + ); + // Succeds: re-adding alt name to `new_key` + let prev_key = client_encryption + .add_key_alt_name(&key, "def") + .await? + .unwrap(); + assert_eq!("def", prev_key.get_array("keyAltNames")?.get_str(0)?); + + Ok(()) + } +} + +// Prose test 14. Decryption Events +mod decryption_events { + use super::*; + + struct DecryptionEventsTestdata { + setup_client: TestClient, + decryption_events: Collection, + ev_handler: Arc, + ciphertext: Binary, + malformed_ciphertext: Binary, + } + + impl DecryptionEventsTestdata { + async fn setup() -> Result> { + if !topology_is_standalone().await { + log_uncaptured("skipping decryption events test: requires standalone topology"); + return Ok(None); + } + + let setup_client = Client::for_test().await; + let db = setup_client.database("db"); + db.collection::("decryption_events") + .drop() + .await?; + db.create_collection("decryption_events").await?; + + let client_encryption = ClientEncryption::new( + setup_client.clone().into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let key_id = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .await?; + let ciphertext = client_encryption + .encrypt("hello", EncryptKey::Id(key_id), Algorithm::Deterministic) + .await?; + let mut malformed_ciphertext = ciphertext.clone(); + let last = malformed_ciphertext.bytes.last_mut().unwrap(); + *last = last.wrapping_add(1); + + let ev_handler = DecryptionEventsHandler::new(); + let mut opts = get_client_options().await.clone(); + opts.retry_reads = Some(false); + opts.command_event_handler = Some(ev_handler.clone().into()); + let encrypted_client = + Client::encrypted_builder(opts, KV_NAMESPACE.clone(), vec![LOCAL_KMS.clone()])? + .extra_options(EXTRA_OPTIONS.clone()) + .disable_crypt_shared(*DISABLE_CRYPT_SHARED) + .build() + .await?; + let decryption_events = encrypted_client + .database("db") + .collection("decryption_events"); + + Ok(Some(Self { + setup_client, + decryption_events, + ev_handler, + ciphertext, + malformed_ciphertext, + })) + } + } + + #[derive(Debug)] + struct DecryptionEventsHandler { + succeeded: Mutex>, + failed: Mutex>, + } + + impl DecryptionEventsHandler { + fn new() -> Arc { + Arc::new(Self { + succeeded: Mutex::new(None), + failed: Mutex::new(None), + }) + } + } + + #[allow(deprecated)] + impl crate::event::command::CommandEventHandler for DecryptionEventsHandler { + fn handle_command_succeeded_event(&self, event: CommandSucceededEvent) { + if event.command_name == "aggregate" { + *self.succeeded.lock().unwrap() = Some(event); + } + } + + fn handle_command_failed_event(&self, event: CommandFailedEvent) { + if event.command_name == "aggregate" { + *self.failed.lock().unwrap() = Some(event); + } + } + } + + // command error + #[tokio::test(flavor = "multi_thread")] + async fn case_1() -> Result<()> { + let td = match DecryptionEventsTestdata::setup().await? { + Some(v) => v, + None => return Ok(()), + }; + + let fail_point = + FailPoint::fail_command(&["aggregate"], FailPointMode::Times(1)).error_code(123); + let _guard = td.setup_client.enable_fail_point(fail_point).await.unwrap(); + let err = td + .decryption_events + .aggregate(vec![doc! { "$count": "total" }]) + .await + .unwrap_err(); + assert_eq!(Some(123), err.sdam_code()); + assert!(td.ev_handler.failed.lock().unwrap().is_some()); + + Ok(()) + } + + // network error + #[tokio::test(flavor = "multi_thread")] + async fn case_2() -> Result<()> { + let td = match DecryptionEventsTestdata::setup().await? { + Some(v) => v, + None => return Ok(()), + }; + + let fail_point = FailPoint::fail_command(&["aggregate"], FailPointMode::Times(1)) + .error_code(123) + .close_connection(true); + let _guard = td.setup_client.enable_fail_point(fail_point).await.unwrap(); + let err = td + .decryption_events + .aggregate(vec![doc! { "$count": "total" }]) + .await + .unwrap_err(); + assert!(err.is_network_error(), "unexpected error: {}", err); + assert!(td.ev_handler.failed.lock().unwrap().is_some()); + + Ok(()) + } + + // decrypt error + #[tokio::test] + async fn case_3() -> Result<()> { + let td = match DecryptionEventsTestdata::setup().await? { + Some(v) => v, + None => return Ok(()), + }; + td.decryption_events + .insert_one(doc! { "encrypted": td.malformed_ciphertext }) + .await?; + let err = td.decryption_events.aggregate(vec![]).await.unwrap_err(); + assert!(err.is_csfle_error()); + let guard = td.ev_handler.succeeded.lock().unwrap(); + let ev = guard.as_ref().unwrap(); + assert_eq!( + ElementType::Binary, + ev.reply.get_document("cursor")?.get_array("firstBatch")?[0] + .as_document() + .unwrap() + .get("encrypted") + .unwrap() + .element_type() + ); + + Ok(()) + } + + // decrypt success + #[tokio::test] + async fn case_4() -> Result<()> { + let td = match DecryptionEventsTestdata::setup().await? { + Some(v) => v, + None => return Ok(()), + }; + td.decryption_events + .insert_one(doc! { "encrypted": td.ciphertext }) + .await?; + td.decryption_events.aggregate(vec![]).await?; + let guard = td.ev_handler.succeeded.lock().unwrap(); + let ev = guard.as_ref().unwrap(); + assert_eq!( + ElementType::Binary, + ev.reply.get_document("cursor")?.get_array("firstBatch")?[0] + .as_document() + .unwrap() + .get("encrypted") + .unwrap() + .element_type() + ); + + Ok(()) + } +} + +// TODO RUST-1441: implement prose test 16. Rewrap + +// Prose test 19. Azure IMDS Credentials Integration Test (case 1: failure) +#[cfg(feature = "azure-kms")] +#[tokio::test] +async fn azure_imds_integration_failure() -> Result<()> { + use mongocrypt::ctx::KmsProvider; + + let c = ClientEncryption::new( + Client::for_test().await.into_client(), + KV_NAMESPACE.clone(), + [(KmsProvider::azure(), doc! {}, None)], + )?; + + let result = c + .create_data_key( + AzureMasterKey::builder() + .key_vault_endpoint("https://keyvault-drivers-2411.vault.azure.net/keys/") + .key_name("KEY-NAME") + .build(), + ) + .await; + + assert!(result.is_err(), "expected error, got {:?}", result); + assert!(result.unwrap_err().is_auth_error()); + + Ok(()) +} + +// Prose test 20. Bypass creating mongocryptd client when shared library is loaded +#[tokio::test] +async fn bypass_mongocryptd_client() -> Result<()> { + if *DISABLE_CRYPT_SHARED { + log_uncaptured("Skipping bypass mongocryptd client test: crypt_shared is disabled."); + return Ok(()); + } + + async fn bind(addr: &str) -> Result { + Ok(TcpListener::bind(addr.parse::()?).await?) + } + + let connected = Arc::new(AtomicBool::new(false)); + { + let connected = Arc::clone(&connected); + let listener = bind("127.0.0.1:27021").await?; + runtime::spawn(async move { + let _ = listener.accept().await; + log_uncaptured("test failure: connection accepted"); + connected.store(true, Ordering::SeqCst); + }) + }; + + let client_encrypted = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .extra_options({ + let mut extra_options = EXTRA_OPTIONS.clone(); + extra_options.insert("mongocryptdURI", "mongodb://localhost:27021"); + extra_options + }) + .build() + .await?; + client_encrypted + .database("db") + .collection::("coll") + .insert_one(doc! { "unencrypted": "test" }) + .await?; + + assert!(!client_encrypted.has_mongocryptd_client().await); + assert!(!connected.load(Ordering::SeqCst)); + + Ok(()) +} + +// Prose test 21. Automatic Data Encryption Keys +mod auto_encryption_keys { + use super::*; + + async fn auto_encryption_keys(master_key: impl Into) -> Result<()> { + if !fle2v2_ok("auto_encryption_keys").await { + return Ok(()); + } + + let master_key = master_key.into(); + + let client = Client::for_test().await; + let db = client.database("test_auto_encryption_keys"); + db.drop().await?; + let ce = ClientEncryption::new( + client.into_client(), + KV_NAMESPACE.clone(), + vec![AWS_KMS.clone(), LOCAL_KMS.clone()], + )?; + + // Case 1: Simple Creation and Validation + ce.create_encrypted_collection(&db, "case_1", master_key.clone()) + .encrypted_fields(doc! { + "fields": [{ + "path": "ssn", + "bsonType": "string", + "keyId": Bson::Null, + }], + }) + .await + .1?; + let coll = db.collection::("case_1"); + let result = coll.insert_one(doc! { "ssn": "123-45-6789" }).await; + assert!( + result.as_ref().unwrap_err().code() == Some(121), + "Expected error 121 (failed validation), got {:?}", + result + ); + + // Case 2: Missing encryptedFields + let result = ce + .create_encrypted_collection(&db, "case_2", master_key.clone()) + .await + .1; + assert!( + result.as_ref().unwrap_err().is_invalid_argument(), + "Expected invalid argument error, got {:?}", + result + ); + + // Case 3: Invalid keyId + let result = ce + .create_encrypted_collection(&db, "case_1", master_key.clone()) + .encrypted_fields(doc! { + "fields": [{ + "path": "ssn", + "bsonType": "string", + "keyId": false, + }], + }) + .await + .1; + assert!( + result.as_ref().unwrap_err().code() == Some(14), + "Expected error 14 (type mismatch), got {:?}", + result + ); + + // Case 4: Insert encrypted value + let (ef, result) = ce + .create_encrypted_collection(&db, "case_4", master_key.clone()) + .encrypted_fields(doc! { + "fields": [{ + "path": "ssn", + "bsonType": "string", + "keyId": Bson::Null, + }], + }) + .await; + result?; + let key = match ef.get_array("fields")?[0] + .as_document() + .unwrap() + .get("keyId") + .unwrap() + { + Bson::Binary(bin) => bin.clone(), + v => panic!("invalid keyId {:?}", v), + }; + let encrypted_payload = ce.encrypt("123-45-6789", key, Algorithm::Unindexed).await?; + let coll = db.collection::("case_1"); + coll.insert_one(doc! { "ssn": encrypted_payload }).await?; + + Ok(()) + } + + #[tokio::test] + async fn local() -> Result<()> { + auto_encryption_keys(LocalMasterKey::builder().build()).await + } + + #[tokio::test] + async fn aws() -> Result<()> { + auto_encryption_keys( + AwsMasterKey::builder() + .region("us-east-1") + .key("arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0") + .build(), + ) + .await + } +} + +// Prose test 22. Range Explicit Encryption +mod range_explicit_encryption { + use super::*; + + async fn range_explicit_encryption_test( + bson_type: &str, + range_options: RangeOptions, + ) -> Result<()> { + let util_client = Client::for_test().await; + + let encrypted_fields = + load_testdata(&format!("data/range-encryptedFields-{}.json", bson_type))?; + + let key1_document = load_testdata("data/keys/key1-document.json")?; + let key1_id = match key1_document.get("_id").unwrap() { + Bson::Binary(binary) => binary, + _ => unreachable!(), + } + .clone(); + + let explicit_encryption_collection = util_client + .database("db") + .collection::("explicit_encryption"); + explicit_encryption_collection + .drop() + .encrypted_fields(encrypted_fields.clone()) + .await?; + util_client + .database("db") + .create_collection("explicit_encryption") + .encrypted_fields(encrypted_fields.clone()) + .await?; + + let datakeys_collection = util_client + .database("keyvault") + .collection::("datakeys"); + datakeys_collection.drop().await?; + util_client + .database("keyvault") + .create_collection("datakeys") + .await?; + + datakeys_collection + .insert_one(key1_document) + .write_concern(WriteConcern::majority()) + .await?; + + let key_vault_client = Client::for_test().await; + + let client_encryption = ClientEncryption::new( + key_vault_client.into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + + let encrypted_client = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .extra_options(EXTRA_OPTIONS.clone()) + .bypass_query_analysis(true) + .build() + .await?; + + let key = format!("encrypted{}", bson_type); + let bson_numbers: BTreeMap = [0, 6, 30, 200] + .iter() + .map(|num| (*num, get_raw_bson_from_num(bson_type, *num))) + .collect(); + let explicit_encryption_collection = encrypted_client + .database("db") + .collection("explicit_encryption"); + + for (id, num) in bson_numbers.keys().enumerate() { + let encrypted_value = client_encryption + .encrypt(bson_numbers[num].clone(), key1_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + explicit_encryption_collection + .insert_one(doc! { + &key: encrypted_value, + "_id": id as i32, + }) + .await?; + } + + // Case 1: Decrypt a payload + let insert_payload = client_encryption + .encrypt(bson_numbers[&6].clone(), key1_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + let decrypted = client_encryption + .decrypt(insert_payload.as_raw_binary()) + .await?; + assert_eq!(decrypted, bson_numbers[&6]); + + // Utilities for cases 2-5 + let explicit_encryption_collection = + explicit_encryption_collection.clone_with_type::(); + let find_options = FindOptions::builder().sort(doc! { "_id": 1 }).build(); + let assert_success = |actual: Vec, expected: &[i32]| { + assert_eq!(actual.len(), expected.len()); + for (idx, num) in expected.iter().enumerate() { + assert_eq!( + actual[idx].get(&key).unwrap(), + Some(bson_numbers[num].as_raw_bson_ref()) + ); + } + }; + + // Case 2: Find encrypted range and return the maximum + let ckey: &crate::bson_compat::CStr = key.as_str().try_into()?; + let query = rawdoc! { + "$and": [ + { ckey: { "$gte": bson_numbers[&6].clone() } }, + { ckey: { "$lte": bson_numbers[&200].clone() } }, + ] + }; + let find_payload = client_encryption + .encrypt_expression(query, key1_id.clone()) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + let docs: Vec = explicit_encryption_collection + .find(find_payload) + .with_options(find_options.clone()) + .await? + .try_collect() + .await?; + assert_success(docs, &[6, 30, 200]); + + // Case 3: Find encrypted range and return the minimum + let query = rawdoc! { + "$and": [ + { ckey: { "$gte": bson_numbers[&0].clone() } }, + { ckey: { "$lte": bson_numbers[&6].clone() } }, + ] + }; + let find_payload = client_encryption + .encrypt_expression(query, key1_id.clone()) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + let docs: Vec = encrypted_client + .database("db") + .collection("explicit_encryption") + .find(find_payload) + .with_options(find_options.clone()) + .await? + .try_collect() + .await?; + assert_success(docs, &[0, 6]); + + // Case 4: Find encrypted range with an open range query + let query = rawdoc! { + "$and": [ + { ckey: { "$gt": bson_numbers[&30].clone() } }, + ] + }; + let find_payload = client_encryption + .encrypt_expression(query, key1_id.clone()) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + let docs: Vec = encrypted_client + .database("db") + .collection("explicit_encryption") + .find(find_payload) + .with_options(find_options.clone()) + .await? + .try_collect() + .await?; + assert_success(docs, &[200]); + + // Case 5: Run an aggregation expression inside $expr + let query = rawdoc! { "$and": [ { "$lt": [ format!("${key}"), get_raw_bson_from_num(bson_type, 30) ] } ] }; + let find_payload = client_encryption + .encrypt_expression(query, key1_id.clone()) + .contention_factor(0) + .range_options(range_options.clone()) + .await?; + + let docs: Vec = encrypted_client + .database("db") + .collection("explicit_encryption") + .find(doc! { "$expr": find_payload }) + .with_options(find_options.clone()) + .await? + .try_collect() + .await?; + assert_success(docs, &[0, 6]); + + // Case 6: Encrypting a document greater than the maximum errors + if bson_type != "DoubleNoPrecision" && bson_type != "DecimalNoPrecision" { + let num = get_raw_bson_from_num(bson_type, 201); + let error = client_encryption + .encrypt(num, key1_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options(range_options.clone()) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::Encryption(_))); + } + + // Case 7: Encrypting a document of a different type errors + if bson_type != "DoubleNoPrecision" && bson_type != "DecimalNoPrecision" { + let value = if bson_type == "Int" { + rawdoc! { ckey: { "$numberDouble": "6" } } + } else { + rawdoc! { ckey: { "$numberInt": "6" } } + }; + let error = client_encryption + .encrypt(value, key1_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options(range_options.clone()) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::Encryption(_))); + } + + // Case 8: Setting precision errors if the type is not a double + if !bson_type.contains("Double") && !bson_type.contains("Decimal") { + let range_options = RangeOptions::builder() + .sparsity(1) + .min(get_bson_from_num(bson_type, 0)) + .max(get_bson_from_num(bson_type, 200)) + .precision(2) + .build(); + let error = client_encryption + .encrypt(bson_numbers[&6].clone(), key1_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options(range_options) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::Encryption(_))); + } + + Ok(()) + } + + fn get_bson_from_num(bson_type: &str, num: i32) -> Bson { + match bson_type { + "DecimalNoPrecision" | "DecimalPrecision" => { + Bson::Decimal128(num.to_string().parse().unwrap()) + } + "DoubleNoPrecision" | "DoublePrecision" => Bson::Double(num as f64), + "Date" => Bson::DateTime(DateTime::from_millis(num as i64)), + "Int" => Bson::Int32(num), + "Long" => Bson::Int64(num as i64), + _ => unreachable!(), + } + } + + fn get_raw_bson_from_num(bson_type: &str, num: i32) -> RawBson { + match bson_type { + "DecimalNoPrecision" | "DecimalPrecision" => { + RawBson::Decimal128(num.to_string().parse().unwrap()) + } + "DoubleNoPrecision" | "DoublePrecision" => RawBson::Double(num as f64), + "Date" => RawBson::DateTime(DateTime::from_millis(num as i64)), + "Int" => RawBson::Int32(num), + "Long" => RawBson::Int64(num as i64), + _ => unreachable!(), + } + } + + #[tokio::test] + async fn range_explicit_encryption() -> Result<()> { + if server_version_lt(8, 0).await || topology_is_standalone().await { + log_uncaptured("Skipping range_explicit_encryption due to unsupported topology"); + return Ok(()); + } + + range_explicit_encryption_test( + "DecimalNoPrecision", + RangeOptions::builder().sparsity(1).trim_factor(1).build(), + ) + .await?; + range_explicit_encryption_test( + "DecimalPrecision", + RangeOptions::builder() + .trim_factor(1) + .sparsity(1) + .min(Bson::Decimal128("0".parse()?)) + .max(Bson::Decimal128("200".parse()?)) + .precision(2) + .build(), + ) + .await?; + range_explicit_encryption_test( + "DoubleNoPrecision", + RangeOptions::builder().trim_factor(1).sparsity(1).build(), + ) + .await?; + range_explicit_encryption_test( + "DoublePrecision", + RangeOptions::builder() + .trim_factor(1) + .sparsity(1) + .min(Bson::Double(0.0)) + .max(Bson::Double(200.0)) + .precision(2) + .build(), + ) + .await?; + range_explicit_encryption_test( + "Date", + RangeOptions::builder() + .trim_factor(1) + .sparsity(1) + .min(Bson::DateTime(DateTime::from_millis(0))) + .max(Bson::DateTime(DateTime::from_millis(200))) + .build(), + ) + .await?; + range_explicit_encryption_test( + "Int", + RangeOptions::builder() + .trim_factor(1) + .sparsity(1) + .min(Bson::Int32(0)) + .max(Bson::Int32(200)) + .build(), + ) + .await?; + range_explicit_encryption_test( + "Long", + RangeOptions::builder() + .trim_factor(1) + .sparsity(1) + .min(Bson::Int64(0)) + .max(Bson::Int64(200)) + .build(), + ) + .await?; + + Ok(()) + } +} + +// Prose test 23. Range explicit encryption applies defaults +#[tokio::test] +async fn range_explicit_encryption_defaults() -> Result<()> { + // Setup + let key_vault_client = Client::for_test().await; + let client_encryption = ClientEncryption::new( + key_vault_client.into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let key_id = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .await?; + let payload_defaults = client_encryption + .encrypt(123, key_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options( + RangeOptions::builder() + .min(Bson::from(0)) + .max(Bson::from(1000)) + .build(), + ) + .await?; + + // Case 1: Uses libmongocrypt defaults + let payload = client_encryption + .encrypt(123, key_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options( + RangeOptions::builder() + .min(Bson::from(0)) + .max(Bson::from(1000)) + .sparsity(2) + .trim_factor(6) + .build(), + ) + .await?; + assert_eq!(payload_defaults.bytes.len(), payload.bytes.len()); + + // Case 2: Accepts trimFactor 0 + let payload = client_encryption + .encrypt(123, key_id.clone(), Algorithm::Range) + .contention_factor(0) + .range_options( + RangeOptions::builder() + .min(Bson::from(0)) + .max(Bson::from(1000)) + .trim_factor(0) + .build(), + ) + .await?; + assert!(payload.bytes.len() > payload_defaults.bytes.len()); + + Ok(()) +} + +// FLE 2.0 Documentation Example +#[tokio::test] +async fn fle2_example() -> Result<()> { + if !fle2v2_ok("fle2_example").await { + return Ok(()); + } + + let test_client = Client::for_test().await; + + // Drop data from prior test runs. + test_client + .database("keyvault") + .collection::("datakeys") + .drop() + .await?; + test_client.database("docsExamples").drop().await?; + + // Create two data keys. + let ce = ClientEncryption::new( + test_client.clone().into_client(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )?; + let key1_id = ce + .create_data_key(LocalMasterKey::builder().build()) + .await?; + let key2_id = ce + .create_data_key(LocalMasterKey::builder().build()) + .await?; + + // Create an encryptedFieldsMap. + let encrypted_fields_map = [( + "docsExamples.encrypted", + doc! { + "fields": [ + { + "path": "encryptedIndexed", + "bsonType": "string", + "keyId": key1_id, + "queries": { "queryType": "equality" }, + }, + { + "path": "encryptedUnindexed", + "bsonType": "string", + "keyId": key2_id, + }, + ] + }, + )]; + + // Create an FLE 2 collection. + let encrypted_client = Client::encrypted_builder( + get_client_options().await.clone(), + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + )? + .extra_options(EXTRA_OPTIONS.clone()) + .encrypted_fields_map(encrypted_fields_map) + .build() + .await?; + let db = encrypted_client.database("docsExamples"); + db.create_collection("encrypted").await?; + let encrypted_coll = db.collection::("encrypted"); + + // Auto encrypt an insert and find. + + // Encrypt an insert. + encrypted_coll + .insert_one(doc! { + "_id": 1, + "encryptedIndexed": "indexedValue", + "encryptedUnindexed": "unindexedValue", + }) + .await?; + + // Encrypt a find. + let found = encrypted_coll + .find_one(doc! { + "encryptedIndexed": "indexedValue", + }) + .await? + .unwrap(); + assert_eq!("indexedValue", found.get_str("encryptedIndexed")?); + assert_eq!("unindexedValue", found.get_str("encryptedUnindexed")?); + + // Find documents without decryption. + let unencrypted_coll = test_client + .database("docsExamples") + .collection::("encrypted"); + let found = unencrypted_coll.find_one(doc! { "_id": 1 }).await?.unwrap(); + assert_eq!( + Some(ElementType::Binary), + found.get("encryptedIndexed").map(Bson::element_type) + ); + assert_eq!( + Some(ElementType::Binary), + found.get("encryptedUnindexed").map(Bson::element_type) + ); + + Ok(()) +} + +#[tokio::test] +async fn encrypt_expression_with_options() { + let key_vault_client = Client::for_test().await.into_client(); + let client_encryption = ClientEncryption::new( + key_vault_client, + KV_NAMESPACE.clone(), + vec![LOCAL_KMS.clone()], + ) + .unwrap(); + let data_key = client_encryption + .create_data_key(LocalMasterKey::builder().build()) + .await + .unwrap(); + + let expression = rawdoc! { + "$and": [ + { "a": { "$gt": 0 } }, + { "a": { "$lt": 10 } }, + ] + }; + let range_options = RangeOptions::builder() + .min(Bson::from(0)) + .max(Bson::from(10)) + .build(); + + let invalid_encrypt_options = EncryptOptions::builder() + .contention_factor(0) + .range_options(range_options.clone()) + .query_type("bad".to_string()) + .build(); + let error = client_encryption + .encrypt_expression(expression.clone(), data_key.clone()) + .with_options(invalid_encrypt_options) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::InvalidArgument { .. })); + + let valid_encrypt_options = EncryptOptions::builder() + .contention_factor(0) + .range_options(range_options) + .build(); + client_encryption + .encrypt_expression(expression, data_key) + .with_options(valid_encrypt_options) + .await + .unwrap(); +} diff --git a/src/test/csfle/spec.rs b/src/test/csfle/spec.rs new file mode 100644 index 000000000..95f302799 --- /dev/null +++ b/src/test/csfle/spec.rs @@ -0,0 +1,36 @@ +use crate::test::spec::{unified_runner::run_unified_tests, v2_runner::run_v2_tests}; + +#[tokio::test(flavor = "multi_thread")] +async fn run_unified() { + let mut skipped_tests = vec![]; + if cfg!(not(feature = "openssl-tls")) { + skipped_tests.push("create datakey with KMIP KMS provider"); + skipped_tests.push("create datakey with KMIP delegated KMS provider"); + skipped_tests.push("create datakey with named KMIP KMS provider"); + } + + run_unified_tests(&["client-side-encryption", "unified"]) + .skip_tests(&skipped_tests) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn run_legacy() { + let mut skipped_files = vec![ + // TODO RUST-528: unskip this file + "timeoutMS.json", + // These files have been migrated to unified tests. + // TODO DRIVERS-3178 remove these once the files are gone. + "fle2v2-BypassQueryAnalysis.json", + "fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json", + "localSchema.json", + "maxWireVersion.json", + ]; + if cfg!(not(feature = "openssl-tls")) { + skipped_files.push("kmipKMS.json"); + } + + run_v2_tests(&["client-side-encryption", "legacy"]) + .skip_files(&skipped_files) + .await; +} diff --git a/src/test/cursor.rs b/src/test/cursor.rs index 6b7a78a59..4904f3819 100644 --- a/src/test/cursor.rs +++ b/src/test/cursor.rs @@ -2,29 +2,18 @@ use std::time::Duration; use futures::{future::Either, StreamExt, TryStreamExt}; use serde::{Deserialize, Serialize}; -use tokio::sync::RwLockReadGuard; use crate::{ bson::doc, options::{CreateCollectionOptions, CursorType, FindOptions}, runtime, - test::{log_uncaptured, util::EventClient, TestClient, LOCK, SERVERLESS}, + Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn tailable_cursor() { - if *SERVERLESS { - log_uncaptured( - "skipping cursor::tailable_cursor; serverless does not support capped collections", - ); - return; - } - - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .create_fresh_collection( function_name!(), @@ -37,20 +26,16 @@ async fn tailable_cursor() { ) .await; - coll.insert_many((0..5).map(|i| doc! { "_id": i }), None) + coll.insert_many((0..5).map(|i| doc! { "_id": i })) .await .unwrap(); let await_time = Duration::from_millis(500); let mut cursor = coll - .find( - None, - FindOptions::builder() - .cursor_type(CursorType::TailableAwait) - .max_await_time(await_time) - .build(), - ) + .find(doc! {}) + .cursor_type(CursorType::TailableAwait) + .max_await_time(await_time) .await .unwrap(); @@ -61,7 +46,7 @@ async fn tailable_cursor() { ); } - let delay = runtime::delay_for(await_time); + let delay = tokio::time::sleep(await_time); let next_doc = cursor.next(); let next_doc = match futures::future::select(Box::pin(delay), Box::pin(next_doc)).await { @@ -73,11 +58,11 @@ async fn tailable_cursor() { ), }; - runtime::execute(async move { - coll.insert_one(doc! { "_id": 5 }, None).await.unwrap(); + runtime::spawn(async move { + coll.insert_one(doc! { "_id": 5 }).await.unwrap(); }); - let delay = runtime::delay_for(await_time); + let delay = tokio::time::sleep(await_time); match futures::future::select(Box::pin(delay), Box::pin(next_doc)).await { Either::Left((..)) => panic!("should have gotten next document, but instead timed"), @@ -87,26 +72,25 @@ async fn tailable_cursor() { }; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn session_cursor_next() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let mut session = client.start_session(None).await.unwrap(); + let client = Client::for_test().await; + let mut session = client.start_session().await.unwrap(); let coll = client .create_fresh_collection(function_name!(), function_name!(), None) .await; - coll.insert_many_with_session((0..5).map(|i| doc! { "_id": i }), None, &mut session) + coll.insert_many((0..5).map(|i| doc! { "_id": i })) + .session(&mut session) .await .unwrap(); - let opts = FindOptions::builder().batch_size(1).build(); let mut cursor = coll - .find_with_session(None, opts, &mut session) + .find(doc! {}) + .batch_size(1) + .session(&mut session) .await .unwrap(); @@ -118,11 +102,9 @@ async fn session_cursor_next() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn batch_exhaustion() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll = client .create_fresh_collection( @@ -131,30 +113,25 @@ async fn batch_exhaustion() { None, ) .await; - coll.insert_many( - vec![ - doc! { "foo": 1 }, - doc! { "foo": 2 }, - doc! { "foo": 3 }, - doc! { "foo": 4 }, - doc! { "foo": 5 }, - doc! { "foo": 6 }, - ], - None, - ) + coll.insert_many(vec![ + doc! { "foo": 1 }, + doc! { "foo": 2 }, + doc! { "foo": 3 }, + doc! { "foo": 4 }, + doc! { "foo": 5 }, + doc! { "foo": 6 }, + ]) .await .unwrap(); // Start a find where batch size will line up with limit. - let cursor = coll - .find(None, FindOptions::builder().batch_size(2).limit(4).build()) - .await - .unwrap(); + let cursor = coll.find(doc! {}).batch_size(2).limit(4).await.unwrap(); let v: Vec<_> = cursor.try_collect().await.unwrap(); assert_eq!(4, v.len()); // Assert that the last `getMore` response always has id 0, i.e. is exhausted. let replies: Vec<_> = client + .events .get_command_events(&["getMore"]) .into_iter() .filter_map(|e| e.as_command_succeeded().map(|e| e.reply.clone())) @@ -165,11 +142,9 @@ async fn batch_exhaustion() { assert_eq!(0, id); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn borrowed_deserialization() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; #[derive(Serialize, Deserialize, Debug, PartialEq)] struct Doc<'a> { @@ -206,14 +181,18 @@ async fn borrowed_deserialization() { Doc { id: 5, foo: "1" }, ]; - coll.insert_many(&docs, None).await.unwrap(); + coll.insert_many(&docs).await.unwrap(); let options = FindOptions::builder() .batch_size(2) .sort(doc! { "_id": 1 }) .build(); - let mut cursor = coll.find(None, options.clone()).await.unwrap(); + let mut cursor = coll + .find(doc! {}) + .with_options(options.clone()) + .await + .unwrap(); let mut i = 0; while cursor.advance().await.unwrap() { @@ -222,9 +201,11 @@ async fn borrowed_deserialization() { i += 1; } - let mut session = client.start_session(None).await.unwrap(); + let mut session = client.start_session().await.unwrap(); let mut cursor = coll - .find_with_session(None, options.clone(), &mut session) + .find(doc! {}) + .with_options(options.clone()) + .session(&mut session) .await .unwrap(); @@ -235,3 +216,51 @@ async fn borrowed_deserialization() { i += 1; } } + +#[tokio::test] +async fn session_cursor_with_type() { + let client = Client::for_test().await; + + let mut session = client.start_session().await.unwrap(); + let coll = client.database("db").collection("coll"); + coll.drop().session(&mut session).await.unwrap(); + + coll.insert_many(vec![doc! { "x": 1 }, doc! { "x": 2 }, doc! { "x": 3 }]) + .session(&mut session) + .await + .unwrap(); + + let mut cursor: crate::SessionCursor = + coll.find(doc! {}).session(&mut session).await.unwrap(); + + let _ = cursor.next(&mut session).await.unwrap().unwrap(); + + let mut cursor_with_type: crate::SessionCursor = + cursor.with_type(); + + let _ = cursor_with_type.next(&mut session).await.unwrap().unwrap(); +} + +#[tokio::test] +async fn cursor_final_batch() { + let client = Client::for_test().await; + let coll = client + .create_fresh_collection("test_cursor_final_batch", "test", None) + .await; + coll.insert_many(vec![ + doc! { "foo": 1 }, + doc! { "foo": 2 }, + doc! { "foo": 3 }, + doc! { "foo": 4 }, + doc! { "foo": 5 }, + ]) + .await + .unwrap(); + + let mut cursor = coll.find(doc! {}).batch_size(3).await.unwrap(); + let mut found = 0; + while cursor.advance().await.unwrap() { + found += 1; + } + assert_eq!(found, 5); +} diff --git a/src/test/db.rs b/src/test/db.rs index faec38b95..b13df1abc 100644 --- a/src/test/db.rs +++ b/src/test/db.rs @@ -1,13 +1,15 @@ use std::cmp::Ord; -use futures::stream::TryStreamExt; -use tokio::sync::RwLockReadGuard; +use crate::{bson::RawDocumentBuf, bson_compat::cstr}; +use futures::{stream::TryStreamExt, StreamExt}; +use serde::Deserialize; use crate::{ + action::Action, bson::{doc, Document}, error::Result, options::{ - AggregateOptions, + ClusteredIndex, Collation, CreateCollectionOptions, IndexOptionDefaults, @@ -15,18 +17,16 @@ use crate::{ ValidationLevel, }, results::{CollectionSpecification, CollectionType}, - test::{ - util::{EventClient, TestClient}, - LOCK, - }, + test::{log_uncaptured, server_version_lt}, + Client, + Cursor, Database, }; -use super::log_uncaptured; - async fn get_coll_info(db: &Database, filter: Option) -> Vec { let mut colls: Vec = db - .list_collections(filter, None) + .list_collections() + .optional(filter, |b, f| b.filter(f)) .await .unwrap() .try_collect() @@ -37,22 +37,14 @@ async fn get_coll_info(db: &Database, filter: Option) -> Vec = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - db.drop(None).await.unwrap(); + db.drop().await.unwrap(); - let colls: Result> = db - .list_collections(None, None) - .await - .unwrap() - .try_collect() - .await; + let colls: Result> = db.list_collections().await.unwrap().try_collect().await; assert_eq!(colls.unwrap().len(), 0); let coll_names = &[ @@ -63,7 +55,7 @@ async fn list_collections() { for coll_name in coll_names { db.collection(coll_name) - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); } @@ -78,28 +70,20 @@ async fn list_collections() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn list_collections_filter() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - db.drop(None).await.unwrap(); + db.drop().await.unwrap(); - let colls: Result> = db - .list_collections(None, None) - .await - .unwrap() - .try_collect() - .await; + let colls: Result> = db.list_collections().await.unwrap().try_collect().await; assert_eq!(colls.unwrap().len(), 0); let coll_names = &["bar", "baz", "foo"]; for coll_name in coll_names { db.collection(coll_name) - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); } @@ -121,17 +105,14 @@ async fn list_collections_filter() { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn list_collection_names() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - db.drop(None).await.unwrap(); + db.drop().await.unwrap(); - assert!(db.list_collection_names(None).await.unwrap().is_empty()); + assert!(db.list_collection_names().await.unwrap().is_empty()); let expected_colls = &[ format!("{}1", function_name!()), @@ -141,30 +122,27 @@ async fn list_collection_names() { for coll in expected_colls { db.collection(coll) - .insert_one(doc! { "x": 1 }, None) + .insert_one(doc! { "x": 1 }) .await .unwrap(); } - let mut actual_colls = db.list_collection_names(None).await.unwrap(); + let mut actual_colls = db.list_collection_names().await.unwrap(); actual_colls.sort(); assert_eq!(&actual_colls, expected_colls); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn collection_management() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let db = client.database(function_name!()); - db.drop(None).await.unwrap(); + db.drop().await.unwrap(); - assert!(db.list_collection_names(None).await.unwrap().is_empty()); + assert!(db.list_collection_names().await.unwrap().is_empty()); - db.create_collection(&format!("{}{}", function_name!(), 1), None) + db.create_collection(format!("{}{}", function_name!(), 1)) .await .unwrap(); @@ -185,7 +163,8 @@ async fn collection_management() { ) .build(); - db.create_collection(&format!("{}{}", function_name!(), 2), options.clone()) + db.create_collection(format!("{}{}", function_name!(), 2)) + .with_options(options.clone()) .await .unwrap(); @@ -193,7 +172,8 @@ async fn collection_management() { .view_on(format!("{}{}", function_name!(), 2)) .pipeline(vec![doc! { "$match": {} }]) .build(); - db.create_collection(&format!("{}{}", function_name!(), 3), view_options.clone()) + db.create_collection(format!("{}{}", function_name!(), 3)) + .with_options(view_options.clone()) .await .unwrap(); @@ -207,7 +187,8 @@ async fn collection_management() { assert_eq!(colls[0].name, format!("{}1", function_name!())); assert_eq!(colls[0].collection_type, CollectionType::Collection); assert_eq!( - bson::to_document(&colls[0].options).expect("serialization should succeed"), + crate::bson_compat::serialize_to_document(&colls[0].options) + .expect("serialization should succeed"), doc! {} ); assert!(!colls[0].info.read_only); @@ -238,18 +219,9 @@ async fn collection_management() { assert!(coll3.id_index.is_none()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn db_aggregate() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - - if client.server_version_lt(4, 0) { - log_uncaptured("skipping db_aggregate due to server version < 4.0"); - return; - } - + let client = Client::for_test().await; let db = client.database("admin"); let pipeline = vec![ @@ -278,23 +250,14 @@ async fn db_aggregate() { }, ]; - db.aggregate(pipeline, None) + db.aggregate(pipeline) .await .expect("aggregate should succeed"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn db_aggregate_disk_use() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - - if client.server_version_lt(4, 0) { - log_uncaptured("skipping db_aggregate_disk_use due to server version < 4.0"); - return; - } - + let client = Client::for_test().await; let db = client.database("admin"); let pipeline = vec![ @@ -323,15 +286,13 @@ async fn db_aggregate_disk_use() { }, ]; - let options = AggregateOptions::builder().allow_disk_use(true).build(); - - db.aggregate(pipeline, Some(options)) + db.aggregate(pipeline) + .allow_disk_use(true) .await .expect("aggregate with disk use should succeed"); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn create_index_options_defaults() { let defaults = IndexOptionDefaults { @@ -340,26 +301,23 @@ async fn create_index_options_defaults() { index_option_defaults_test(Some(defaults), function_name!()).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn create_index_options_defaults_not_specified() { index_option_defaults_test(None, function_name!()).await; } async fn index_option_defaults_test(defaults: Option, name: &str) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let db = client.database(name); - let options = CreateCollectionOptions::builder() - .index_option_defaults(defaults.clone()) - .build(); - db.create_collection(name, options).await.unwrap(); - db.drop(None).await.unwrap(); + db.create_collection(name) + .optional(defaults.clone(), |b, d| b.index_option_defaults(d)) + .await + .unwrap(); + db.drop().await.unwrap(); - let events = client.get_command_started_events(&["create"]); + let events = client.events.get_command_started_events(&["create"]); assert_eq!(events.len(), 1); let event_defaults = match events[0].command.get_document("indexOptionDefaults") { @@ -370,3 +328,143 @@ async fn index_option_defaults_test(defaults: Option, name: }; assert_eq!(event_defaults, defaults); } + +#[test] +fn deserialize_clustered_index_option_from_bool() { + let options_doc = doc! { "clusteredIndex": true }; + let options: CreateCollectionOptions = + crate::bson_compat::deserialize_from_document(options_doc).unwrap(); + let clustered_index = options + .clustered_index + .expect("deserialized options should include clustered_index"); + assert_eq!(clustered_index, ClusteredIndex::default()); +} + +#[tokio::test] +async fn clustered_index_list_collections() { + if server_version_lt(5, 3).await { + return; + } + + let client = Client::for_test().await; + let database = client.database("db"); + + database + .create_collection("clustered_index_collection") + .clustered_index(ClusteredIndex::default()) + .await + .unwrap(); + + let collections = database + .list_collections() + .await + .unwrap() + .try_collect::>() + .await + .unwrap(); + let clustered_index_collection = collections + .iter() + .find(|specification| specification.name == "clustered_index_collection") + .unwrap(); + assert!(clustered_index_collection.options.clustered_index.is_some()); +} + +#[tokio::test] +async fn aggregate_with_generics() { + if server_version_lt(5, 1).await { + log_uncaptured( + "skipping aggregate_with_generics: $documents agg stage only available on 5.1+", + ); + return; + } + + #[derive(Deserialize)] + struct A { + str: String, + } + + let client = Client::for_test().await; + let database = client.database("aggregate_with_generics"); + + // The cursor returned will contain these documents + let pipeline = vec![doc! { "$documents": [ { "str": "hi" } ] }]; + + // Assert at compile-time that the default cursor returned is a Cursor + let _: Cursor = database.aggregate(pipeline.clone()).await.unwrap(); + + // Assert that data is properly deserialized when using with_type + let mut cursor = database + .aggregate(pipeline.clone()) + .with_type::() + .await + .unwrap(); + assert!(cursor.advance().await.unwrap()); + assert_eq!(&cursor.deserialize_current().unwrap().str, "hi"); + + // Assert that `with_type` can be used with an explicit session. + let mut session = client.start_session().await.unwrap(); + let _ = database + .aggregate(pipeline.clone()) + .session(&mut session) + .with_type::() + .await + .unwrap(); + let _ = database + .aggregate(pipeline.clone()) + .with_type::() + .session(&mut session) + .await + .unwrap(); +} + +#[tokio::test] +async fn test_run_command() { + let client = Client::for_test().await; + let database = client.database("db"); + + // Test run_command + { + let got = database.run_command(doc! {"ping": 1}).await.unwrap(); + assert_eq!(crate::bson_util::get_int(got.get("ok").unwrap()), Some(1)); + } + + // Test run_raw_command + { + let mut cmd = RawDocumentBuf::new(); + cmd.append(cstr!("ping"), 1); + let got = database.run_raw_command(cmd).await.unwrap(); + assert_eq!(crate::bson_util::get_int(got.get("ok").unwrap()), Some(1)); + } + + // Create a collection with a single document + { + let coll = database.collection("coll"); + coll.drop().await.expect("should drop"); + coll.insert_one(doc! {"foo": "bar"}) + .await + .expect("should insert"); + } + + // Test run_cursor_command + { + let cursor = database + .run_cursor_command(doc! {"find": "coll", "filter": {}}) + .await + .unwrap(); + let v: Vec> = cursor.collect().await; + assert_eq!(v.len(), 1); + assert_eq!(v[0].as_ref().unwrap().get_str("foo").unwrap(), "bar"); + } + + // Test run_raw_cursor_command + { + let mut cmd = RawDocumentBuf::new(); + cmd.append(cstr!("find"), "coll"); + cmd.append(cstr!("filter"), RawDocumentBuf::new()); + + let cursor = database.run_raw_cursor_command(cmd).await.unwrap(); + let v: Vec> = cursor.collect().await; + assert_eq!(v.len(), 1); + assert_eq!(v[0].as_ref().unwrap().get_str("foo").unwrap(), "bar"); + } +} diff --git a/src/test/documentation_examples.rs b/src/test/documentation_examples.rs new file mode 100644 index 000000000..a804d8c5b --- /dev/null +++ b/src/test/documentation_examples.rs @@ -0,0 +1,1764 @@ +mod aggregation_data; + +use crate::bson::Document; +use futures::TryStreamExt; + +use crate::{ + bson::{doc, Bson}, + error::Result, + options::{ClientOptions, ServerApi, ServerApiVersion}, + test::{ + log_uncaptured, + server_version_lt, + server_version_matches, + topology_is_load_balanced, + topology_is_replica_set, + topology_is_sharded, + transactions_supported, + DEFAULT_URI, + }, + Client, + Collection, +}; + +macro_rules! assert_coll_count { + ($coll:expr, $expected:expr) => { + assert_eq!($coll.count_documents(doc! {}).await.unwrap(), $expected); + }; +} + +macro_rules! assert_cursor_count { + ($cursor:expr, $expected:expr) => {{ + let docs: Vec<_> = $cursor.try_collect().await.unwrap(); + assert_eq!(docs.len(), $expected); + }}; +} + +macro_rules! run_on_each_doc { + ($cursor:expr, $name:ident, $check:block) => {{ + let mut cursor = $cursor; + + while let Some($name) = cursor.try_next().await.unwrap() $check; + }}; +} + +async fn insert_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 1 + collection + .insert_one(doc! { + "item": "canvas", + "qty": 100, + "tags": ["cotton"], + "size": { + "h": 28, + "w": 35.5, + "uom": "cm", + } + }) + .await?; + // End Example 1 + + assert_coll_count!(collection, 1); + + // Start Example 2 + let cursor = collection.find(doc! { "item": "canvas" }).await?; + // End Example 2 + + assert_cursor_count!(cursor, 1); + + // Start Example 3 + let docs = vec![ + doc! { + "item": "journal", + "qty": 25, + "tags": ["blank", "red"], + "size": { + "h": 14, + "w": 21, + "uom": "cm" + } + }, + doc! { + "item": "mat", + "qty": 85, + "tags": ["gray"], + "size":{ + "h": 27.9, + "w": 35.5, + "uom": "cm" + } + }, + doc! { + "item": "mousepad", + "qty": 25, + "tags": ["gel", "blue"], + "size": { + "h": 19, + "w": 22.85, + "uom": "cm" + } + }, + ]; + + collection.insert_many(docs).await?; + // End Example 3 + + assert_coll_count!(collection, 4); + + Ok(()) +} + +async fn query_top_level_fields_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 6 + let docs = vec![ + doc! { + "item": "journal", + "qty": 25, + "size": { + "h": 14, + "w": 21, + "uom": "cm" + }, + "status": "A" + }, + doc! { + "item": "notebook", + "qty": 50, + "size": { + "h": 8.5, + "w": 11, + "uom": "in" + }, + "status": "A" + }, + doc! { + "item": "paper", + "qty": 100, + "size": { + "h": 8.5, + "w": 11, + "uom": "in" + }, + "status": "D" + }, + doc! { + "item": "planner", + "qty": 75, + "size": { + "h": 22.85, + "w": 30, + "uom": "cm" + }, + "status": "D" + }, + doc! { + "item": "postcard", + "qty": 45, + "size": { + "h": 10, + "w": 15.25, + "uom": "cm" + }, + "status": "A" + }, + ]; + + collection.insert_many(docs).await?; + // End Example 6 + + assert_coll_count!(collection, 5); + + // Start Example 7 + let cursor = collection.find(doc! {}).await?; + // End Example 7 + + assert_cursor_count!(cursor, 5); + + // Start Example 9 + let cursor = collection.find(doc! { "status": "D" }).await?; + // End Example 9 + + assert_cursor_count!(cursor, 2); + + // Start Example 10 + let cursor = collection + .find(doc! { + "status": { + "$in": ["A", "D"], + } + }) + .await?; + // End Example 10 + + assert_cursor_count!(cursor, 5); + + // Start Example 11 + let cursor = collection + .find(doc! { + "status": "A", + "qty": { "$lt": 30 }, + }) + .await?; + // End Example 11 + + assert_cursor_count!(cursor, 1); + + // Start Example 12 + let cursor = collection + .find(doc! { + "$or": [ + { "status": "A" }, + { + "qty": { "$lt": 30 }, + } + ], + }) + .await?; + // End Example 12 + + assert_cursor_count!(cursor, 3); + + // Start Example 13 + let cursor = collection + .find(doc! { + "status": "A", + "$or": [ + { + "qty": { "$lt": 30 }, + }, + { + "item": { "$regex": "^p" }, + }, + ], + }) + .await?; + // End Example 13 + + assert_cursor_count!(cursor, 2); + + Ok(()) +} + +async fn query_embedded_documents_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 14 + let docs = vec![ + doc! { + "item": "journal", + "qty": 25, + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "notebook", + "qty": 50, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "A", + }, + doc! { + "item": "paper", + "qty": 100, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "D" + }, + doc! { + "item": "planner", + "qty": 75, + "size": { + "h": 22.85, + "w": 30, + "uom": "cm", + }, + "status": "D" + }, + doc! { + "item": "postcard", + "qty": 45, + "size": { + "h": 10, + "w": 15.25, + "uom": "cm" + }, + "status": "A", + }, + ]; + + collection.insert_many(docs).await?; + // End Example 14 + + assert_coll_count!(collection, 5); + + // Start Example 15 + let cursor = collection + .find(doc! { + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + }) + .await?; + // End Example 15 + + assert_cursor_count!(cursor, 1); + + // Start Example 16 + let cursor = collection + .find(doc! { + "size": { + "w": 21, + "h": 14, + "uom": "cm", + }, + }) + .await?; + // End Example 16 + + assert_cursor_count!(cursor, 0); + + // Start Example 17 + let cursor = collection.find(doc! { "size.uom": "in" }).await?; + // End Example 17 + + assert_cursor_count!(cursor, 2); + + // Start Example 18 + let cursor = collection + .find(doc! { + "size.h": { "$lt": 15 }, + }) + .await?; + // End Example 18 + + assert_cursor_count!(cursor, 4); + + // Start Example 19 + let cursor = collection + .find(doc! { + "size.h": { "$lt": 15 }, + "size.uom": "in", + "status": "D", + }) + .await?; + // End Example 19 + + assert_cursor_count!(cursor, 1); + + Ok(()) +} + +async fn query_arrays_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 20 + let docs = vec![ + doc! { + "item": "journal", + "qty": 25, + "tags": ["blank", "red"], + "dim_cm": [14, 21], + }, + doc! { + "item": "notebook", + "qty": 50, + "tags": ["red", "blank"], + "dim_cm": [14, 21], + }, + doc! { + "item": "paper", + "qty": 100, + "tags": ["red", "blank", "plain"], + "dim_cm": [14, 21], + }, + doc! { + "item": "planner", + "qty": 75, + "tags": ["blank", "red"], + "dim_cm": [22.85, 30], + }, + doc! { + "item": "postcard", + "qty": 45, + "tags": ["blue"], + "dim_cm": [10, 15.25], + }, + ]; + + collection.insert_many(docs).await?; + // End Example 20 + + assert_coll_count!(collection, 5); + + // Start Example 21 + let cursor = collection + .find(doc! { + "tags": ["red", "blank"], + }) + .await?; + // End Example 21 + + assert_cursor_count!(cursor, 1); + + // Start Example 22 + let cursor = collection + .find(doc! { + "tags": { + "$all": ["red", "blank"], + } + }) + .await?; + // End Example 22 + + assert_cursor_count!(cursor, 4); + + // Start Example 23 + let cursor = collection + .find(doc! { + "tags": "red", + }) + .await?; + // End Example 23 + + assert_cursor_count!(cursor, 4); + + // Start Example 24 + let cursor = collection + .find(doc! { + "dim_cm": { "$gt": 25 }, + }) + .await?; + // End Example 24 + + assert_cursor_count!(cursor, 1); + + // Start Example 25 + let cursor = collection + .find(doc! { + "dim_cm": { + "$gt": 15, + "$lt": 20, + }, + }) + .await?; + // End Example 25 + + assert_cursor_count!(cursor, 4); + + // Start Example 26 + let cursor = collection + .find(doc! { + "dim_cm": { + "$elemMatch": { + "$gt": 22, + "$lt": 30, + } + }, + }) + .await?; + // End Example 26 + + assert_cursor_count!(cursor, 1); + + // Start Example 27 + let cursor = collection + .find(doc! { + "dim_cm.1": { "$gt": 25 }, + }) + .await?; + // End Example 27 + + assert_cursor_count!(cursor, 1); + + // Start Example 28 + let cursor = collection + .find(doc! { + "tags": { "$size": 3 }, + }) + .await?; + // End Example 28 + + assert_cursor_count!(cursor, 1); + + Ok(()) +} + +async fn query_array_embedded_documents_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 29 + let docs = vec![ + doc! { + "item": "journal", + "instock": [ + { "warehouse": "A", "qty": 5 }, + { "warehouse": "C", "qty": 15 } + ] + }, + doc! { + "item": "notebook", + "instock": [{ "warehouse": "C", "qty": 5 }] + }, + doc! { + "item": "paper", + "instock": [ + { "warehouse": "A", "qty": 60 }, + { "warehouse": "B", "qty": 15 } + ] + }, + doc! { + "item": "planner", + "instock": [ + { "warehouse": "A", "qty": 40 }, + { "warehouse": "B", "qty": 5 } + ] + }, + doc! { + "item": "postcard", + "instock": [ + { "warehouse": "B", "qty": 15 }, + { "warehouse": "C", "qty": 35 } + ] + }, + ]; + + collection.insert_many(docs).await?; + // End Example 29 + + assert_coll_count!(collection, 5); + + // Start Example 30 + let cursor = collection + .find(doc! { + "instock": { + "warehouse": "A", + "qty": 5, + }, + }) + .await?; + // End Example 30 + + assert_cursor_count!(cursor, 1); + + // Start Example 31 + let cursor = collection + .find(doc! { + "instock": { + "qty": 5, + "warehouse": "A", + }, + }) + .await?; + // End Example 31 + + assert_cursor_count!(cursor, 0); + + // Start Example 32 + let cursor = collection + .find(doc! { + "instock.0.qty": { "$lte": 20 }, + }) + .await?; + // End Example 32 + + assert_cursor_count!(cursor, 3); + + // Start Example 33 + let cursor = collection + .find(doc! { + "instock.qty": { "$lte": 20 }, + }) + .await?; + // End Example 33 + + assert_cursor_count!(cursor, 5); + + // Start Example 34 + let cursor = collection + .find(doc! { + "instock": { + "$elemMatch": { + "qty": 5, + "warehouse": "A", + } + }, + }) + .await?; + // End Example 34 + + assert_cursor_count!(cursor, 1); + + // Start Example 35 + let cursor = collection + .find(doc! { + "instock": { + "$elemMatch": { + "qty": { + "$gt": 10, + "$lte": 20, + } + } + }, + }) + .await?; + // End Example 35 + + assert_cursor_count!(cursor, 3); + + // Start Example 36 + let cursor = collection + .find(doc! { + "instock.qty": { + "$gt": 10, + "$lte": 20, + }, + }) + .await?; + // End Example 36 + + assert_cursor_count!(cursor, 4); + + // Start Example 37 + let cursor = collection + .find(doc! { + "instock.qty": 5, + "instock.warehouse": "A", + }) + .await?; + // End Example 37 + + assert_cursor_count!(cursor, 2); + + Ok(()) +} + +async fn query_null_or_missing_fields_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 38 + let docs = vec![ + doc! { + "_id": 1, + "item": Bson::Null, + }, + doc! { + "_id": 2, + }, + ]; + + collection.insert_many(docs).await?; + // End Example 38 + + assert_coll_count!(collection, 2); + + // Start Example 39 + let cursor = collection + .find(doc! { + "item": Bson::Null, + }) + .await?; + // End Example 39 + + assert_cursor_count!(cursor, 2); + + // Start Example 40 + let cursor = collection + .find(doc! { + "item": { "$type": 10 }, + }) + .await?; + // End Example 40 + + assert_cursor_count!(cursor, 1); + + // Start Example 41 + let cursor = collection + .find(doc! { + "item": { "$exists": false }, + }) + .await?; + // End Example 41 + + assert_cursor_count!(cursor, 1); + + Ok(()) +} + +async fn projection_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 42 + let docs = vec![ + doc! { + "item": "journal", + "status": "A", + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + "instock": [ + { + "warehouse": "A", + "qty": 5, + }, + ], + }, + doc! { + "item": "notebook", + "status": "A", + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "instock": [ + { + "warehouse": + "C", + "qty": 5, + }, + ] + }, + doc! { + "item": "paper", + "status": "D", + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "instock": [ + { + "warehouse": "A", + "qty": 60, + }, + ], + }, + doc! { + "item": "planner", + "status": "D", + "size": { + "h": 22.85, + "w": 30, + "uom": "cm", + }, + "instock": [ + { + "warehouse": "A", + "qty": 40, + }, + ], + }, + doc! { + "item": "postcard", + "status": "A", + "size": { + "h": 10, + "w": 15.25, + "uom": "cm", + }, + "instock": [ + { + "warehouse": "B", + "qty": 15, + }, + { + "warehouse": "C", + "qty": 35, + }, + ], + }, + ]; + + collection.insert_many(docs).await?; + // End Example 42 + + assert_coll_count!(collection, 5); + + // Start Example 43 + let cursor = collection + .find(doc! { + "status": "A", + }) + .await?; + // End Example 43 + + assert_cursor_count!(cursor, 3); + + // Start Example 44 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "item": 1, + "status": 1, + }) + .await?; + // End Example 44 + + run_on_each_doc!(cursor, doc, { + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("status")); + assert!(!doc.contains_key("size")); + assert!(!doc.contains_key("instock")); + }); + + // Start Example 45 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "item": 1, + "status": 1, + "_id": 0, + }) + .await?; + // End Example 45 + + run_on_each_doc!(cursor, doc, { + assert!(!doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("status")); + assert!(!doc.contains_key("size")); + assert!(!doc.contains_key("instock")); + }); + + // Start Example 46 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "status": 0, + "instock": 0, + }) + .await?; + // End Example 46 + + run_on_each_doc!(cursor, doc, { + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(!doc.contains_key("status")); + assert!(doc.contains_key("size")); + assert!(!doc.contains_key("instock")); + }); + + // Start Example 47 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "item": 1, + "status": 1, + "size.uom": 1, + }) + .await?; + // End Example 47 + + run_on_each_doc!(cursor, doc, { + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("status")); + assert!(doc.contains_key("size")); + assert!(!doc.contains_key("instock")); + + let size = doc.get_document("size").unwrap(); + + assert!(size.contains_key("uom")); + assert!(!size.contains_key("h")); + assert!(!size.contains_key("w")); + }); + + // Start Example 48 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "size.uom": 0, + }) + .await?; + // End Example 48 + + run_on_each_doc!(cursor, doc, { + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("status")); + assert!(doc.contains_key("size")); + assert!(doc.contains_key("instock")); + + let size = doc.get_document("size").unwrap(); + + assert!(!size.contains_key("uom")); + assert!(size.contains_key("h")); + assert!(size.contains_key("w")); + }); + + // Start Example 50 + let cursor = collection + .find(doc! { + "status": "A", + }) + .projection(doc! { + "item": 1, + "status": 1, + "instock": { "$slice": -1 }, + }) + .await?; + // End Example 50 + + run_on_each_doc!(cursor, doc, { + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("status")); + assert!(!doc.contains_key("size")); + assert!(doc.contains_key("instock")); + + let instock = doc.get_array("instock").unwrap(); + + assert_eq!(instock.len(), 1); + }); + + Ok(()) +} + +async fn update_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 51 + let docs = vec![ + doc! { + "item": "canvas", + "qty": 100, + "size": { + "h": 28, + "w": 35.5, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "journal", + "qty": 25, + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "mat", + "qty": 85, + "size": { + "h": 27.9, + "w": 35.5, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "mousepad", + "qty": 25, + "size": { + "h": 19, + "w": 22.85, + "uom": "cm", + }, + "status": "P", + }, + doc! { + "item": "notebook", + "qty": 50, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "P", + }, + doc! { + "item": "paper", + "qty": 100, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "D", + }, + doc! { + "item": "planner", + "qty": 75, + "size": { + "h": 22.85, + "w": 30, + "uom": "cm", + }, + "status": "D", + }, + doc! { + "item": "postcard", + "qty": 45, + "size": { + "h": 10, + "w": 15.25, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "sketchbook", + "qty": 80, + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "sketch pad", + "qty": 95, + "size": { + "h": 22.85, + "w": 30.5, + "uom": "cm", + }, + "status": "A", + }, + ]; + + collection.insert_many(docs).await?; + // End Example 51 + + assert_coll_count!(collection, 10); + + // Start Example 52 + collection + .update_one( + doc! { "item": "paper" }, + doc! { + "$set": { + "size.uom": "cm", + "status": "P", + }, + "$currentDate": { "lastModified": true }, + }, + ) + .await?; + // End Example 52 + + run_on_each_doc!( + collection.find(doc! { "item": "paper" }).await.unwrap(), + doc, + { + let uom = doc.get_document("size").unwrap().get_str("uom").unwrap(); + assert_eq!(uom, "cm"); + + let status = doc.get_str("status").unwrap(); + assert_eq!(status, "P"); + + assert!(doc.contains_key("lastModified")); + } + ); + + // Start Example 53 + collection + .update_many( + doc! { + "qty": { "$lt": 50 }, + }, + doc! { + "$set": { + "size.uom": "in", + "status": "P", + }, + "$currentDate": { "lastModified": true }, + }, + ) + .await?; + // End Example 53 + + run_on_each_doc!( + collection + .find(doc! { + "qty": { "$lt": 50 }, + }) + .await + .unwrap(), + doc, + { + let uom = doc.get_document("size").unwrap().get_str("uom").unwrap(); + assert_eq!(uom, "in"); + + let status = doc.get_str("status").unwrap(); + assert_eq!(status, "P"); + + assert!(doc.contains_key("lastModified")); + } + ); + + // Start Example 54 + collection + .replace_one( + doc! { "item": "paper" }, + doc! { + "item": "paper", + "instock": [ + { + "warehouse": "A", + "qty": 60, + }, + { + "warehouse": "B", + "qty": 40, + }, + ], + }, + ) + .await?; + // End Example 54 + + run_on_each_doc!( + collection.find(doc! { "item": "paper" }).await.unwrap(), + doc, + { + assert_eq!(doc.len(), 3); + assert!(doc.contains_key("_id")); + assert!(doc.contains_key("item")); + assert!(doc.contains_key("instock")); + + let instock = doc.get_array("instock").unwrap(); + assert_eq!(instock.len(), 2); + } + ); + + Ok(()) +} + +async fn delete_examples(collection: &Collection) -> Result<()> { + collection.drop().await?; + + // Start Example 55 + let docs = vec![ + doc! { + "item": "journal", + "qty": 25, + "size": { + "h": 14, + "w": 21, + "uom": "cm", + }, + "status": "A", + }, + doc! { + "item": "notebook", + "qty": 50, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "P", + }, + doc! { + "item": "paper", + "qty": 100, + "size": { + "h": 8.5, + "w": 11, + "uom": "in", + }, + "status": "D", + }, + doc! { + "item": "planner", + "qty": 75, + "size": { + "h": 22.85, + "w": 30, + "uom": "cm", + }, + "status": "D", + }, + doc! { + "item": "postcard", + "qty": 45, + "size": { + "h": 10, + "w": 15.25, + "uom": "cm", + }, + "status": "A", + }, + ]; + + collection.insert_many(docs).await?; + // End Example 55 + + assert_coll_count!(collection, 5); + + // Start Example 57 + collection.delete_many(doc! { "status": "A" }).await?; + // End Example 57 + + assert_coll_count!(collection, 3); + + // Start Example 58 + collection.delete_one(doc! { "status": "D" }).await?; + // End Example 58 + + assert_coll_count!(collection, 2); + + // Start Example 56 + collection.delete_many(doc! {}).await?; + // End Example 56 + + assert_coll_count!(collection, 0); + + Ok(()) +} + +type GenericResult = std::result::Result>; + +#[allow(unused_variables)] +async fn stable_api_examples() -> GenericResult<()> { + if server_version_lt(4, 9).await { + log_uncaptured("skipping stable API examples due to unsupported server version"); + return Ok(()); + } + if topology_is_sharded().await && server_version_matches("<=5.0.2").await { + // See SERVER-58794. + log_uncaptured( + "skipping stable API examples due to unsupported server version on sharded topology", + ); + return Ok(()); + } + if topology_is_load_balanced().await { + log_uncaptured("skipping stable API examples due to load-balanced topology"); + return Ok(()); + } + + let setup_client = Client::for_test().await; + + let uri = DEFAULT_URI.clone(); + // Start Versioned API Example 1 + let mut options = ClientOptions::parse(&uri).await?; + let server_api = ServerApi::builder().version(ServerApiVersion::V1).build(); + options.server_api = Some(server_api); + let client = Client::with_options(options)?; + // End Versioned API Example 1 + + // Start Versioned API Example 2 + let mut options = ClientOptions::parse(&uri).await?; + let server_api = ServerApi::builder() + .version(ServerApiVersion::V1) + .strict(true) + .build(); + options.server_api = Some(server_api); + let client = Client::with_options(options)?; + // End Versioned API Example 2 + + // Start Versioned API Example 3 + let mut options = ClientOptions::parse(&uri).await?; + let server_api = ServerApi::builder() + .version(ServerApiVersion::V1) + .strict(false) + .build(); + options.server_api = Some(server_api); + let client = Client::with_options(options)?; + // End Versioned API Example 3 + + // Start Versioned API Example 4 + let mut options = ClientOptions::parse(&uri).await?; + let server_api = ServerApi::builder() + .version(ServerApiVersion::V1) + .deprecation_errors(true) + .build(); + options.server_api = Some(server_api); + let client = Client::with_options(options)?; + // End Versioned API Example 4 + + let mut options = ClientOptions::parse(&uri).await?; + let server_api = ServerApi::builder() + .version(ServerApiVersion::V1) + .strict(true) + .build(); + options.server_api = Some(server_api); + let client = Client::with_options(options)?; + let db = client.database("stable-api-migration-examples"); + db.collection::("sales").drop().await?; + + use std::{error::Error, result::Result}; + + // Start Versioned API Example 5 + // With the `bson-chrono-0_4` feature enabled, this function can be dropped in favor of using + // `chrono::DateTime` values directly. + fn iso_date(text: &str) -> Result> { + let chrono_dt = chrono::DateTime::parse_from_rfc3339(text)?; + Ok(crate::bson::DateTime::from_millis( + chrono_dt.timestamp_millis(), + )) + } + db.collection("sales").insert_many(vec![ + doc! { "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : iso_date("2021-01-01T08:00:00Z")? }, + doc! { "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : iso_date("2021-02-03T09:05:00Z")? }, + doc! { "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : iso_date("2021-02-03T09:00:00Z")? }, + doc! { "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10, "date" : iso_date("2021-02-15T08:00:00Z")? }, + doc! { "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : iso_date("2021-02-15T09:05:00Z")? }, + doc! { "_id" : 6, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : iso_date("2021-02-15T12:05:10Z")? }, + doc! { "_id" : 7, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : iso_date("2021-02-15T14:12:12Z")? }, + doc! { "_id" : 8, "item" : "abc", "price" : 10, "quantity" : 5, "date" : iso_date("2021-03-16T20:20:13Z")? } + ]).await?; + // End Versioned API Example 5 + + // Start Versioned API Example 6 + let result = db + .run_command(doc! { + "count": "sales" + }) + .await; + if let Err(err) = &result { + println!("{:#?}", err.kind); + // Prints: + // Command( + // CommandError { + // code: 323, + // code_name: "APIStrictError", + // message: "Provided apiStrict:true, but the command count is not in API Version 1. Information on supported commands and migrations in API Version 1 can be found at https://www.mongodb.com/docs/v5.0/reference/stable-api/", + // }, + // ) + } + // End Versioned API Example 6 + + // TODO: Uncomment or remove this test once the prior example is updated + // if let ErrorKind::Command(ref err) = *result.as_ref().unwrap_err().kind { + // assert_eq!(err.code, 323); + // assert_eq!(err.code_name, "APIStrictError".to_string()); + // } else { + // panic!("invalid result {:?}", result); + // }; + + // Start Versioned API Example 7 + let count = db + .collection::("sales") + .count_documents(doc! {}) + .await?; + // End Versioned API Example 7 + + // Start Versioned API Example 8 + assert_eq!(count, 8); + // End Versioned API Example 8 + + Ok(()) +} + +#[allow(unused_imports)] +async fn aggregation_examples() -> GenericResult<()> { + let client = Client::for_test().await; + let db = client.database("aggregation_examples"); + db.drop().await?; + aggregation_data::populate(&db).await?; + + // Each example is within its own scope to allow the example to include + // `use futures::TryStreamExt;` without causing multiple definition errors. + + { + // Start Aggregation Example 1 + use futures::TryStreamExt; + let cursor = db + .collection::("sales") + .aggregate(vec![ + doc! { "$match": { "items.fruit": "banana" } }, + doc! { "$sort": { "date": 1 } }, + ]) + .await?; + let values: Vec<_> = cursor.try_collect().await?; + // End Aggregation Example 1 + assert_eq!(5, values.len()); + } + + { + // Start Aggregation Example 2 + use futures::TryStreamExt; + let cursor = db + .collection::("sales") + .aggregate(vec![ + doc! { + "$unwind": "$items" + }, + doc! { "$match": { + "items.fruit": "banana", + }}, + doc! { + "$group": { + "_id": { "day": { "$dayOfWeek": "$date" } }, + "count": { "$sum": "$items.quantity" } + } + }, + doc! { + "$project": { + "dayOfWeek": "$_id.day", + "numberSold": "$count", + "_id": 0 + } + }, + doc! { + "$sort": { "numberSold": 1 } + }, + ]) + .await?; + let values: Vec<_> = cursor.try_collect().await?; + // End Aggregation Example 2 + assert_eq!(4, values.len()); + } + + { + // Start Aggregation Example 3 + use futures::TryStreamExt; + let cursor = db.collection::("sales").aggregate( + vec![ + doc! { + "$unwind": "$items" + }, + doc! { + "$group": { + "_id": { "day": { "$dayOfWeek": "$date" } }, + "items_sold": { "$sum": "$items.quantity" }, + "revenue": { "$sum": { "$multiply": [ "$items.quantity", "$items.price" ] } } + } + }, + doc! { + "$project": { + "day": "$_id.day", + "revenue": 1, + "items_sold": 1, + "discount": { + "$cond": { "if": { "$lte": [ "$revenue", 250 ] }, "then": 25, "else": 0 } + } + } + }, + ] + ).await?; + let values: Vec<_> = cursor.try_collect().await?; + // End Aggregation Example 3 + assert_eq!(4, values.len()); + } + + { + // Start Aggregation Example 4 + use futures::TryStreamExt; + let cursor = db + .collection::("air_alliances") + .aggregate(vec![ + doc! { + "$lookup": { + "from": "air_airlines", + "let": { "constituents": "$airlines" }, + "pipeline": [ + { + "$match": { "$expr": { "$in": [ "$name", "$$constituents" ] } } + } + ], + "as": "airlines" + } + }, + doc! { + "$project": { + "_id": 0, + "name": 1, + "airlines": { + "$filter": { + "input": "$airlines", + "as": "airline", + "cond": { "$eq": ["$$airline.country", "Canada"] } + } + } + } + }, + ]) + .await?; + let values: Vec<_> = cursor.try_collect().await?; + // End Aggregation Example 4 + assert_eq!(3, values.len()); + } + + Ok(()) +} + +async fn run_command_examples() -> Result<()> { + let client = Client::for_test().await; + let db = client.database("run_command_examples"); + db.drop().await?; + db.collection::("restaurants") + .insert_one(doc! { + "name": "Chez Panisse", + "city": "Oakland", + "state": "California", + "country": "United States", + "rating": 4.4, + }) + .await?; + + #[allow(unused)] + // Start runCommand Example 1 + let info = db.run_command(doc! {"buildInfo": 1}).await?; + // End runCommand Example 1 + + #[allow(unused)] + // Start runCommand Example 2 + let stats = db.run_command(doc! {"collStats": "restaurants"}).await?; + // End runCommand Example 2 + + Ok(()) +} + +async fn index_examples() -> Result<()> { + let client = Client::for_test().await; + let db = client.database("index_examples"); + db.drop().await?; + db.collection::("records") + .insert_many(vec![ + doc! { + "student": "Marty McFly", + "classYear": 1986, + "school": "Hill Valley High", + "score": 56.5, + }, + doc! { + "student": "Ferris F. Bueller", + "classYear": 1987, + "school": "Glenbrook North High", + "status": "Suspended", + "score": 76.0, + }, + ]) + .await?; + db.collection::("restaurants") + .insert_many(vec![ + doc! { + "name": "Chez Panisse", + "city": "Oakland", + "state": "California", + "country": "United States", + "rating": 4.4, + }, + doc! { + "name": "Eleven Madison Park", + "cuisine": "French", + "city": "New York City", + "state": "New York", + "country": "United States", + "rating": 7.1, + }, + ]) + .await?; + + use crate::IndexModel; + // Start Index Example 1 + db.collection::("records") + .create_index(IndexModel::builder().keys(doc! { "score": 1 }).build()) + .await?; + // End Index Example 1 + + use crate::options::IndexOptions; + // Start Index Example 2 + db.collection::("records") + .create_index( + IndexModel::builder() + .keys(doc! { "cuisine": 1, "name": 1 }) + .options( + IndexOptions::builder() + .partial_filter_expression(doc! { "rating": { "$gt": 5 } }) + .build(), + ) + .build(), + ) + .await?; + // End Index Example 2 + + Ok(()) +} + +#[allow(unused_variables)] +async fn change_streams_examples() -> Result<()> { + use crate::{options::FullDocumentType, runtime}; + use std::time::Duration; + + if !topology_is_replica_set().await && !topology_is_sharded().await { + log_uncaptured("skipping change_streams_examples due to unsupported topology"); + return Ok(()); + } + + let client = Client::for_test().await; + let db = client.database("change_streams_examples"); + db.drop().await?; + let inventory = db.collection::("inventory"); + // Populate an item so the collection exists for the change stream to watch. + inventory.insert_one(doc! {}).await?; + + // Background writer thread so that the `stream.next()` calls return something. + let (tx, mut rx) = tokio::sync::oneshot::channel(); + let writer_inventory = inventory.clone(); + let handle = runtime::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_millis(100)); + loop { + tokio::select! { + _ = interval.tick() => { + writer_inventory.insert_one(doc! {}).await?; + } + _ = &mut rx => break, + } + } + Result::Ok(()) + }); + + #[allow(unused_variables, unused_imports)] + { + { + // Start Changestream Example 1 + use futures::stream::TryStreamExt; + + let mut stream = inventory.watch().await?; + let next = stream.try_next().await?; + // End Changestream Example 1 + } + + { + // Start Changestream Example 2 + use futures::stream::TryStreamExt; + let mut stream = inventory + .watch() + .full_document(FullDocumentType::UpdateLookup) + .await?; + let next = stream.try_next().await?; + // End Changestream Example 2 + } + + { + let stream = inventory.watch().await?; + // Start Changestream Example 3 + use futures::stream::TryStreamExt; + let resume_token = stream.resume_token(); + let mut stream = inventory.watch().resume_after(resume_token).await?; + stream.try_next().await?; + // End Changestream Example 3 + } + } + + // Shut down the writer thread. + let _ = tx.send(()); + handle.await?; + + Ok(()) +} + +async fn convenient_transaction_examples() -> Result<()> { + use crate::ClientSession; + if !transactions_supported().await { + log_uncaptured( + "skipping convenient transaction API examples due to no transaction support", + ); + return Ok(()); + } + + let client = Client::for_test().await; + // Start Transactions withTxn API Example 1 + + // Prereq: Create collections. CRUD operations in transactions must be on existing collections. + + client + .database("mydb1") + .collection::("foo") + .insert_one(doc! { "abc": 0}) + .await?; + client + .database("mydb2") + .collection::("bar") + .insert_one(doc! { "xyz": 0}) + .await?; + + // Step 1: Define the callback that specifies the sequence of operations to perform inside the + // transaction. + async fn callback(session: &mut ClientSession) -> Result<()> { + let collection_one = session + .client() + .database("mydb1") + .collection::("foo"); + let collection_two = session + .client() + .database("mydb2") + .collection::("bar"); + + // Important: You must pass the session to the operations. + collection_one + .insert_one(doc! { "abc": 1 }) + .session(&mut *session) + .await?; + collection_two + .insert_one(doc! { "xyz": 999 }) + .session(session) + .await?; + + Ok(()) + } + + // Step 2: Start a client session. + let mut session = client.start_session().await?; + + // Step 3: Use and_run2 to start a transaction, execute the callback, and commit (or + // abort on error). + session.start_transaction().and_run2(callback).await?; + + // End Transactions withTxn API Example 1 + + Ok(()) +} + +#[tokio::test] +async fn test() { + let client = Client::for_test().await; + let coll = client + .database("documentation_examples") + .collection("inventory"); + + insert_examples(&coll).await.unwrap(); + query_top_level_fields_examples(&coll).await.unwrap(); + query_embedded_documents_examples(&coll).await.unwrap(); + query_arrays_examples(&coll).await.unwrap(); + query_array_embedded_documents_examples(&coll) + .await + .unwrap(); + query_null_or_missing_fields_examples(&coll).await.unwrap(); + projection_examples(&coll).await.unwrap(); + update_examples(&coll).await.unwrap(); + delete_examples(&coll).await.unwrap(); + stable_api_examples().await.unwrap(); + aggregation_examples().await.unwrap(); + run_command_examples().await.unwrap(); + index_examples().await.unwrap(); + change_streams_examples().await.unwrap(); + convenient_transaction_examples().await.unwrap(); +} diff --git a/src/test/documentation_examples/aggregation_data.rs b/src/test/documentation_examples/aggregation_data.rs index d32476c0d..4added6f1 100644 --- a/src/test/documentation_examples/aggregation_data.rs +++ b/src/test/documentation_examples/aggregation_data.rs @@ -1,4 +1,4 @@ -use bson::{doc, DateTime}; +use crate::bson::{doc, DateTime}; use crate::Database; @@ -13,242 +13,233 @@ pub(crate) async fn populate(db: &Database) -> GenericResult<()> { let date_20180111 = DateTime::parse_rfc3339_str("2018-01-11T07:15:00.000Z")?; db.collection("sales") - .insert_many( - vec![ - doc! { - "date": date_20180208, - "items": [ - doc! { - "fruit": "kiwi", - "quantity": 2, - "price": 0.5, - }, - doc! { - "fruit": "apple", - "quantity": 1, - "price": 1.0, - }, - ], - }, - doc! { - "date": date_20180109, - "items": [ - doc! { - "fruit": "banana", - "quantity": 8, - "price": 1.0, - }, - doc! { - "fruit": "apple", - "quantity": 1, - "price": 1.0, - }, - doc! { - "fruit": "papaya", - "quantity": 1, - "price": 4.0, - }, - ], - }, - doc! { - "date": date_20180127, - "items": [ - doc! { - "fruit": "banana", - "quantity": 1, - "price": 1.0, - }, - ], - }, - doc! { - "date": date_20180203, - "items": [ - doc! { - "fruit": "banana", - "quantity": 1, - "price": 1.0, - }, - ], - }, - doc! { - "date": date_20180205, - "items": [ - doc! { - "fruit": "banana", - "quantity": 1, - "price": 1.0, - }, - doc! { - "fruit": "mango", - "quantity": 2, - "price": 2.0, - }, - doc! { - "fruit": "apple", - "quantity": 1, - "price": 1.0, - }, - ], - }, - doc! { - "date": date_20180111, - "items": [ - doc! { - "fruit": "banana", - "quantity": 1, - "price": 1.0, - }, - doc! { - "fruit": "apple", - "quantity": 1, - "price": 1.0, - }, - doc! { - "fruit": "papaya", - "quantity": 3, - "price": 4.0, - }, - ], - }, - ], - None, - ) + .insert_many(vec![ + doc! { + "date": date_20180208, + "items": [ + doc! { + "fruit": "kiwi", + "quantity": 2, + "price": 0.5, + }, + doc! { + "fruit": "apple", + "quantity": 1, + "price": 1.0, + }, + ], + }, + doc! { + "date": date_20180109, + "items": [ + doc! { + "fruit": "banana", + "quantity": 8, + "price": 1.0, + }, + doc! { + "fruit": "apple", + "quantity": 1, + "price": 1.0, + }, + doc! { + "fruit": "papaya", + "quantity": 1, + "price": 4.0, + }, + ], + }, + doc! { + "date": date_20180127, + "items": [ + doc! { + "fruit": "banana", + "quantity": 1, + "price": 1.0, + }, + ], + }, + doc! { + "date": date_20180203, + "items": [ + doc! { + "fruit": "banana", + "quantity": 1, + "price": 1.0, + }, + ], + }, + doc! { + "date": date_20180205, + "items": [ + doc! { + "fruit": "banana", + "quantity": 1, + "price": 1.0, + }, + doc! { + "fruit": "mango", + "quantity": 2, + "price": 2.0, + }, + doc! { + "fruit": "apple", + "quantity": 1, + "price": 1.0, + }, + ], + }, + doc! { + "date": date_20180111, + "items": [ + doc! { + "fruit": "banana", + "quantity": 1, + "price": 1.0, + }, + doc! { + "fruit": "apple", + "quantity": 1, + "price": 1.0, + }, + doc! { + "fruit": "papaya", + "quantity": 3, + "price": 4.0, + }, + ], + }, + ]) .await?; db.collection("airlines") - .insert_many( - vec![ - doc! { - "airline": 17, - "name": "Air Canada", - "alias": "AC", - "iata": "ACA", - "icao": "AIR CANADA", - "active": "Y", - "country": "Canada", - "base": "TAL", - }, - doc! { - "airline": 18, - "name": "Turkish Airlines", - "alias": "YK", - "iata": "TRK", - "icao": "TURKISH", - "active": "Y", - "country": "Turkey", - "base": "AET", - }, - doc! { - "airline": 22, - "name": "Saudia", - "alias": "SV", - "iata": "SVA", - "icao": "SAUDIA", - "active": "Y", - "country": "Saudi Arabia", - "base": "JSU", - }, - doc! { - "airline": 29, - "name": "Finnair", - "alias": "AY", - "iata": "FIN", - "icao": "FINNAIR", - "active": "Y", - "country": "Finland", - "base": "JMZ", - }, - doc! { - "airline": 34, - "name": "Afric'air Express", - "alias": "", - "iata": "AAX", - "icao": "AFREX", - "active": "N", - "country": "Ivory Coast", - "base": "LOK", - }, - doc! { - "airline": 37, - "name": "Artem-Avia", - "alias": "", - "iata": "ABA", - "icao": "ARTEM-AVIA", - "active": "N", - "country": "Ukraine", - "base": "JBR", - }, - doc! { - "airline": 38, - "name": "Lufthansa", - "alias": "LH", - "iata": "DLH", - "icao": "LUFTHANSA", - "active": "Y", - "country": "Germany", - "base": "CYS", - }, - ], - None, - ) + .insert_many(vec![ + doc! { + "airline": 17, + "name": "Air Canada", + "alias": "AC", + "iata": "ACA", + "icao": "AIR CANADA", + "active": "Y", + "country": "Canada", + "base": "TAL", + }, + doc! { + "airline": 18, + "name": "Turkish Airlines", + "alias": "YK", + "iata": "TRK", + "icao": "TURKISH", + "active": "Y", + "country": "Turkey", + "base": "AET", + }, + doc! { + "airline": 22, + "name": "Saudia", + "alias": "SV", + "iata": "SVA", + "icao": "SAUDIA", + "active": "Y", + "country": "Saudi Arabia", + "base": "JSU", + }, + doc! { + "airline": 29, + "name": "Finnair", + "alias": "AY", + "iata": "FIN", + "icao": "FINNAIR", + "active": "Y", + "country": "Finland", + "base": "JMZ", + }, + doc! { + "airline": 34, + "name": "Afric'air Express", + "alias": "", + "iata": "AAX", + "icao": "AFREX", + "active": "N", + "country": "Ivory Coast", + "base": "LOK", + }, + doc! { + "airline": 37, + "name": "Artem-Avia", + "alias": "", + "iata": "ABA", + "icao": "ARTEM-AVIA", + "active": "N", + "country": "Ukraine", + "base": "JBR", + }, + doc! { + "airline": 38, + "name": "Lufthansa", + "alias": "LH", + "iata": "DLH", + "icao": "LUFTHANSA", + "active": "Y", + "country": "Germany", + "base": "CYS", + }, + ]) .await?; db.collection("air_alliances") - .insert_many( - vec![ - doc! { - "name": "Star Alliance", - "airlines": [ - "Air Canada", - "Avianca", - "Air China", - "Air New Zealand", - "Asiana Airlines", - "Brussels Airlines", - "Copa Airlines", - "Croatia Airlines", - "EgyptAir", - "TAP Portugal", - "United Airlines", - "Turkish Airlines", - "Swiss International Air Lines", - "Lufthansa", - ], - }, - doc! { - "name": "SkyTeam", - "airlines": [ - "Aerolinias Argentinas", - "Aeromexico", - "Air Europa", - "Air France", - "Alitalia", - "Delta Air Lines", - "Garuda Indonesia", - "Kenya Airways", - "KLM", - "Korean Air", - "Middle East Airlines", - "Saudia", - ], - }, - doc! { - "name": "OneWorld", - "airlines": [ - "Air Berlin", - "American Airlines", - "British Airways", - "Cathay Pacific", - "Finnair", - "Iberia Airlines", - "Japan Airlines", - "LATAM Chile", - "LATAM Brasil", - "Malasya Airlines", - "Canadian Airlines", - ], - }, - ], - None, - ) + .insert_many(vec![ + doc! { + "name": "Star Alliance", + "airlines": [ + "Air Canada", + "Avianca", + "Air China", + "Air New Zealand", + "Asiana Airlines", + "Brussels Airlines", + "Copa Airlines", + "Croatia Airlines", + "EgyptAir", + "TAP Portugal", + "United Airlines", + "Turkish Airlines", + "Swiss International Air Lines", + "Lufthansa", + ], + }, + doc! { + "name": "SkyTeam", + "airlines": [ + "Aerolinias Argentinas", + "Aeromexico", + "Air Europa", + "Air France", + "Alitalia", + "Delta Air Lines", + "Garuda Indonesia", + "Kenya Airways", + "KLM", + "Korean Air", + "Middle East Airlines", + "Saudia", + ], + }, + doc! { + "name": "OneWorld", + "airlines": [ + "Air Berlin", + "American Airlines", + "British Airways", + "Cathay Pacific", + "Finnair", + "Iberia Airlines", + "Japan Airlines", + "LATAM Chile", + "LATAM Brasil", + "Malasya Airlines", + "Canadian Airlines", + ], + }, + ]) .await?; Ok(()) diff --git a/src/test/documentation_examples/mod.rs b/src/test/documentation_examples/mod.rs deleted file mode 100644 index 5e1932299..000000000 --- a/src/test/documentation_examples/mod.rs +++ /dev/null @@ -1,1931 +0,0 @@ -mod aggregation_data; - -use bson::Document; -use futures::TryStreamExt; -use semver::Version; -use tokio::sync::RwLockReadGuard; - -use crate::{ - bson::{doc, Bson}, - error::Result, - options::{ClientOptions, FindOptions, ServerApi, ServerApiVersion}, - test::{log_uncaptured, TestClient, DEFAULT_URI, LOCK}, - Client, - Collection, -}; - -macro_rules! assert_coll_count { - ($coll:expr, $expected:expr) => { - assert_eq!($coll.count_documents(None, None).await.unwrap(), $expected); - }; -} - -macro_rules! assert_cursor_count { - ($cursor:expr, $expected:expr) => {{ - let docs: Vec<_> = $cursor.try_collect().await.unwrap(); - assert_eq!(docs.len(), $expected); - }}; -} - -macro_rules! run_on_each_doc { - ($cursor:expr, $name:ident, $check:block) => {{ - let mut cursor = $cursor; - - while let Some($name) = cursor.try_next().await.unwrap() $check; - }}; -} - -async fn insert_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 1 - collection - .insert_one( - doc! { - "item": "canvas", - "qty": 100, - "tags": ["cotton"], - "size": { - "h": 28, - "w": 35.5, - "uom": "cm", - } - }, - None, - ) - .await?; - // End Example 1 - - assert_coll_count!(collection, 1); - - // Start Example 2 - let cursor = collection.find(doc! { "item": "canvas" }, None).await?; - // End Example 2 - - assert_cursor_count!(cursor, 1); - - // Start Example 3 - let docs = vec![ - doc! { - "item": "journal", - "qty": 25, - "tags": ["blank", "red"], - "size": { - "h": 14, - "w": 21, - "uom": "cm" - } - }, - doc! { - "item": "mat", - "qty": 85, - "tags": ["gray"], - "size":{ - "h": 27.9, - "w": 35.5, - "uom": "cm" - } - }, - doc! { - "item": "mousepad", - "qty": 25, - "tags": ["gel", "blue"], - "size": { - "h": 19, - "w": 22.85, - "uom": "cm" - } - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 3 - - assert_coll_count!(collection, 4); - - Ok(()) -} - -async fn query_top_level_fields_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 6 - let docs = vec![ - doc! { - "item": "journal", - "qty": 25, - "size": { - "h": 14, - "w": 21, - "uom": "cm" - }, - "status": "A" - }, - doc! { - "item": "notebook", - "qty": 50, - "size": { - "h": 8.5, - "w": 11, - "uom": "in" - }, - "status": "A" - }, - doc! { - "item": "paper", - "qty": 100, - "size": { - "h": 8.5, - "w": 11, - "uom": "in" - }, - "status": "D" - }, - doc! { - "item": "planner", - "qty": 75, - "size": { - "h": 22.85, - "w": 30, - "uom": "cm" - }, - "status": "D" - }, - doc! { - "item": "postcard", - "qty": 45, - "size": { - "h": 10, - "w": 15.25, - "uom": "cm" - }, - "status": "A" - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 6 - - assert_coll_count!(collection, 5); - - // Start Example 7 - let cursor = collection.find(None, None).await?; - // End Example 7 - - assert_cursor_count!(cursor, 5); - - // Start Example 9 - let cursor = collection.find(doc! { "status": "D" }, None).await?; - // End Example 9 - - assert_cursor_count!(cursor, 2); - - // Start Example 10 - let cursor = collection - .find( - doc! { - "status": { - "$in": ["A", "D"], - } - }, - None, - ) - .await?; - // End Example 10 - - assert_cursor_count!(cursor, 5); - - // Start Example 11 - let cursor = collection - .find( - doc! { - "status": "A", - "qty": { "$lt": 30 }, - }, - None, - ) - .await?; - // End Example 11 - - assert_cursor_count!(cursor, 1); - - // Start Example 12 - let cursor = collection - .find( - doc! { - "$or": [ - { "status": "A" }, - { - "qty": { "$lt": 30 }, - } - ], - }, - None, - ) - .await?; - // End Example 12 - - assert_cursor_count!(cursor, 3); - - // Start Example 13 - let cursor = collection - .find( - doc! { - "status": "A", - "$or": [ - { - "qty": { "$lt": 30 }, - }, - { - "item": { "$regex": "^p" }, - }, - ], - }, - None, - ) - .await?; - // End Example 13 - - assert_cursor_count!(cursor, 2); - - Ok(()) -} - -async fn query_embedded_documents_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 14 - let docs = vec![ - doc! { - "item": "journal", - "qty": 25, - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "notebook", - "qty": 50, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "A", - }, - doc! { - "item": "paper", - "qty": 100, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "D" - }, - doc! { - "item": "planner", - "qty": 75, - "size": { - "h": 22.85, - "w": 30, - "uom": "cm", - }, - "status": "D" - }, - doc! { - "item": "postcard", - "qty": 45, - "size": { - "h": 10, - "w": 15.25, - "uom": "cm" - }, - "status": "A", - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 14 - - assert_coll_count!(collection, 5); - - // Start Example 15 - let cursor = collection - .find( - doc! { - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - }, - None, - ) - .await?; - // End Example 15 - - assert_cursor_count!(cursor, 1); - - // Start Example 16 - let cursor = collection - .find( - doc! { - "size": { - "w": 21, - "h": 14, - "uom": "cm", - }, - }, - None, - ) - .await?; - // End Example 16 - - assert_cursor_count!(cursor, 0); - - // Start Example 17 - let cursor = collection.find(doc! { "size.uom": "in" }, None).await?; - // End Example 17 - - assert_cursor_count!(cursor, 2); - - // Start Example 18 - let cursor = collection - .find( - doc! { - "size.h": { "$lt": 15 }, - }, - None, - ) - .await?; - // End Example 18 - - assert_cursor_count!(cursor, 4); - - // Start Example 19 - let cursor = collection - .find( - doc! { - "size.h": { "$lt": 15 }, - "size.uom": "in", - "status": "D", - }, - None, - ) - .await?; - // End Example 19 - - assert_cursor_count!(cursor, 1); - - Ok(()) -} - -async fn query_arrays_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 20 - let docs = vec![ - doc! { - "item": "journal", - "qty": 25, - "tags": ["blank", "red"], - "dim_cm": [14, 21], - }, - doc! { - "item": "notebook", - "qty": 50, - "tags": ["red", "blank"], - "dim_cm": [14, 21], - }, - doc! { - "item": "paper", - "qty": 100, - "tags": ["red", "blank", "plain"], - "dim_cm": [14, 21], - }, - doc! { - "item": "planner", - "qty": 75, - "tags": ["blank", "red"], - "dim_cm": [22.85, 30], - }, - doc! { - "item": "postcard", - "qty": 45, - "tags": ["blue"], - "dim_cm": [10, 15.25], - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 20 - - assert_coll_count!(collection, 5); - - // Start Example 21 - let cursor = collection - .find( - doc! { - "tags": ["red", "blank"], - }, - None, - ) - .await?; - // End Example 21 - - assert_cursor_count!(cursor, 1); - - // Start Example 22 - let cursor = collection - .find( - doc! { - "tags": { - "$all": ["red", "blank"], - } - }, - None, - ) - .await?; - // End Example 22 - - assert_cursor_count!(cursor, 4); - - // Start Example 23 - let cursor = collection - .find( - doc! { - "tags": "red", - }, - None, - ) - .await?; - // End Example 23 - - assert_cursor_count!(cursor, 4); - - // Start Example 24 - let cursor = collection - .find( - doc! { - "dim_cm": { "$gt": 25 }, - }, - None, - ) - .await?; - // End Example 24 - - assert_cursor_count!(cursor, 1); - - // Start Example 25 - let cursor = collection - .find( - doc! { - "dim_cm": { - "$gt": 15, - "$lt": 20, - }, - }, - None, - ) - .await?; - // End Example 25 - - assert_cursor_count!(cursor, 4); - - // Start Example 26 - let cursor = collection - .find( - doc! { - "dim_cm": { - "$elemMatch": { - "$gt": 22, - "$lt": 30, - } - }, - }, - None, - ) - .await?; - // End Example 26 - - assert_cursor_count!(cursor, 1); - - // Start Example 27 - let cursor = collection - .find( - doc! { - "dim_cm.1": { "$gt": 25 }, - }, - None, - ) - .await?; - // End Example 27 - - assert_cursor_count!(cursor, 1); - - // Start Example 28 - let cursor = collection - .find( - doc! { - "tags": { "$size": 3 }, - }, - None, - ) - .await?; - // End Example 28 - - assert_cursor_count!(cursor, 1); - - Ok(()) -} - -async fn query_array_embedded_documents_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 29 - let docs = vec![ - doc! { - "item": "journal", - "instock": [ - { "warehouse": "A", "qty": 5 }, - { "warehouse": "C", "qty": 15 } - ] - }, - doc! { - "item": "notebook", - "instock": [{ "warehouse": "C", "qty": 5 }] - }, - doc! { - "item": "paper", - "instock": [ - { "warehouse": "A", "qty": 60 }, - { "warehouse": "B", "qty": 15 } - ] - }, - doc! { - "item": "planner", - "instock": [ - { "warehouse": "A", "qty": 40 }, - { "warehouse": "B", "qty": 5 } - ] - }, - doc! { - "item": "postcard", - "instock": [ - { "warehouse": "B", "qty": 15 }, - { "warehouse": "C", "qty": 35 } - ] - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 29 - - assert_coll_count!(collection, 5); - - // Start Example 30 - let cursor = collection - .find( - doc! { - "instock": { - "warehouse": "A", - "qty": 5, - }, - }, - None, - ) - .await?; - // End Example 30 - - assert_cursor_count!(cursor, 1); - - // Start Example 31 - let cursor = collection - .find( - doc! { - "instock": { - "qty": 5, - "warehouse": "A", - }, - }, - None, - ) - .await?; - // End Example 31 - - assert_cursor_count!(cursor, 0); - - // Start Example 32 - let cursor = collection - .find( - doc! { - "instock.0.qty": { "$lte": 20 }, - }, - None, - ) - .await?; - // End Example 32 - - assert_cursor_count!(cursor, 3); - - // Start Example 33 - let cursor = collection - .find( - doc! { - "instock.qty": { "$lte": 20 }, - }, - None, - ) - .await?; - // End Example 33 - - assert_cursor_count!(cursor, 5); - - // Start Example 34 - let cursor = collection - .find( - doc! { - "instock": { - "$elemMatch": { - "qty": 5, - "warehouse": "A", - } - }, - }, - None, - ) - .await?; - // End Example 34 - - assert_cursor_count!(cursor, 1); - - // Start Example 35 - let cursor = collection - .find( - doc! { - "instock": { - "$elemMatch": { - "qty": { - "$gt": 10, - "$lte": 20, - } - } - }, - }, - None, - ) - .await?; - // End Example 35 - - assert_cursor_count!(cursor, 3); - - // Start Example 36 - let cursor = collection - .find( - doc! { - "instock.qty": { - "$gt": 10, - "$lte": 20, - }, - }, - None, - ) - .await?; - // End Example 36 - - assert_cursor_count!(cursor, 4); - - // Start Example 37 - let cursor = collection - .find( - doc! { - "instock.qty": 5, - "instock.warehouse": "A", - }, - None, - ) - .await?; - // End Example 37 - - assert_cursor_count!(cursor, 2); - - Ok(()) -} - -async fn query_null_or_missing_fields_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 38 - let docs = vec![ - doc! { - "_id": 1, - "item": Bson::Null, - }, - doc! { - "_id": 2, - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 38 - - assert_coll_count!(collection, 2); - - // Start Example 39 - let cursor = collection - .find( - doc! { - "item": Bson::Null, - }, - None, - ) - .await?; - // End Example 39 - - assert_cursor_count!(cursor, 2); - - // Start Example 40 - let cursor = collection - .find( - doc! { - "item": { "$type": 10 }, - }, - None, - ) - .await?; - // End Example 40 - - assert_cursor_count!(cursor, 1); - - // Start Example 41 - let cursor = collection - .find( - doc! { - "item": { "$exists": false }, - }, - None, - ) - .await?; - // End Example 41 - - assert_cursor_count!(cursor, 1); - - Ok(()) -} - -async fn projection_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 42 - let docs = vec![ - doc! { - "item": "journal", - "status": "A", - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - "instock": [ - { - "warehouse": "A", - "qty": 5, - }, - ], - }, - doc! { - "item": "notebook", - "status": "A", - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "instock": [ - { - "warehouse": - "C", - "qty": 5, - }, - ] - }, - doc! { - "item": "paper", - "status": "D", - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "instock": [ - { - "warehouse": "A", - "qty": 60, - }, - ], - }, - doc! { - "item": "planner", - "status": "D", - "size": { - "h": 22.85, - "w": 30, - "uom": "cm", - }, - "instock": [ - { - "warehouse": "A", - "qty": 40, - }, - ], - }, - doc! { - "item": "postcard", - "status": "A", - "size": { - "h": 10, - "w": 15.25, - "uom": "cm", - }, - "instock": [ - { - "warehouse": "B", - "qty": 15, - }, - { - "warehouse": "C", - "qty": 35, - }, - ], - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 42 - - assert_coll_count!(collection, 5); - - // Start Example 43 - let cursor = collection - .find( - doc! { - "status": "A", - }, - None, - ) - .await?; - // End Example 43 - - assert_cursor_count!(cursor, 3); - - // Start Example 44 - let options = FindOptions::builder() - .projection(doc! { - "item": 1, - "status": 1, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 44 - - run_on_each_doc!(cursor, doc, { - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("status")); - assert!(!doc.contains_key("size")); - assert!(!doc.contains_key("instock")); - }); - - // Start Example 45 - let options = FindOptions::builder() - .projection(doc! { - "item": 1, - "status": 1, - "_id": 0, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 45 - - run_on_each_doc!(cursor, doc, { - assert!(!doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("status")); - assert!(!doc.contains_key("size")); - assert!(!doc.contains_key("instock")); - }); - - // Start Example 46 - let options = FindOptions::builder() - .projection(doc! { - "status": 0, - "instock": 0, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 46 - - run_on_each_doc!(cursor, doc, { - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(!doc.contains_key("status")); - assert!(doc.contains_key("size")); - assert!(!doc.contains_key("instock")); - }); - - // Start Example 47 - let options = FindOptions::builder() - .projection(doc! { - "item": 1, - "status": 1, - "size.uom": 1, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 47 - - run_on_each_doc!(cursor, doc, { - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("status")); - assert!(doc.contains_key("size")); - assert!(!doc.contains_key("instock")); - - let size = doc.get_document("size").unwrap(); - - assert!(size.contains_key("uom")); - assert!(!size.contains_key("h")); - assert!(!size.contains_key("w")); - }); - - // Start Example 48 - let options = FindOptions::builder() - .projection(doc! { - "size.uom": 0, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 48 - - run_on_each_doc!(cursor, doc, { - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("status")); - assert!(doc.contains_key("size")); - assert!(doc.contains_key("instock")); - - let size = doc.get_document("size").unwrap(); - - assert!(!size.contains_key("uom")); - assert!(size.contains_key("h")); - assert!(size.contains_key("w")); - }); - - // Start Example 50 - let options = FindOptions::builder() - .projection(doc! { - "item": 1, - "status": 1, - "instock": { "$slice": -1 }, - }) - .build(); - - let cursor = collection - .find( - doc! { - "status": "A", - }, - options, - ) - .await?; - // End Example 50 - - run_on_each_doc!(cursor, doc, { - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("status")); - assert!(!doc.contains_key("size")); - assert!(doc.contains_key("instock")); - - let instock = doc.get_array("instock").unwrap(); - - assert_eq!(instock.len(), 1); - }); - - Ok(()) -} - -async fn update_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 51 - let docs = vec![ - doc! { - "item": "canvas", - "qty": 100, - "size": { - "h": 28, - "w": 35.5, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "journal", - "qty": 25, - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "mat", - "qty": 85, - "size": { - "h": 27.9, - "w": 35.5, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "mousepad", - "qty": 25, - "size": { - "h": 19, - "w": 22.85, - "uom": "cm", - }, - "status": "P", - }, - doc! { - "item": "notebook", - "qty": 50, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "P", - }, - doc! { - "item": "paper", - "qty": 100, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "D", - }, - doc! { - "item": "planner", - "qty": 75, - "size": { - "h": 22.85, - "w": 30, - "uom": "cm", - }, - "status": "D", - }, - doc! { - "item": "postcard", - "qty": 45, - "size": { - "h": 10, - "w": 15.25, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "sketchbook", - "qty": 80, - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "sketch pad", - "qty": 95, - "size": { - "h": 22.85, - "w": 30.5, - "uom": "cm", - }, - "status": "A", - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 51 - - assert_coll_count!(collection, 10); - - // Start Example 52 - collection - .update_one( - doc! { "item": "paper" }, - doc! { - "$set": { - "size.uom": "cm", - "status": "P", - }, - "$currentDate": { "lastModified": true }, - }, - None, - ) - .await?; - // End Example 52 - - run_on_each_doc!( - collection - .find(doc! { "item": "paper" }, None) - .await - .unwrap(), - doc, - { - let uom = doc.get_document("size").unwrap().get_str("uom").unwrap(); - assert_eq!(uom, "cm"); - - let status = doc.get_str("status").unwrap(); - assert_eq!(status, "P"); - - assert!(doc.contains_key("lastModified")); - } - ); - - // Start Example 53 - collection - .update_many( - doc! { - "qty": { "$lt": 50 }, - }, - doc! { - "$set": { - "size.uom": "in", - "status": "P", - }, - "$currentDate": { "lastModified": true }, - }, - None, - ) - .await?; - // End Example 53 - - run_on_each_doc!( - collection - .find( - doc! { - "qty": { "$lt": 50 }, - }, - None, - ) - .await - .unwrap(), - doc, - { - let uom = doc.get_document("size").unwrap().get_str("uom").unwrap(); - assert_eq!(uom, "in"); - - let status = doc.get_str("status").unwrap(); - assert_eq!(status, "P"); - - assert!(doc.contains_key("lastModified")); - } - ); - - // Start Example 54 - collection - .replace_one( - doc! { "item": "paper" }, - doc! { - "item": "paper", - "instock": [ - { - "warehouse": "A", - "qty": 60, - }, - { - "warehouse": "B", - "qty": 40, - }, - ], - }, - None, - ) - .await?; - // End Example 54 - - run_on_each_doc!( - collection - .find(doc! { "item": "paper" }, None,) - .await - .unwrap(), - doc, - { - assert_eq!(doc.len(), 3); - assert!(doc.contains_key("_id")); - assert!(doc.contains_key("item")); - assert!(doc.contains_key("instock")); - - let instock = doc.get_array("instock").unwrap(); - assert_eq!(instock.len(), 2); - } - ); - - Ok(()) -} - -async fn delete_examples(collection: &Collection) -> Result<()> { - collection.drop(None).await?; - - // Start Example 55 - let docs = vec![ - doc! { - "item": "journal", - "qty": 25, - "size": { - "h": 14, - "w": 21, - "uom": "cm", - }, - "status": "A", - }, - doc! { - "item": "notebook", - "qty": 50, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "P", - }, - doc! { - "item": "paper", - "qty": 100, - "size": { - "h": 8.5, - "w": 11, - "uom": "in", - }, - "status": "D", - }, - doc! { - "item": "planner", - "qty": 75, - "size": { - "h": 22.85, - "w": 30, - "uom": "cm", - }, - "status": "D", - }, - doc! { - "item": "postcard", - "qty": 45, - "size": { - "h": 10, - "w": 15.25, - "uom": "cm", - }, - "status": "A", - }, - ]; - - collection.insert_many(docs, None).await?; - // End Example 55 - - assert_coll_count!(collection, 5); - - // Start Example 57 - collection.delete_many(doc! { "status": "A" }, None).await?; - // End Example 57 - - assert_coll_count!(collection, 3); - - // Start Example 58 - collection.delete_one(doc! { "status": "D" }, None).await?; - // End Example 58 - - assert_coll_count!(collection, 2); - - // Start Example 56 - collection.delete_many(doc! {}, None).await?; - // End Example 56 - - assert_coll_count!(collection, 0); - - Ok(()) -} - -type GenericResult = std::result::Result>; - -#[allow(unused_variables)] -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -async fn stable_api_examples() -> GenericResult<()> { - let setup_client = TestClient::new().await; - if setup_client.server_version_lt(4, 9) { - log_uncaptured("skipping stable API examples due to unsupported server version"); - return Ok(()); - } - if setup_client.is_sharded() && setup_client.server_version <= Version::new(5, 0, 2) { - // See SERVER-58794. - log_uncaptured( - "skipping stable API examples due to unsupported server version on sharded topology", - ); - return Ok(()); - } - if setup_client.is_load_balanced() { - log_uncaptured("skipping stable API examples due to load-balanced topology"); - return Ok(()); - } - - let uri = DEFAULT_URI.clone(); - // Start Versioned API Example 1 - let mut options = ClientOptions::parse(&uri).await?; - let server_api = ServerApi::builder().version(ServerApiVersion::V1).build(); - options.server_api = Some(server_api); - let client = Client::with_options(options)?; - // End Versioned API Example 1 - - // Start Versioned API Example 2 - let mut options = ClientOptions::parse(&uri).await?; - let server_api = ServerApi::builder() - .version(ServerApiVersion::V1) - .strict(true) - .build(); - options.server_api = Some(server_api); - let client = Client::with_options(options)?; - // End Versioned API Example 2 - - // Start Versioned API Example 3 - let mut options = ClientOptions::parse(&uri).await?; - let server_api = ServerApi::builder() - .version(ServerApiVersion::V1) - .strict(false) - .build(); - options.server_api = Some(server_api); - let client = Client::with_options(options)?; - // End Versioned API Example 3 - - // Start Versioned API Example 4 - let mut options = ClientOptions::parse(&uri).await?; - let server_api = ServerApi::builder() - .version(ServerApiVersion::V1) - .deprecation_errors(true) - .build(); - options.server_api = Some(server_api); - let client = Client::with_options(options)?; - // End Versioned API Example 4 - - let mut options = ClientOptions::parse(&uri).await?; - let server_api = ServerApi::builder() - .version(ServerApiVersion::V1) - .strict(true) - .build(); - options.server_api = Some(server_api); - let client = Client::with_options(options)?; - let db = client.database("stable-api-migration-examples"); - db.collection::("sales").drop(None).await?; - - use std::{error::Error, result::Result}; - - // Start Versioned API Example 5 - // With the `bson-chrono-0_4` feature enabled, this function can be dropped in favor of using - // `chrono::DateTime` values directly. - fn iso_date(text: &str) -> Result> { - let chrono_dt = chrono::DateTime::parse_from_rfc3339(text)?; - Ok(bson::DateTime::from_millis(chrono_dt.timestamp_millis())) - } - db.collection("sales").insert_many(vec![ - doc! { "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : iso_date("2021-01-01T08:00:00Z")? }, - doc! { "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : iso_date("2021-02-03T09:05:00Z")? }, - doc! { "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : iso_date("2021-02-03T09:00:00Z")? }, - doc! { "_id" : 4, "item" : "abc", "price" : 10, "quantity" : 10, "date" : iso_date("2021-02-15T08:00:00Z")? }, - doc! { "_id" : 5, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : iso_date("2021-02-15T09:05:00Z")? }, - doc! { "_id" : 6, "item" : "xyz", "price" : 5, "quantity" : 5, "date" : iso_date("2021-02-15T12:05:10Z")? }, - doc! { "_id" : 7, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : iso_date("2021-02-15T14:12:12Z")? }, - doc! { "_id" : 8, "item" : "abc", "price" : 10, "quantity" : 5, "date" : iso_date("2021-03-16T20:20:13Z")? } - ], None).await?; - // End Versioned API Example 5 - - // Start Versioned API Example 6 - let result = db - .run_command( - doc! { - "count": "sales" - }, - None, - ) - .await; - if let Err(err) = &result { - println!("{:#?}", err.kind); - // Prints: - // Command( - // CommandError { - // code: 323, - // code_name: "APIStrictError", - // message: "Provided apiStrict:true, but the command count is not in API Version 1. Information on supported commands and migrations in API Version 1 can be found at https://www.mongodb.com/docs/v5.0/reference/stable-api/", - // }, - // ) - } - // End Versioned API Example 6 - - // TODO: Uncomment or remove this test once the prior example is updated - // if let ErrorKind::Command(ref err) = *result.as_ref().unwrap_err().kind { - // assert_eq!(err.code, 323); - // assert_eq!(err.code_name, "APIStrictError".to_string()); - // } else { - // panic!("invalid result {:?}", result); - // }; - - // Start Versioned API Example 7 - let count = db - .collection::("sales") - .count_documents(None, None) - .await?; - // End Versioned API Example 7 - - // Start Versioned API Example 8 - assert_eq!(count, 8); - // End Versioned API Example 8 - - Ok(()) -} - -#[allow(unused_imports)] -async fn aggregation_examples() -> GenericResult<()> { - let client = TestClient::new().await; - let db = client.database("aggregation_examples"); - db.drop(None).await?; - aggregation_data::populate(&db).await?; - - // Each example is within its own scope to allow the example to include - // `use futures::TryStreamExt;` without causing multiple definition errors. - - { - // Start Aggregation Example 1 - use futures::TryStreamExt; - let cursor = db - .collection::("sales") - .aggregate( - vec![ - doc! { "$match": { "items.fruit": "banana" } }, - doc! { "$sort": { "date": 1 } }, - ], - None, - ) - .await?; - let values: Vec<_> = cursor.try_collect().await?; - // End Aggregation Example 1 - assert_eq!(5, values.len()); - } - - { - // Start Aggregation Example 2 - use futures::TryStreamExt; - let cursor = db - .collection::("sales") - .aggregate( - vec![ - doc! { - "$unwind": "$items" - }, - doc! { "$match": { - "items.fruit": "banana", - }}, - doc! { - "$group": { - "_id": { "day": { "$dayOfWeek": "$date" } }, - "count": { "$sum": "$items.quantity" } - } - }, - doc! { - "$project": { - "dayOfWeek": "$_id.day", - "numberSold": "$count", - "_id": 0 - } - }, - doc! { - "$sort": { "numberSold": 1 } - }, - ], - None, - ) - .await?; - let values: Vec<_> = cursor.try_collect().await?; - // End Aggregation Example 2 - assert_eq!(4, values.len()); - } - - { - // Start Aggregation Example 3 - use futures::TryStreamExt; - let cursor = db.collection::("sales").aggregate( - vec![ - doc! { - "$unwind": "$items" - }, - doc! { - "$group": { - "_id": { "day": { "$dayOfWeek": "$date" } }, - "items_sold": { "$sum": "$items.quantity" }, - "revenue": { "$sum": { "$multiply": [ "$items.quantity", "$items.price" ] } } - } - }, - doc! { - "$project": { - "day": "$_id.day", - "revenue": 1, - "items_sold": 1, - "discount": { - "$cond": { "if": { "$lte": [ "$revenue", 250 ] }, "then": 25, "else": 0 } - } - } - }, - ], - None, - ).await?; - let values: Vec<_> = cursor.try_collect().await?; - // End Aggregation Example 3 - assert_eq!(4, values.len()); - } - - { - // Start Aggregation Example 4 - use futures::TryStreamExt; - let cursor = db - .collection::("air_alliances") - .aggregate( - vec![ - doc! { - "$lookup": { - "from": "air_airlines", - "let": { "constituents": "$airlines" }, - "pipeline": [ - { - "$match": { "$expr": { "$in": [ "$name", "$$constituents" ] } } - } - ], - "as": "airlines" - } - }, - doc! { - "$project": { - "_id": 0, - "name": 1, - "airlines": { - "$filter": { - "input": "$airlines", - "as": "airline", - "cond": { "$eq": ["$$airline.country", "Canada"] } - } - } - } - }, - ], - None, - ) - .await?; - let values: Vec<_> = cursor.try_collect().await?; - // End Aggregation Example 4 - assert_eq!(3, values.len()); - } - - Ok(()) -} - -async fn run_command_examples() -> Result<()> { - let client = TestClient::new().await; - let db = client.database("run_command_examples"); - db.drop(None).await?; - db.collection::("restaurants") - .insert_one( - doc! { - "name": "Chez Panisse", - "city": "Oakland", - "state": "California", - "country": "United States", - "rating": 4.4, - }, - None, - ) - .await?; - - #[allow(unused)] - // Start runCommand Example 1 - let info = db.run_command(doc! {"buildInfo": 1}, None).await?; - // End runCommand Example 1 - - #[allow(unused)] - // Start runCommand Example 2 - let stats = db - .run_command(doc! {"collStats": "restaurants"}, None) - .await?; - // End runCommand Example 2 - - Ok(()) -} - -async fn index_examples() -> Result<()> { - let client = TestClient::new().await; - let db = client.database("index_examples"); - db.drop(None).await?; - db.collection::("records") - .insert_many( - vec![ - doc! { - "student": "Marty McFly", - "classYear": 1986, - "school": "Hill Valley High", - "score": 56.5, - }, - doc! { - "student": "Ferris F. Bueller", - "classYear": 1987, - "school": "Glenbrook North High", - "status": "Suspended", - "score": 76.0, - }, - ], - None, - ) - .await?; - db.collection::("restaurants") - .insert_many( - vec![ - doc! { - "name": "Chez Panisse", - "city": "Oakland", - "state": "California", - "country": "United States", - "rating": 4.4, - }, - doc! { - "name": "Eleven Madison Park", - "cuisine": "French", - "city": "New York City", - "state": "New York", - "country": "United States", - "rating": 7.1, - }, - ], - None, - ) - .await?; - - use crate::IndexModel; - // Start Index Example 1 - db.collection::("records") - .create_index( - IndexModel::builder().keys(doc! { "score": 1 }).build(), - None, - ) - .await?; - // End Index Example 1 - - use crate::options::IndexOptions; - // Start Index Example 2 - db.collection::("records") - .create_index( - IndexModel::builder() - .keys(doc! { "cuisine": 1, "name": 1 }) - .options( - IndexOptions::builder() - .partial_filter_expression(doc! { "rating": { "$gt": 5 } }) - .build(), - ) - .build(), - None, - ) - .await?; - // End Index Example 2 - - Ok(()) -} - -async fn change_streams_examples() -> Result<()> { - use crate::{change_stream::options::FullDocumentType, options::ChangeStreamOptions, runtime}; - use std::time::Duration; - - let client = TestClient::new().await; - if !client.is_replica_set() && !client.is_sharded() { - log_uncaptured("skipping change_streams_examples due to unsupported topology"); - return Ok(()); - } - let db = client.database("change_streams_examples"); - db.drop(None).await?; - let inventory = db.collection::("inventory"); - // Populate an item so the collection exists for the change stream to watch. - inventory.insert_one(doc! {}, None).await?; - - // Background writer thread so that the `stream.next()` calls return something. - let (tx, mut rx) = tokio::sync::oneshot::channel(); - let writer_inventory = inventory.clone(); - let handle = runtime::spawn(async move { - let mut interval = runtime::interval(Duration::from_millis(100)); - loop { - tokio::select! { - _ = interval.tick() => { - writer_inventory.insert_one(doc! {}, None).await?; - } - _ = &mut rx => break, - } - } - Result::Ok(()) - }); - - #[allow(unused_variables, unused_imports)] - { - { - // Start Changestream Example 1 - use futures::stream::TryStreamExt; - let mut stream = inventory.watch(None, None).await?; - let next = stream.try_next().await?; - // End Changestream Example 1 - } - - { - // Start Changestream Example 2 - use futures::stream::TryStreamExt; - let options = ChangeStreamOptions::builder() - .full_document(Some(FullDocumentType::UpdateLookup)) - .build(); - let mut stream = inventory.watch(None, options).await?; - let next = stream.try_next().await?; - // End Changestream Example 2 - } - - { - let stream = inventory.watch(None, None).await?; - // Start Changestream Example 3 - use futures::stream::TryStreamExt; - let resume_token = stream.resume_token(); - let options = ChangeStreamOptions::builder() - .resume_after(resume_token) - .build(); - let mut stream = inventory.watch(None, options).await?; - stream.try_next().await?; - // End Changestream Example 3 - } - } - - // Shut down the writer thread. - let _ = tx.send(()); - handle.await?; - - Ok(()) -} - -async fn convenient_transaction_examples() -> Result<()> { - use crate::ClientSession; - use futures::FutureExt; - - let setup_client = Client::test_builder().build().await; - if !setup_client.supports_transactions() { - log_uncaptured( - "skipping convenient transaction API examples due to no transaction support", - ); - return Ok(()); - } - - let uri = DEFAULT_URI.clone(); - // Start Transactions withTxn API Example 1 - - // For a replica set, include the replica set name and a seedlist of the members in the URI - // string; e.g. let uri = "mongodb://mongodb0.example.com:27017,mongodb1.example.com:27017/? - // replicaSet=myRepl"; For a sharded cluster, connect to the mongos instances; e.g. - // let uri = "mongodb://mongos0.example.com:27017,mongos1.example.com:27017/"; - - let client = Client::with_uri_str(uri).await?; - - // Prereq: Create collections. CRUD operations in transactions must be on existing collections. - - client - .database("mydb1") - .collection::("foo") - .insert_one(doc! { "abc": 0}, None) - .await?; - client - .database("mydb2") - .collection::("bar") - .insert_one(doc! { "xyz": 0}, None) - .await?; - - // Step 1: Define the callback that specifies the sequence of operations to perform inside the - // transaction. - async fn callback(session: &mut ClientSession) -> Result<()> { - let collection_one = session - .client() - .database("mydb1") - .collection::("foo"); - let collection_two = session - .client() - .database("mydb2") - .collection::("bar"); - - // Important: You must pass the session to the operations. - collection_one - .insert_one_with_session(doc! { "abc": 1 }, None, session) - .await?; - collection_two - .insert_one_with_session(doc! { "xyz": 999 }, None, session) - .await?; - - Ok(()) - } - - // Step 2: Start a client session. - let mut session = client.start_session(None).await?; - - // Step 3: Use with_transaction to start a transaction, execute the callback, and commit (or - // abort on error). - session - .with_transaction((), |session, _| callback(session).boxed(), None) - .await?; - - // End Transactions withTxn API Example 1 - - Ok(()) -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn test() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let coll = client - .database("documentation_examples") - .collection("inventory"); - - insert_examples(&coll).await.unwrap(); - query_top_level_fields_examples(&coll).await.unwrap(); - query_embedded_documents_examples(&coll).await.unwrap(); - query_arrays_examples(&coll).await.unwrap(); - query_array_embedded_documents_examples(&coll) - .await - .unwrap(); - query_null_or_missing_fields_examples(&coll).await.unwrap(); - projection_examples(&coll).await.unwrap(); - update_examples(&coll).await.unwrap(); - delete_examples(&coll).await.unwrap(); - stable_api_examples().await.unwrap(); - aggregation_examples().await.unwrap(); - run_command_examples().await.unwrap(); - index_examples().await.unwrap(); - change_streams_examples().await.unwrap(); - convenient_transaction_examples().await.unwrap(); -} diff --git a/src/test/happy_eyeballs.rs b/src/test/happy_eyeballs.rs new file mode 100644 index 000000000..112541309 --- /dev/null +++ b/src/test/happy_eyeballs.rs @@ -0,0 +1,39 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +use crate::runtime::stream::tcp_connect; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +static CONTROL: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 10036); +const SLOW_V4: u8 = 4; +const SLOW_V6: u8 = 6; + +async fn happy_request(payload: u8) -> (SocketAddr, SocketAddr) { + let mut control = tcp_connect(vec![CONTROL]).await.unwrap(); + control.write_u8(payload).await.unwrap(); + let resp = control.read_u8().await.unwrap(); + assert_eq!(resp, 1); + let v4_port = control.read_u16().await.unwrap(); + let v6_port = control.read_u16().await.unwrap(); + ( + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), v4_port), + SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), v6_port), + ) +} + +#[tokio::test] +async fn slow_ipv4() { + let (v4_addr, v6_addr) = happy_request(SLOW_V4).await; + let mut conn = tcp_connect(vec![v4_addr, v6_addr]).await.unwrap(); + assert!(conn.peer_addr().unwrap().is_ipv6()); + let data = conn.read_u8().await.unwrap(); + assert_eq!(data, 6); +} + +#[tokio::test] +async fn slow_ipv6() { + let (v4_addr, v6_addr) = happy_request(SLOW_V6).await; + let mut conn = tcp_connect(vec![v4_addr, v6_addr]).await.unwrap(); + assert!(conn.peer_addr().unwrap().is_ipv4()); + let data = conn.read_u8().await.unwrap(); + assert_eq!(data, 4); +} diff --git a/src/test/index_management.rs b/src/test/index_management.rs index 65669a347..4a03b006b 100644 --- a/src/test/index_management.rs +++ b/src/test/index_management.rs @@ -1,35 +1,54 @@ +#[path = "index_management/search_index.rs"] +mod search_index_skip_ci; + use futures::stream::TryStreamExt; -use tokio::sync::RwLockReadGuard; use crate::{ bson::doc, error::ErrorKind, - options::{CommitQuorum, CreateIndexOptions, IndexOptions}, + options::{CommitQuorum, IndexOptions}, test::{ log_uncaptured, - util::{EventClient, TestClient}, - LOCK, + server_version_lt, + spec::unified_runner::run_unified_tests, + topology_is_load_balanced, + topology_is_sharded, + topology_is_standalone, }, + Client, IndexModel, }; +#[tokio::test] +async fn run_unified() { + let mut skipped_files = Vec::new(); + let mut skipped_tests = Vec::new(); + // TODO DRIVERS-2794: unskip these tests + if server_version_lt(7, 2).await + && (topology_is_sharded().await || topology_is_load_balanced().await) + { + skipped_files.push("listSearchIndexes.json"); + skipped_tests.push("listSearchIndexes ignores read and write concern"); + } + + run_unified_tests(&["index-management"]) + .skip_files(&skipped_files) + .skip_tests(&skipped_tests) + .await; +} + // Test that creating indexes works as expected. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn index_management_creates() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; // Test creating a single index with driver-generated name. let result = coll - .create_index( - IndexModel::builder().keys(doc! { "a": 1, "b": -1 }).build(), - None, - ) + .create_index(IndexModel::builder().keys(doc! { "a": 1, "b": -1 }).build()) .await .expect("Test failed to create index"); @@ -37,20 +56,17 @@ async fn index_management_creates() { // Test creating several indexes, with both specified and unspecified names. let result = coll - .create_indexes( - vec![ - IndexModel::builder().keys(doc! { "c": 1 }).build(), - IndexModel::builder() - .keys(doc! { "d": 1 }) - .options( - IndexOptions::builder() - .name("customname".to_string()) - .build(), - ) - .build(), - ], - None, - ) + .create_indexes(vec![ + IndexModel::builder().keys(doc! { "c": 1 }).build(), + IndexModel::builder() + .keys(doc! { "d": 1 }) + .options( + IndexOptions::builder() + .name("customname".to_string()) + .build(), + ) + .build(), + ]) .await .expect("Test failed to create indexes"); @@ -67,19 +83,31 @@ async fn index_management_creates() { assert_eq!(names, vec!["_id_", "a_1_b_-1", "c_1", "customname"]); } +// Test that creating indexes with string field types produces correct names. +#[tokio::test] +async fn index_management_string_names() { + let client = Client::for_test().await; + let coll = client + .init_db_and_coll("index_management", "string_names") + .await; + let result = coll + .create_index(IndexModel::builder().keys(doc! { "field": "2d" }).build()) + .await + .expect("Test failed to create index"); + assert_eq!(result.index_name, "field_2d"); +} + // Test that creating a duplicate index works as expected. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn index_management_handles_duplicates() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let result = coll - .create_index(IndexModel::builder().keys(doc! { "a": 1 }).build(), None) + .create_index(IndexModel::builder().keys(doc! { "a": 1 }).build()) .await .expect("Test failed to create index"); @@ -87,7 +115,7 @@ async fn index_management_handles_duplicates() { // Insert duplicate. let result = coll - .create_index(IndexModel::builder().keys(doc! { "a": 1 }).build(), None) + .create_index(IndexModel::builder().keys(doc! { "a": 1 }).build()) .await .expect("Test failed to create index"); @@ -95,13 +123,10 @@ async fn index_management_handles_duplicates() { // Test partial duplication. let result = coll - .create_indexes( - vec![ - IndexModel::builder().keys(doc! { "a": 1 }).build(), // Duplicate - IndexModel::builder().keys(doc! { "b": 1 }).build(), // Not duplicate - ], - None, - ) + .create_indexes(vec![ + IndexModel::builder().keys(doc! { "a": 1 }).build(), // Duplicate + IndexModel::builder().keys(doc! { "b": 1 }).build(), // Not duplicate + ]) .await .expect("Test failed to create indexes"); @@ -112,12 +137,10 @@ async fn index_management_handles_duplicates() { } // Test that listing indexes works as expected. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn index_management_lists() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; @@ -131,7 +154,7 @@ async fn index_management_lists() { .build(), ]; - coll.create_indexes(insert_data.clone(), None) + coll.create_indexes(insert_data.clone()) .await .expect("Test failed to create indexes"); @@ -143,7 +166,7 @@ async fn index_management_lists() { ]; let mut indexes = coll - .list_indexes(None) + .list_indexes() .await .expect("Test failed to list indexes"); @@ -175,25 +198,20 @@ async fn index_management_lists() { } // Test that dropping indexes works as expected. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn index_management_drops() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let result = coll - .create_indexes( - vec![ - IndexModel::builder().keys(doc! { "a": 1 }).build(), - IndexModel::builder().keys(doc! { "b": 1 }).build(), - IndexModel::builder().keys(doc! { "c": 1 }).build(), - ], - None, - ) + .create_indexes(vec![ + IndexModel::builder().keys(doc! { "a": 1 }).build(), + IndexModel::builder().keys(doc! { "b": 1 }).build(), + IndexModel::builder().keys(doc! { "c": 1 }).build(), + ]) .await .expect("Test failed to create multiple indexes"); @@ -203,7 +221,7 @@ async fn index_management_drops() { ); // Test dropping single index. - coll.drop_index("a_1", None) + coll.drop_index("a_1") .await .expect("Test failed to drop index"); let names = coll @@ -213,7 +231,7 @@ async fn index_management_drops() { assert_eq!(names, vec!["_id_", "b_1", "c_1"]); // Test dropping several indexes. - coll.drop_indexes(None) + coll.drop_indexes() .await .expect("Test failed to drop indexes"); let names = coll @@ -224,84 +242,118 @@ async fn index_management_drops() { } // Test that index management commands execute the expected database commands. -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn index_management_executes_commands() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; // Collection::create_index and Collection::create_indexes execute createIndexes. assert_eq!( - client.get_command_started_events(&["createIndexes"]).len(), + client + .events + .get_command_started_events(&["createIndexes"]) + .len(), 0 ); - coll.create_index(IndexModel::builder().keys(doc! { "a": 1 }).build(), None) + coll.create_index(IndexModel::builder().keys(doc! { "a": 1 }).build()) .await .expect("Create Index op failed"); assert_eq!( - client.get_command_started_events(&["createIndexes"]).len(), + client + .events + .get_command_started_events(&["createIndexes"]) + .len(), 1 ); - coll.create_indexes( - vec![ - IndexModel::builder().keys(doc! { "b": 1 }).build(), - IndexModel::builder().keys(doc! { "c": 1 }).build(), - ], - None, - ) + coll.create_indexes(vec![ + IndexModel::builder().keys(doc! { "b": 1 }).build(), + IndexModel::builder().keys(doc! { "c": 1 }).build(), + ]) .await .expect("Create Indexes op failed"); assert_eq!( - client.get_command_started_events(&["createIndexes"]).len(), + client + .events + .get_command_started_events(&["createIndexes"]) + .len(), 2 ); // Collection::list_indexes and Collection::list_index_names execute listIndexes. - assert_eq!(client.get_command_started_events(&["listIndexes"]).len(), 0); - coll.list_indexes(None).await.expect("List index op failed"); - assert_eq!(client.get_command_started_events(&["listIndexes"]).len(), 1); + assert_eq!( + client + .events + .get_command_started_events(&["listIndexes"]) + .len(), + 0 + ); + coll.list_indexes().await.expect("List index op failed"); + assert_eq!( + client + .events + .get_command_started_events(&["listIndexes"]) + .len(), + 1 + ); coll.list_index_names().await.expect("List index op failed"); - assert_eq!(client.get_command_started_events(&["listIndexes"]).len(), 2); + assert_eq!( + client + .events + .get_command_started_events(&["listIndexes"]) + .len(), + 2 + ); // Collection::drop_index and Collection::drop_indexes execute dropIndexes. - assert_eq!(client.get_command_started_events(&["dropIndexes"]).len(), 0); - coll.drop_index("a_1", None) - .await - .expect("Drop index op failed"); - assert_eq!(client.get_command_started_events(&["dropIndexes"]).len(), 1); - coll.drop_indexes(None) - .await - .expect("Drop indexes op failed"); - assert_eq!(client.get_command_started_events(&["dropIndexes"]).len(), 2); + assert_eq!( + client + .events + .get_command_started_events(&["dropIndexes"]) + .len(), + 0 + ); + coll.drop_index("a_1").await.expect("Drop index op failed"); + assert_eq!( + client + .events + .get_command_started_events(&["dropIndexes"]) + .len(), + 1 + ); + coll.drop_indexes().await.expect("Drop indexes op failed"); + assert_eq!( + client + .events + .get_command_started_events(&["dropIndexes"]) + .len(), + 2 + ); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] #[function_name::named] async fn commit_quorum_error() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - if client.is_standalone() { + if topology_is_standalone().await { log_uncaptured("skipping commit_quorum_error due to standalone topology"); return; } + let client = Client::for_test().await; + let coll = client .init_db_and_coll(function_name!(), function_name!()) .await; let model = IndexModel::builder().keys(doc! { "x": 1 }).build(); - let options = CreateIndexOptions::builder() + let result = coll + .create_index(model) .commit_quorum(CommitQuorum::Majority) - .build(); - let result = coll.create_index(model, options).await; + .await; - if client.server_version_lt(4, 4) { + if server_version_lt(4, 4).await { let err = result.unwrap_err(); assert!(matches!(*err.kind, ErrorKind::InvalidArgument { .. })); } else { diff --git a/src/test/index_management/search_index.rs b/src/test/index_management/search_index.rs new file mode 100644 index 000000000..b5fa46edb --- /dev/null +++ b/src/test/index_management/search_index.rs @@ -0,0 +1,355 @@ +use std::time::{Duration, Instant}; + +use futures_util::TryStreamExt; + +use crate::{ + bson::{doc, oid::ObjectId, Document}, + search_index::SearchIndexType, + Client, + Collection, + SearchIndexModel, +}; + +/// Search Index Case 1: Driver can successfully create and list search indexes +#[tokio::test] +async fn search_index_create_list() { + let start = Instant::now(); + let deadline = start + Duration::from_secs(60 * 5); + + let client = Client::for_test().await; + let db = client.database("search_index_test"); + let coll_name = ObjectId::new().to_hex(); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index"); + + let found = 'outer: loop { + let mut cursor = coll0.list_search_indexes().await.unwrap(); + while let Some(d) = cursor.try_next().await.unwrap() { + if d.get_str("name").is_ok_and(|n| n == "test-search-index") + && d.get_bool("queryable").unwrap_or(false) + { + break 'outer d; + } + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("timed out"); + } + }; + + assert_eq!( + found.get_document("latestDefinition").unwrap(), + &doc! { "mappings": { "dynamic": false } } + ); +} + +/// Search Index Case 2: Driver can successfully create multiple indexes in batch +#[tokio::test] +async fn search_index_create_multiple() { + let start = Instant::now(); + let deadline = start + Duration::from_secs(60 * 5); + + let client = Client::for_test().await; + let db = client.database("search_index_test"); + let coll_name = ObjectId::new().to_hex(); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let names = coll0 + .create_search_indexes([ + SearchIndexModel::builder() + .name(String::from("test-search-index-1")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + SearchIndexModel::builder() + .name(String::from("test-search-index-2")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ]) + .await + .unwrap(); + assert_eq!(names, ["test-search-index-1", "test-search-index-2"]); + + let mut index1 = None; + let mut index2 = None; + loop { + let mut cursor = coll0.list_search_indexes().await.unwrap(); + while let Some(d) = cursor.try_next().await.unwrap() { + if d.get_str("name").is_ok_and(|n| n == "test-search-index-1") + && d.get_bool("queryable").unwrap_or(false) + { + index1 = Some(d); + } else if d.get_str("name").is_ok_and(|n| n == "test-search-index-2") + && d.get_bool("queryable").unwrap_or(false) + { + index2 = Some(d); + } + } + if index1.is_some() && index2.is_some() { + break; + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("timed out"); + } + } + + assert_eq!( + index1.unwrap().get_document("latestDefinition").unwrap(), + &doc! { "mappings": { "dynamic": false } } + ); + assert_eq!( + index2.unwrap().get_document("latestDefinition").unwrap(), + &doc! { "mappings": { "dynamic": false } } + ); +} + +/// Search Index Case 3: Driver can successfully drop search indexes +#[tokio::test] +async fn search_index_drop() { + let start = Instant::now(); + let deadline = start + Duration::from_secs(60 * 5); + + let client = Client::for_test().await; + let db = client.database("search_index_test"); + let coll_name = ObjectId::new().to_hex(); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index"); + + 'outer: loop { + let mut cursor = coll0.list_search_indexes().await.unwrap(); + while let Some(d) = cursor.try_next().await.unwrap() { + if d.get_str("name").is_ok_and(|n| n == "test-search-index") + && d.get_bool("queryable").unwrap_or(false) + { + break 'outer; + } + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("search index creation timed out"); + } + } + + coll0.drop_search_index("test-search-index").await.unwrap(); + + loop { + let cursor = coll0.list_search_indexes().await.unwrap(); + if !cursor.has_next() { + break; + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("search index drop timed out"); + } + } +} + +/// Search Index Case 4: Driver can update a search index +#[tokio::test] +async fn search_index_update() { + let start = Instant::now(); + let deadline = start + Duration::from_secs(60 * 5); + + let client = Client::for_test().await; + let db = client.database("search_index_test"); + let coll_name = ObjectId::new().to_hex(); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index"); + + 'outer: loop { + let mut cursor = coll0.list_search_indexes().await.unwrap(); + while let Some(d) = cursor.try_next().await.unwrap() { + if d.get_str("name").is_ok_and(|n| n == "test-search-index") + && d.get_bool("queryable").unwrap_or(false) + { + break 'outer; + } + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("search index creation timed out"); + } + } + + coll0 + .update_search_index( + "test-search-index", + doc! { "mappings": { "dynamic": true } }, + ) + .await + .unwrap(); + + let found = 'find: loop { + let mut cursor = coll0.list_search_indexes().await.unwrap(); + while let Some(d) = cursor.try_next().await.unwrap() { + if d.get_str("name").is_ok_and(|n| n == "test-search-index") + && d.get_bool("queryable").unwrap_or(false) + && d.get_str("status").is_ok_and(|s| s == "READY") + { + break 'find d; + } + } + tokio::time::sleep(Duration::from_secs(5)).await; + if Instant::now() > deadline { + panic!("search index update timed out"); + } + }; + + assert_eq!( + found.get_document("latestDefinition").unwrap(), + &doc! { "mappings": { "dynamic": true } } + ); +} + +/// Search Index Case 5: dropSearchIndex suppresses namespace not found errors +#[tokio::test] +async fn search_index_drop_not_found() { + let client = Client::for_test().await; + let coll_name = ObjectId::new().to_hex(); + let coll0 = client + .database("search_index_test") + .collection::(&coll_name); + + coll0.drop_search_index("test-search-index").await.unwrap(); +} + +async fn wait_for_index(coll: &Collection, name: &str) -> Document { + let deadline = Instant::now() + Duration::from_secs(60 * 5); + while Instant::now() < deadline { + let mut cursor = coll.list_search_indexes().name(name).await.unwrap(); + while let Some(def) = cursor.try_next().await.unwrap() { + if def.get_str("name").is_ok_and(|n| n == name) + && def.get_bool("queryable").unwrap_or(false) + { + return def; + } + } + tokio::time::sleep(Duration::from_secs(5)).await; + } + panic!("search index creation timed out"); +} + +// SearchIndex Case 7: Driver can successfully handle search index types when creating indexes +#[tokio::test] +async fn search_index_create_with_type() { + let client = Client::for_test().await; + let coll_name = ObjectId::new().to_hex(); + let db = client.database("search_index_test"); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index-case7-implicit")) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index-case7-implicit"); + let index1 = wait_for_index(&coll0, &name).await; + assert_eq!(index1.get_str("type").unwrap(), "search"); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index-case7-explicit")) + .index_type(SearchIndexType::Search) + .definition(doc! { "mappings": { "dynamic": false } }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index-case7-explicit"); + let index2 = wait_for_index(&coll0, &name).await; + assert_eq!(index2.get_str("type").unwrap(), "search"); + + let name = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index-case7-vector")) + .index_type(SearchIndexType::VectorSearch) + .definition(doc! { + "fields": [{ + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean", + }] + }) + .build(), + ) + .await + .unwrap(); + assert_eq!(name, "test-search-index-case7-vector"); + let index3 = wait_for_index(&coll0, &name).await; + assert_eq!(index3.get_str("type").unwrap(), "vectorSearch"); +} + +// SearchIndex Case 8: Driver requires explicit type to create a vector search index +#[tokio::test] +async fn search_index_requires_explicit_vector() { + let client = Client::for_test().await; + let coll_name = ObjectId::new().to_hex(); + let db = client.database("search_index_test"); + db.create_collection(&coll_name).await.unwrap(); + let coll0 = db.collection::(&coll_name); + + let result = coll0 + .create_search_index( + SearchIndexModel::builder() + .name(String::from("test-search-index-case8-error")) + .definition(doc! { + "fields": [{ + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean", + }] + }) + .build(), + ) + .await; + assert!( + result + .as_ref() + .is_err_and(|e| e.to_string().contains("Attribute mappings missing")), + "invalid result: {:?}", + result + ); +} diff --git a/src/test/lambda_examples/auth.rs b/src/test/lambda_examples/auth.rs index 5de94f2e9..5fd162870 100644 --- a/src/test/lambda_examples/auth.rs +++ b/src/test/lambda_examples/auth.rs @@ -1,43 +1,43 @@ use crate as mongodb; // begin lambda connection example 2 -use async_once::AsyncOnce; use lambda_runtime::{service_fn, LambdaEvent}; -use lazy_static::lazy_static; use mongodb::{ bson::doc, options::{AuthMechanism, ClientOptions, Credential}, Client, }; use serde_json::Value; +use tokio::sync::OnceCell; // Initialize a global static MongoDB Client with AWS authentication. The following environment // variables should also be set: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and, optionally, // AWS_SESSION_TOKEN. -// -// The client can be accessed as follows: -// let client = MONGODB_CLIENT.get().await; -lazy_static! { - static ref MONGODB_CLIENT: AsyncOnce = AsyncOnce::new(async { - let uri = std::env::var("MONGODB_URI") - .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); - let mut options = ClientOptions::parse(uri) - .await - .expect("Failed to parse options from URI"); - let credential = Credential::builder() - .mechanism(AuthMechanism::MongoDbAws) - .build(); - options.credential = Some(credential); - Client::with_options(options).expect("Failed to create MongoDB Client") - }); +static MONGODB_CLIENT: OnceCell = OnceCell::const_new(); + +async fn get_mongodb_client() -> &'static Client { + MONGODB_CLIENT + .get_or_init(|| async { + let uri = std::env::var("MONGODB_URI") + .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); + let mut options = ClientOptions::parse(&uri) + .await + .expect("Failed to parse options from URI"); + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbAws) + .build(); + options.credential = Some(credential); + Client::with_options(options).expect("Failed to create MongoDB Client") + }) + .await } // Runs a ping operation on the "db" database and returns the response. async fn handler(_: LambdaEvent) -> Result { - let client = MONGODB_CLIENT.get().await; + let client = get_mongodb_client().await; let response = client .database("db") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await?; let json = serde_json::to_value(response)?; Ok(json) @@ -57,14 +57,13 @@ async fn handler_create_client(_: LambdaEvent) -> Result = AsyncOnce::new(async { - let uri = std::env::var("MONGODB_URI") - .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); - Client::with_uri_str(uri) - .await - .expect("Failed to create MongoDB Client") - }); +static MONGODB_CLIENT: OnceCell = OnceCell::const_new(); + +async fn get_mongodb_client() -> &'static Client { + MONGODB_CLIENT + .get_or_init(|| async { + let uri = std::env::var("MONGODB_URI") + .expect("MONGODB_URI must be set to the URI of the MongoDB deployment"); + Client::with_uri_str(uri) + .await + .expect("Failed to create MongoDB Client") + }) + .await } // Runs a ping operation on the "db" database and returns the response. async fn handler(_: LambdaEvent) -> Result { - let client = MONGODB_CLIENT.get().await; + let client = get_mongodb_client().await; let response = client .database("db") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await?; let json = serde_json::to_value(response)?; Ok(json) @@ -40,8 +40,7 @@ async fn main() -> Result<(), lambda_runtime::Error> { } // end lambda connection example 1 -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn test_handler() { if std::env::var("MONGODB_API_VERSION").is_ok() { return; diff --git a/src/test/mod.rs b/src/test/mod.rs deleted file mode 100644 index 4049b4171..000000000 --- a/src/test/mod.rs +++ /dev/null @@ -1,182 +0,0 @@ -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod atlas_connectivity; -mod atlas_planned_maintenance_testing; -mod auth_aws; -mod change_stream; -mod client; -mod coll; -#[cfg(feature = "in-use-encryption-unstable")] -mod csfle; -mod cursor; -mod db; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod documentation_examples; -mod index_management; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod lambda_examples; -pub(crate) mod serde_helpers; -pub(crate) mod spec; -mod timeseries; -pub(crate) mod util; - -#[cfg(feature = "in-use-encryption-unstable")] -pub(crate) use self::csfle::{KmsProviderList, KMS_PROVIDERS_MAP}; -pub(crate) use self::{ - spec::{run_spec_test, RunOn, Serverless, Topology}, - util::{ - assert_matches, - eq_matches, - file_level_log, - log_uncaptured, - Event, - EventClient, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - MatchErrExt, - Matchable, - SdamEvent, - TestClient, - }, -}; - -use async_once::AsyncOnce; -use home::home_dir; -use lazy_static::lazy_static; - -use self::util::TestLock; -#[cfg(feature = "tracing-unstable")] -use self::util::TracingHandler; -use crate::{ - client::{ - auth::Credential, - options::{ServerApi, ServerApiVersion}, - }, - options::{ClientOptions, Compressor}, -}; -use std::{fs::read_to_string, str::FromStr}; - -const MAX_POOL_SIZE: u32 = 100; - -lazy_static! { - pub(crate) static ref CLIENT_OPTIONS: AsyncOnce = AsyncOnce::new(async { - let mut options = ClientOptions::parse_uri(&*DEFAULT_URI, None).await.unwrap(); - update_options_for_testing(&mut options); - options - }); - pub(crate) static ref LOCK: TestLock = TestLock::new(); - pub(crate) static ref DEFAULT_URI: String = get_default_uri(); - pub(crate) static ref SERVER_API: Option = match std::env::var("MONGODB_API_VERSION") - { - Ok(server_api_version) if !server_api_version.is_empty() => Some(ServerApi { - version: ServerApiVersion::from_str(server_api_version.as_str()).unwrap(), - deprecation_errors: None, - strict: None, - }), - _ => None, - }; - pub(crate) static ref SERVERLESS: bool = - matches!(std::env::var("SERVERLESS"), Ok(s) if s == "serverless"); - pub(crate) static ref LOAD_BALANCED_SINGLE_URI: Option = - std::env::var("SINGLE_MONGOS_LB_URI").ok(); - pub(crate) static ref LOAD_BALANCED_MULTIPLE_URI: Option = - std::env::var("MULTI_MONGOS_LB_URI").ok(); - pub(crate) static ref ZSTD_COMPRESSION_ENABLED: bool = - matches!(std::env::var("ZSTD_COMPRESSION_ENABLED"), Ok(s) if s == "true"); - pub(crate) static ref ZLIB_COMPRESSION_ENABLED: bool = - matches!(std::env::var("ZLIB_COMPRESSION_ENABLED"), Ok(s) if s == "true"); - pub(crate) static ref SNAPPY_COMPRESSION_ENABLED: bool = - matches!(std::env::var("SNAPPY_COMPRESSION_ENABLED"), Ok(s) if s == "true"); - pub(crate) static ref SERVERLESS_ATLAS_USER: Option = - std::env::var("SERVERLESS_ATLAS_USER").ok(); - pub(crate) static ref SERVERLESS_ATLAS_PASSWORD: Option = - std::env::var("SERVERLESS_ATLAS_PASSWORD").ok(); -} - -// conditional definitions do not work within the lazy_static! macro, so this -// needs to be defined separately. -#[cfg(feature = "tracing-unstable")] -lazy_static! { - /// A global default tracing handler that will be installed the first time this - /// value is accessed. A global handler must be used anytime the multi-threaded - /// test runtime is in use, as non-global handlers only apply to the thread - /// they are registered in. - /// By default this handler will collect no tracing events. - /// Its minimum severity levels can be configured on a per-component basis using - /// [`TracingHandler:set_levels`]. The test lock MUST be acquired exclusively in - /// any test that will use the handler to avoid mixing events from multiple tests. - pub(crate) static ref DEFAULT_GLOBAL_TRACING_HANDLER: TracingHandler = { - let handler = TracingHandler::new(); - tracing::subscriber::set_global_default(handler.clone()) - .expect("setting global default tracing subscriber failed"); - handler - }; -} - -pub(crate) fn update_options_for_testing(options: &mut ClientOptions) { - if options.max_pool_size.is_none() { - options.max_pool_size = Some(MAX_POOL_SIZE); - } - if options.server_api.is_none() { - options.server_api = SERVER_API.clone(); - } - if options.compressors.is_none() { - options.compressors = get_compressors(); - } - if options.credential.is_none() && SERVERLESS_ATLAS_USER.is_some() { - options.credential = Some( - Credential::builder() - .username(SERVERLESS_ATLAS_USER.clone()) - .password(SERVERLESS_ATLAS_PASSWORD.clone()) - .build(), - ); - } -} - -fn get_compressors() -> Option> { - #[allow(unused_mut)] - let mut compressors = vec![]; - - if *SNAPPY_COMPRESSION_ENABLED { - #[cfg(feature = "snappy-compression")] - compressors.push(Compressor::Snappy); - #[cfg(not(feature = "snappy-compression"))] - panic!("To use snappy compression, the \"snappy-compression\" feature flag must be set."); - } - if *ZLIB_COMPRESSION_ENABLED { - #[cfg(feature = "zlib-compression")] - compressors.push(Compressor::Zlib { level: None }); - #[cfg(not(feature = "zlib-compression"))] - panic!("To use zlib compression, the \"zlib-compression\" feature flag must be set."); - } - if *ZSTD_COMPRESSION_ENABLED { - #[cfg(feature = "zstd-compression")] - compressors.push(Compressor::Zstd { level: None }); - #[cfg(not(feature = "zstd-compression"))] - panic!("To use zstd compression, the \"zstd-compression\" feature flag must be set."); - } - if compressors.is_empty() { - None - } else { - Some(compressors) - } -} - -fn get_default_uri() -> String { - if let Some(uri) = LOAD_BALANCED_SINGLE_URI.clone() { - if !uri.is_empty() { - return uri; - } - } - if let Ok(uri) = std::env::var("MONGODB_URI") { - return uri; - } - if let Some(mut home) = home_dir() { - home.push(".mongodb_uri"); - if let Ok(uri) = read_to_string(home) { - return uri; - } - } - "mongodb://localhost:27017".to_string() -} diff --git a/src/test/serde_helpers.rs b/src/test/serde_helpers.rs deleted file mode 100644 index 1b0ac0944..000000000 --- a/src/test/serde_helpers.rs +++ /dev/null @@ -1,18 +0,0 @@ -use serde::de::{Deserialize, DeserializeOwned, Deserializer}; - -pub(crate) fn deserialize_nonempty_vec<'de, D, T>( - deserializer: D, -) -> std::result::Result>, D::Error> -where - D: Deserializer<'de>, - T: DeserializeOwned, -{ - let vec: Vec = Vec::deserialize(deserializer)?; - if vec.is_empty() { - return Err(serde::de::Error::custom(format!( - "list provided for {} cannot be empty", - std::any::type_name::() - ))); - } - Ok(Some(vec)) -} diff --git a/src/test/spec.rs b/src/test/spec.rs new file mode 100644 index 000000000..db90e1088 --- /dev/null +++ b/src/test/spec.rs @@ -0,0 +1,147 @@ +mod auth; +mod change_streams; +mod collection_management; +mod command_monitoring; +mod connection_stepdown; +mod crud; +mod faas; +mod gridfs; +mod handshake; +#[cfg(feature = "dns-resolver")] +mod initial_dns_seedlist_discovery; +mod load_balancers; +#[path = "spec/oidc.rs"] +pub(crate) mod oidc_skip_ci; +mod read_write_concern; +mod retryable_reads; +mod retryable_writes; +mod run_command; +mod sdam; +mod sessions; +#[cfg(feature = "tracing-unstable")] +mod trace; +mod transactions; +pub(crate) mod unified_runner; +pub(crate) mod v2_runner; +mod versioned_api; +mod write_error; + +use std::{ + any::type_name, + ffi::OsStr, + fs::{read_dir, File}, + future::Future, + path::PathBuf, +}; + +use serde::{de::DeserializeOwned, Deserialize}; + +pub(crate) use self::{ + oidc_skip_ci as oidc, + unified_runner::{merge_uri_options, ExpectedEventType, Topology}, + v2_runner::{operation::Operation, test_file::RunOn}, +}; +use crate::bson::Bson; + +use super::log_uncaptured; + +pub(crate) fn deserialize_spec_tests_from_exact_path( + path: &[&str], + skipped_files: Option<&[&str]>, +) -> Vec<(T, PathBuf)> { + deserialize_spec_tests_common(path.iter().collect(), skipped_files) +} + +pub(crate) fn deserialize_spec_tests( + spec: &[&str], + skipped_files: Option<&[&str]>, +) -> Vec<(T, PathBuf)> { + let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "src", "test", "spec", "json"] + .iter() + .collect(); + path.extend(spec); + deserialize_spec_tests_common(path, skipped_files) +} + +fn deserialize_spec_tests_common( + path: PathBuf, + skipped_files: Option<&[&str]>, +) -> Vec<(T, PathBuf)> { + let mut tests = vec![]; + for entry in + read_dir(&path).unwrap_or_else(|e| panic!("Failed to read directory at {:?}: {}", &path, e)) + { + let path = entry.unwrap().path(); + let Some(filename) = path + .file_name() + .and_then(OsStr::to_str) + .filter(|name| name.ends_with(".json")) + else { + continue; + }; + + if let Ok(unskipped_filename) = std::env::var("TEST_FILE") { + if filename != unskipped_filename { + continue; + } + } + + if let Some(skipped_files) = skipped_files { + if skipped_files.contains(&filename) { + log_uncaptured(format!("Skipping deserializing {:?}", &path)); + continue; + } + } + + let file = File::open(&path) + .unwrap_or_else(|e| panic!("Failed to open file at {:?}: {}", &path, e)); + + // Use BSON as an intermediary to deserialize extended JSON properly. + let deserializer = &mut serde_json::Deserializer::from_reader(file); + let test_bson: Bson = serde_path_to_error::deserialize(deserializer).unwrap_or_else(|e| { + panic!( + "Failed to deserialize test JSON to BSON in {:?}: {}", + &path, e + ) + }); + + let deserializer = crate::bson::Deserializer::new(test_bson); + let test: T = serde_path_to_error::deserialize(deserializer).unwrap_or_else(|e| { + panic!( + "Failed to deserialize test BSON to {} in {:?}: {}", + type_name::(), + &path, + e + ) + }); + + tests.push((test, path)); + } + + tests +} + +pub(crate) async fn run_spec_test(spec: &[&str], run_test_file: F) +where + F: Fn(T) -> G, + G: Future, + T: DeserializeOwned, +{ + for (test_file, _) in deserialize_spec_tests(spec, None) { + run_test_file(test_file).await; + } +} + +#[derive(Debug, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase", deny_unknown_fields)] +pub(crate) enum Serverless { + Require, + Forbid, + Allow, +} + +impl Serverless { + pub(crate) fn can_run(&self) -> bool { + *self != Self::Require + } +} diff --git a/src/test/spec/auth.rs b/src/test/spec/auth.rs index 61373aa3a..db1962726 100644 --- a/src/test/spec/auth.rs +++ b/src/test/spec/auth.rs @@ -32,6 +32,7 @@ impl From for Credential { .mechanism .and_then(|s| AuthMechanism::from_str(s.as_str()).ok()), mechanism_properties: test_credential.mechanism_properties, + oidc_callback: crate::client::auth::oidc::Callback::new(), } } } @@ -51,19 +52,34 @@ async fn run_auth_test(test_file: TestFile) { test_case.description = test_case.description.replace('$', "%"); let skipped_mechanisms = [ + #[cfg(not(feature = "gssapi-auth"))] "GSSAPI", "MONGODB-CR", #[cfg(not(feature = "aws-auth"))] "MONGODB-AWS", ]; - // TODO: GSSAPI (RUST-196) if skipped_mechanisms .iter() .any(|mech| test_case.description.contains(mech)) { continue; } + #[cfg(not(feature = "gssapi-auth"))] + // This one's GSSAPI but doesn't include it in the description + if test_case + .description + .contains("must raise an error when the hostname canonicalization is invalid") + { + continue; + } + // Lack of callback can't be validated from just URI parsing, as it's set in code + if test_case + .description + .contains("should throw an exception if neither environment nor callbacks specified") + { + continue; + } match ClientOptions::parse(test_case.uri.as_str()).await { Ok(options) => { @@ -90,8 +106,7 @@ async fn run_auth_test(test_file: TestFile) { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_spec_test(&["auth"], run_auth_test).await; +#[tokio::test] +async fn run_legacy() { + run_spec_test(&["auth", "legacy"], run_auth_test).await; } diff --git a/src/test/spec/change_streams.rs b/src/test/spec/change_streams.rs index 04e4f6747..0ba77b1d4 100644 --- a/src/test/spec/change_streams.rs +++ b/src/test/spec/change_streams.rs @@ -1,15 +1,11 @@ -use crate::test::{spec::unified_runner::run_unified_tests, LOCK}; +use crate::test::spec::unified_runner::run_unified_tests; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] // multi_thread required for FailPoint -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint async fn run_unified() { - let _guard = LOCK.run_exclusively().await; run_unified_tests(&["change-streams", "unified"]) .skip_files(&[ // TODO RUST-1281: unskip this file "change-streams-showExpandedEvents.json", - // TODO RUST-1423: unskip this file - "change-streams-disambiguatedPaths.json", ]) .skip_tests(&[ // TODO RUST-1658: unskip these tests diff --git a/src/test/spec/client_side_encryption.rs b/src/test/spec/client_side_encryption.rs deleted file mode 100644 index 09ed80e6b..000000000 --- a/src/test/spec/client_side_encryption.rs +++ /dev/null @@ -1,37 +0,0 @@ -use tokio::sync::RwLockWriteGuard; - -use crate::test::{ - spec::{unified_runner::run_unified_tests, v2_runner::run_v2_tests}, - LOCK, -}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let mut skipped_tests = vec![]; - if cfg!(not(feature = "openssl-tls")) { - skipped_tests.push("create datakey with KMIP KMS provider"); - } - - run_unified_tests(&["client-side-encryption", "unified"]) - .skip_tests(&skipped_tests) - .await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_legacy() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - // TODO RUST-528: unskip this file - let mut skipped_files = vec!["timeoutMS.json"]; - if cfg!(not(feature = "openssl-tls")) { - skipped_files.push("kmipKMS.json"); - } - - run_v2_tests(&["client-side-encryption", "legacy"]) - .skip_files(&skipped_files) - .await; -} diff --git a/src/test/spec/collection_management.rs b/src/test/spec/collection_management.rs index 2da6ef0af..586658d91 100644 --- a/src/test/spec/collection_management.rs +++ b/src/test/spec/collection_management.rs @@ -1,11 +1,9 @@ -use crate::test::{spec::unified_runner::run_unified_tests, LOCK}; +use crate::test::spec::unified_runner::run_unified_tests; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run_unified() { - let _guard = LOCK.run_exclusively().await; run_unified_tests(&["collection-management"]) // The driver does not support modifyCollection. - .skip_files(&["modifyCollection-pre_and_post_images.json"]) + .skip_files(&["modifyCollection-pre_and_post_images.json", "modifyCollection-errorResponse.json"]) .await; } diff --git a/src/test/spec/command_monitoring.rs b/src/test/spec/command_monitoring.rs new file mode 100644 index 000000000..5edd26e4e --- /dev/null +++ b/src/test/spec/command_monitoring.rs @@ -0,0 +1,18 @@ +use crate::test::spec::unified_runner::run_unified_tests; + +#[tokio::test(flavor = "multi_thread")] +async fn command_monitoring_unified() { + run_unified_tests(&["command-logging-and-monitoring", "monitoring"]) + .skip_files(&[ + // TODO RUST-1599: Unskip this file + "find.json", + // The driver does not support unacknowledged writes. + "unacknowledged-client-bulkWrite.json", + ]) + .skip_tests(&[ + // This test relies on old OP_QUERY behavior that many drivers still use for < 4.4, but + // we do not use, due to never implementing OP_QUERY. + "A successful find event with a getmore and the server kills the cursor (<= 4.4)", + ]) + .await; +} diff --git a/src/test/spec/command_monitoring/event.rs b/src/test/spec/command_monitoring/event.rs deleted file mode 100644 index 5d203b2fa..000000000 --- a/src/test/spec/command_monitoring/event.rs +++ /dev/null @@ -1,93 +0,0 @@ -use serde::Deserialize; - -use crate::{ - bson::Document, - test::{eq_matches, CommandEvent, MatchErrExt, Matchable}, -}; - -#[derive(Debug, Deserialize, PartialEq)] -pub enum TestEvent { - #[serde(rename = "command_started_event")] - Started { - command_name: String, - database_name: String, - command: Document, - }, - #[serde(rename = "command_succeeded_event")] - Succeeded { - command_name: String, - reply: Document, - }, - #[serde(rename = "command_failed_event")] - Failed { command_name: String }, -} - -impl Matchable for TestEvent { - fn content_matches(&self, actual: &TestEvent) -> Result<(), String> { - match (self, actual) { - ( - TestEvent::Started { - command_name: actual_command_name, - database_name: actual_database_name, - command: actual_command, - }, - TestEvent::Started { - command_name: expected_command_name, - database_name: expected_database_name, - command: expected_command, - }, - ) => { - eq_matches("command_name", actual_command_name, expected_command_name)?; - eq_matches( - "database_name", - actual_database_name, - expected_database_name, - )?; - actual_command.matches(expected_command).prefix("command")?; - Ok(()) - } - ( - TestEvent::Succeeded { - command_name: actual_command_name, - reply: actual_reply, - }, - TestEvent::Succeeded { - command_name: expected_command_name, - reply: expected_reply, - }, - ) => { - eq_matches("command_name", actual_command_name, expected_command_name)?; - actual_reply.matches(expected_reply).prefix("reply")?; - Ok(()) - } - ( - TestEvent::Failed { - command_name: actual_command_name, - }, - TestEvent::Failed { - command_name: expected_command_name, - }, - ) => eq_matches("command_name", actual_command_name, expected_command_name), - _ => Err(format!("expected event {:?}, got {:?}", self, actual)), - } - } -} - -impl From for TestEvent { - fn from(event: CommandEvent) -> Self { - match event { - CommandEvent::Started(event) => TestEvent::Started { - command_name: event.command_name, - database_name: event.db, - command: event.command, - }, - CommandEvent::Failed(event) => TestEvent::Failed { - command_name: event.command_name, - }, - CommandEvent::Succeeded(event) => TestEvent::Succeeded { - command_name: event.command_name, - reply: event.reply, - }, - } - } -} diff --git a/src/test/spec/command_monitoring/mod.rs b/src/test/spec/command_monitoring/mod.rs deleted file mode 100644 index 25829da5f..000000000 --- a/src/test/spec/command_monitoring/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::test::{spec::unified_runner::run_unified_tests, LOCK}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn command_monitoring_unified() { - let _guard = LOCK.run_exclusively().await; - run_unified_tests(&["command-logging-and-monitoring", "monitoring"]) - .skip_tests(&[ - // This test relies on old OP_QUERY behavior that many drivers still use for < 4.4, but - // we do not use, due to never implementing OP_QUERY. - "A successful find event with a getmore and the server kills the cursor (<= 4.4)", - ]) - .await; -} diff --git a/src/test/spec/command_monitoring/operation.rs b/src/test/spec/command_monitoring/operation.rs deleted file mode 100644 index 89c7d9578..000000000 --- a/src/test/spec/command_monitoring/operation.rs +++ /dev/null @@ -1,297 +0,0 @@ -use std::{ops::Deref, time::Duration}; - -use futures::{future::BoxFuture, stream::StreamExt, FutureExt}; -use serde::{ - de::{self, Deserializer}, - Deserialize, -}; - -use crate::{ - bson::{Bson, Deserializer as BsonDeserializer, Document}, - bson_util, - error::Result, - options::{FindOptions, Hint, InsertManyOptions, UpdateOptions}, - test::util::{CommandEvent, EventClient}, - Collection, -}; - -pub(super) trait TestOperation { - /// The command names to monitor as part of this test. - fn command_names(&self) -> &[&str]; - - fn execute(&self, collection: Collection) -> BoxFuture>; -} - -pub(super) struct AnyTestOperation { - operation: Box, -} - -impl<'de> Deserialize<'de> for AnyTestOperation { - fn deserialize>(deserializer: D) -> std::result::Result { - #[derive(Deserialize)] - struct OperationDefinition { - name: String, - arguments: Bson, - } - - let definition = OperationDefinition::deserialize(deserializer)?; - let boxed_op = match definition.name.as_str() { - "insertOne" => InsertOne::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "insertMany" => InsertMany::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "updateOne" => UpdateOne::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "updateMany" => UpdateMany::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "deleteMany" => DeleteMany::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "deleteOne" => DeleteOne::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - "find" => Find::deserialize(BsonDeserializer::new(definition.arguments)) - .map(|op| Box::new(op) as Box), - _ => unimplemented!(), - } - .map_err(|e| de::Error::custom(format!("{}", e)))?; - - Ok(AnyTestOperation { - operation: boxed_op, - }) - } -} - -impl Deref for AnyTestOperation { - type Target = Box; - - fn deref(&self) -> &Box { - &self.operation - } -} - -#[derive(Deserialize)] -pub(super) struct DeleteMany { - filter: Document, -} - -impl TestOperation for DeleteMany { - fn command_names(&self) -> &[&str] { - &["delete"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - collection - .delete_many(self.filter.clone(), None) - .await - .map(|_| ()) - } - .boxed() - } -} - -#[derive(Deserialize)] -pub(super) struct DeleteOne { - filter: Document, -} - -impl TestOperation for DeleteOne { - fn command_names(&self) -> &[&str] { - &["delete"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - collection - .delete_one(self.filter.clone(), None) - .await - .map(|_| ()) - } - .boxed() - } -} - -#[derive(Debug, Default, Deserialize)] -#[serde(deny_unknown_fields)] -pub(super) struct Find { - filter: Option, - // `FindOptions` cannot be embedded directly because serde doesn't support combining `flatten` - // and `deny_unknown_fields`, so its fields are replicated here. - #[serde(default)] - sort: Option, - #[serde(default)] - skip: Option, - #[serde(default, rename = "batchSize")] - batch_size: Option, - #[serde(default)] - limit: Option, - #[serde(default)] - comment: Option, - #[serde(default)] - hint: Option, - #[serde( - rename = "maxTimeMS", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis", - default - )] - max_time: Option, - #[serde(default)] - min: Option, - #[serde(default)] - max: Option, - #[serde(rename = "returnKey", default)] - return_key: Option, - #[serde(rename = "showRecordId", default)] - show_record_id: Option, -} - -impl TestOperation for Find { - fn command_names(&self) -> &[&str] { - &["find", "getMore"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - // `FindOptions` is constructed without the use of `..Default::default()` to enforce at - // compile-time that any new fields added there need to be considered here. - let options = FindOptions { - sort: self.sort.clone(), - skip: self.skip, - batch_size: self.batch_size.map(|i| i as u32), - limit: self.limit, - comment: self.comment.clone(), - hint: self.hint.clone(), - max_time: self.max_time, - min: self.min.clone(), - max: self.max.clone(), - return_key: self.return_key, - show_record_id: self.show_record_id, - allow_disk_use: None, - allow_partial_results: None, - cursor_type: None, - max_await_time: None, - max_scan: None, - no_cursor_timeout: None, - projection: None, - read_concern: None, - selection_criteria: None, - collation: None, - }; - - let mut cursor = collection.find(self.filter.clone(), options).await?; - - while let Some(result) = cursor.next().await { - result?; - } - Ok(()) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct InsertMany { - documents: Vec, - #[serde(default)] - options: Option, -} - -impl TestOperation for InsertMany { - fn command_names(&self) -> &[&str] { - &["insert"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - collection - .insert_many(self.documents.clone(), self.options.clone()) - .await - .map(|_| ()) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct InsertOne { - document: Document, -} - -impl TestOperation for InsertOne { - fn command_names(&self) -> &[&str] { - &["insert"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - collection - .insert_one(self.document.clone(), None) - .await - .map(|_| ()) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct UpdateMany { - filter: Document, - update: Document, -} - -impl TestOperation for UpdateMany { - fn command_names(&self) -> &[&str] { - &["update"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - collection - .update_many(self.filter.clone(), self.update.clone(), None) - .await - .map(|_| ()) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct UpdateOne { - filter: Document, - update: Document, - #[serde(default)] - upsert: Option, -} - -impl TestOperation for UpdateOne { - fn command_names(&self) -> &[&str] { - &["update"] - } - - fn execute(&self, collection: Collection) -> BoxFuture> { - async move { - let options = self.upsert.map(|b| UpdateOptions { - upsert: Some(b), - ..Default::default() - }); - collection - .update_one(self.filter.clone(), self.update.clone(), options) - .await - .map(|_| ()) - } - .boxed() - } -} - -impl EventClient { - pub(super) async fn run_operation_with_events( - &self, - operation: AnyTestOperation, - database_name: &str, - collection_name: &str, - ) -> Vec { - let _: Result<_> = operation - .execute(self.database(database_name).collection(collection_name)) - .await; - self.get_command_events(operation.command_names()) - } -} diff --git a/src/test/spec/connection_stepdown.rs b/src/test/spec/connection_stepdown.rs index 08a7fa3d4..18e572bca 100644 --- a/src/test/spec/connection_stepdown.rs +++ b/src/test/spec/connection_stepdown.rs @@ -1,22 +1,20 @@ use std::{future::Future, time::Duration}; use futures::stream::StreamExt; -use tokio::sync::RwLockWriteGuard; use crate::{ bson::{doc, Document}, error::{CommandError, ErrorKind}, - options::{ - Acknowledgment, - ClientOptions, - CreateCollectionOptions, - DropCollectionOptions, - FindOptions, - InsertManyOptions, - WriteConcern, + options::{Acknowledgment, WriteConcern}, + selection_criteria::SelectionCriteria, + test::{ + get_client_options, + log_uncaptured, + server_version_eq, + server_version_lt, + topology_is_replica_set, + EventClient, }, - runtime, - test::{log_uncaptured, util::EventClient, CLIENT_OPTIONS, LOCK}, Collection, Database, }; @@ -25,15 +23,7 @@ async fn run_test( name: &str, test: impl Fn(EventClient, Database, Collection) -> F, ) { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let options = ClientOptions::builder() - .hosts(CLIENT_OPTIONS.get().await.hosts.clone()) - .retry_writes(false) - .build(); - let client = EventClient::with_additional_options(Some(options), None, None, None).await; - - if !client.is_replica_set() { + if !topology_is_replica_set().await { log_uncaptured(format!( "skipping test {:?} due to not running on a replica set", name @@ -41,6 +31,14 @@ async fn run_test( return; } + let mut options = get_client_options().await.clone(); + options.retry_writes = Some(false); + let client = crate::Client::for_test() + .options(options) + .use_single_mongos() + .monitor_events() + .await; + let name = format!("step-down-{}", name); let db = client.database(&name); @@ -48,59 +46,47 @@ async fn run_test( let wc_majority = WriteConcern::builder().w(Acknowledgment::Majority).build(); - let _: Result<_, _> = coll - .drop(Some( - DropCollectionOptions::builder() - .write_concern(wc_majority.clone()) - .build(), - )) - .await; + let _: Result<_, _> = coll.drop().write_concern(wc_majority.clone()).await; - db.create_collection( - &name, - Some( - CreateCollectionOptions::builder() - .write_concern(wc_majority) - .build(), - ), - ) - .await - .unwrap(); + db.create_collection(&name) + .write_concern(wc_majority) + .await + .unwrap(); test(client, db, coll).await; } -#[function_name::named] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn get_more() { async fn get_more_test(client: EventClient, _db: Database, coll: Collection) { // This test requires server version 4.2 or higher. - if client.server_version_lt(4, 2) { + if server_version_lt(4, 2).await { log_uncaptured("skipping get_more due to server version < 4.2"); return; } let docs = vec![doc! { "x": 1 }; 5]; - coll.insert_many( - docs, - Some( - InsertManyOptions::builder() - .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build()) - .build(), - ), - ) - .await - .unwrap(); - - let mut cursor = coll - .find(None, Some(FindOptions::builder().batch_size(2).build())) + coll.insert_many(docs) + .write_concern(WriteConcern::majority()) .await .unwrap(); + let mut cursor = coll.find(doc! {}).batch_size(2).await.unwrap(); + + let db = client.database("admin"); + + db.run_command(doc! { "replSetFreeze": 0 }) + .selection_criteria(SelectionCriteria::ReadPreference( + crate::selection_criteria::ReadPreference::Secondary { + options: Default::default(), + }, + )) + .await + .expect("replSetFreeze should have succeeded"); + client .database("admin") - .run_command(doc! { "replSetStepDown": 5, "force": true }, None) + .run_command(doc! { "replSetStepDown": 30, "force": true }) .await .expect("stepdown should have succeeded"); @@ -112,16 +98,14 @@ async fn get_more() { .expect("cursor iteration should have succeeded"); } - runtime::delay_for(Duration::from_millis(250)).await; - assert_eq!(client.count_pool_cleared_events(), 0); + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!(client.events.count_pool_cleared_events(), 0); } - run_test(function_name!(), get_more_test).await; + run_test("get_more", get_more_test).await; } -#[function_name::named] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn notwritableprimary_keep_pool() { async fn notwritableprimary_keep_pool_test( client: EventClient, @@ -129,28 +113,25 @@ async fn notwritableprimary_keep_pool() { coll: Collection, ) { // This test requires server version 4.2 or higher. - if client.server_version_lt(4, 2) { + if server_version_lt(4, 2).await { log_uncaptured("skipping notwritableprimary_keep_pool due to server version < 4.2"); return; } client .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "mode": { "times": 1 }, - "data": { - "failCommands": ["insert"], - "errorCode": 10107 - } - }, - None, - ) + .run_command(doc! { + "configureFailPoint": "failCommand", + "mode": { "times": 1 }, + "data": { + "failCommands": ["insert"], + "errorCode": 10107 + } + }) .await .unwrap(); - let result = coll.insert_one(doc! { "test": 1 }, None).await; + let result = coll.insert_one(doc! { "test": 1 }).await; assert!( matches!( result.map_err(|e| *e.kind), @@ -159,19 +140,22 @@ async fn notwritableprimary_keep_pool() { "insert should have failed" ); - coll.insert_one(doc! { "test": 1 }, None) + coll.insert_one(doc! { "test": 1 }) .await .expect("insert should have succeeded"); - runtime::delay_for(Duration::from_millis(250)).await; - assert_eq!(client.count_pool_cleared_events(), 0); + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!(client.events.count_pool_cleared_events(), 0); } - run_test(function_name!(), notwritableprimary_keep_pool_test).await; + run_test( + "notwritableprimary_keep_pool", + notwritableprimary_keep_pool_test, + ) + .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn notwritableprimary_reset_pool() { async fn notwritableprimary_reset_pool_test( client: EventClient, @@ -179,7 +163,7 @@ async fn notwritableprimary_reset_pool() { coll: Collection, ) { // This test must only run on 4.0 servers. - if !client.server_version_eq(4, 0) { + if !server_version_eq(4, 0).await { log_uncaptured( "skipping notwritableprimary_reset_pool due to unsupported server version", ); @@ -188,21 +172,18 @@ async fn notwritableprimary_reset_pool() { client .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "mode": { "times": 1 }, - "data": { - "failCommands": ["insert"], - "errorCode": 10107 - } - }, - None, - ) + .run_command(doc! { + "configureFailPoint": "failCommand", + "mode": { "times": 1 }, + "data": { + "failCommands": ["insert"], + "errorCode": 10107 + } + }) .await .unwrap(); - let result = coll.insert_one(doc! { "test": 1 }, None).await; + let result = coll.insert_one(doc! { "test": 1 }).await; assert!( matches!( result.map_err(|e| *e.kind), @@ -211,10 +192,10 @@ async fn notwritableprimary_reset_pool() { "insert should have failed" ); - runtime::delay_for(Duration::from_millis(250)).await; - assert_eq!(client.count_pool_cleared_events(), 1); + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!(client.events.count_pool_cleared_events(), 1); - coll.insert_one(doc! { "test": 1 }, None) + coll.insert_one(doc! { "test": 1 }) .await .expect("insert should have succeeded"); } @@ -226,37 +207,27 @@ async fn notwritableprimary_reset_pool() { .await; } -#[function_name::named] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn shutdown_in_progress() { async fn shutdown_in_progress_test( client: EventClient, _db: Database, coll: Collection, ) { - if client.server_version_lt(4, 0) { - log_uncaptured("skipping shutdown_in_progress due to server version < 4.0"); - return; - } - client .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "mode": { "times": 1 }, - "data": { - "failCommands": ["insert"], - "errorCode": 91 - } - }, - None, - ) + .run_command(doc! { + "configureFailPoint": "failCommand", + "mode": { "times": 1 }, + "data": { + "failCommands": ["insert"], + "errorCode": 91 + } + }) .await .unwrap(); - let result = coll.insert_one(doc! { "test": 1 }, None).await; + let result = coll.insert_one(doc! { "test": 1 }).await; assert!( matches!( result.map_err(|e| *e.kind), @@ -265,48 +236,38 @@ async fn shutdown_in_progress() { "insert should have failed" ); - runtime::delay_for(Duration::from_millis(250)).await; - assert_eq!(client.count_pool_cleared_events(), 1); + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!(client.events.count_pool_cleared_events(), 1); - coll.insert_one(doc! { "test": 1 }, None) + coll.insert_one(doc! { "test": 1 }) .await .expect("insert should have succeeded"); } - run_test(function_name!(), shutdown_in_progress_test).await; + run_test("shutdown_in_progress", shutdown_in_progress_test).await; } -#[function_name::named] -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn interrupted_at_shutdown() { async fn interrupted_at_shutdown_test( client: EventClient, _db: Database, coll: Collection, ) { - if client.server_version_lt(4, 0) { - log_uncaptured("skipping interrupted_at_shutdown due to server version < 4.2"); - return; - } - client .database("admin") - .run_command( - doc! { - "configureFailPoint": "failCommand", - "mode": { "times": 1 }, - "data": { - "failCommands": ["insert"], - "errorCode": 11600 - } - }, - None, - ) + .run_command(doc! { + "configureFailPoint": "failCommand", + "mode": { "times": 1 }, + "data": { + "failCommands": ["insert"], + "errorCode": 11600 + } + }) .await .unwrap(); - let result = coll.insert_one(doc! { "test": 1 }, None).await; + let result = coll.insert_one(doc! { "test": 1 }).await; assert!( matches!( result.map_err(|e| *e.kind), @@ -315,15 +276,15 @@ async fn interrupted_at_shutdown() { "insert should have failed" ); - runtime::delay_for(Duration::from_millis(250)).await; - assert_eq!(client.count_pool_cleared_events(), 1); + tokio::time::sleep(Duration::from_millis(250)).await; + assert_eq!(client.events.count_pool_cleared_events(), 1); - coll.insert_one(doc! { "test": 1 }, None) + coll.insert_one(doc! { "test": 1 }) .await .expect("insert should have succeeded"); - runtime::delay_for(Duration::from_millis(250)).await; + tokio::time::sleep(Duration::from_millis(250)).await; } - run_test(function_name!(), interrupted_at_shutdown_test).await; + run_test("interrupted_at_shutdown", interrupted_at_shutdown_test).await; } diff --git a/src/test/spec/crud.rs b/src/test/spec/crud.rs index 54df1f5db..dcbd52dff 100644 --- a/src/test/spec/crud.rs +++ b/src/test/spec/crud.rs @@ -1,40 +1,88 @@ -use tokio::sync::RwLockWriteGuard; +use crate::{ + bson::doc, + test::{log_uncaptured, server_version_lt, spec::unified_runner::run_unified_tests}, + Client, +}; -use crate::test::{spec::unified_runner::run_unified_tests, LOCK}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; + let skipped_files = vec![ + // The Rust driver does not support unacknowledged writes (and does not intend to in + // the future). + "bulkWrite-deleteMany-hint-unacknowledged.json", + "bulkWrite-deleteOne-hint-unacknowledged.json", + "bulkWrite-replaceOne-hint-unacknowledged.json", + "bulkWrite-updateMany-hint-unacknowledged.json", + "bulkWrite-updateOne-hint-unacknowledged.json", + "deleteMany-hint-unacknowledged.json", + "deleteOne-hint-unacknowledged.json", + "findOneAndDelete-hint-unacknowledged.json", + "findOneAndReplace-hint-unacknowledged.json", + "findOneAndUpdate-hint-unacknowledged.json", + "replaceOne-hint-unacknowledged.json", + "updateMany-hint-unacknowledged.json", + "updateOne-hint-unacknowledged.json", + // TODO RUST-1405: unskip the errorResponse tests + "client-bulkWrite-errorResponse.json", + "bulkWrite-errorResponse.json", + "updateOne-errorResponse.json", + "insertOne-errorResponse.json", + "deleteOne-errorResponse.json", + "aggregate-merge-errorResponse.json", + "findOneAndUpdate-errorResponse.json", + ]; + + let skipped_tests = vec![ + // Unacknowledged write; see above. + "Unacknowledged write using dollar-prefixed or dotted keys may be silently rejected on \ + pre-5.0 server", + "Requesting unacknowledged write with verboseResults is a client-side error", + "Requesting unacknowledged write with ordered is a client-side error", + ]; + run_unified_tests(&["crud", "unified"]) - .skip_files(&[ - // The Rust driver does not support unacknowledged writes (and does not intend to in - // the future). - "bulkWrite-deleteMany-hint-unacknowledged.json", - "bulkWrite-deleteOne-hint-unacknowledged.json", - "bulkWrite-replaceOne-hint-unacknowledged.json", - "bulkWrite-updateMany-hint-unacknowledged.json", - "bulkWrite-updateOne-hint-unacknowledged.json", - "deleteMany-hint-unacknowledged.json", - "deleteOne-hint-unacknowledged.json", - "findOneAndDelete-hint-unacknowledged.json", - "findOneAndReplace-hint-unacknowledged.json", - "findOneAndUpdate-hint-unacknowledged.json", - "replaceOne-hint-unacknowledged.json", - "updateMany-hint-unacknowledged.json", - "updateOne-hint-unacknowledged.json", - ]) - .skip_tests(&[ - // Unacknowledged write; see above. - "Unacknowledged write using dollar-prefixed or dotted keys may be silently rejected \ - on pre-5.0 server", - // TODO RUST-663: Unskip these tests. - "Aggregate with $out includes read preference for 5.0+ server", - "Aggregate with $out omits read preference for pre-5.0 server", - "Aggregate with $merge includes read preference for 5.0+ server", - "Aggregate with $merge omits read preference for pre-5.0 server", - "Database-level aggregate with $out omits read preference for pre-5.0 server", - "Database-level aggregate with $merge omits read preference for pre-5.0 server", - ]) + .skip_files(&skipped_files) + .skip_tests(&skipped_tests) .await; } + +#[tokio::test] +async fn generated_id_first_field() { + let client = Client::for_test().monitor_events().await; + let events = &client.events; + let collection = client.database("db").collection("coll"); + + collection.insert_one(doc! { "x": 1 }).await.unwrap(); + let insert_events = events.get_command_started_events(&["insert"]); + let insert_document = insert_events[0] + .command + .get_array("documents") + .unwrap() + .first() + .unwrap() + .as_document() + .unwrap(); + let (key, _) = insert_document.iter().next().unwrap(); + assert_eq!(key, "_id"); + + if server_version_lt(8, 0).await { + log_uncaptured("skipping bulk write test in generated_id_first_field"); + return; + } + + let insert_one_model = collection.insert_one_model(doc! { "y": 2 }).unwrap(); + client.bulk_write(vec![insert_one_model]).await.unwrap(); + let bulk_write_events = events.get_command_started_events(&["bulkWrite"]); + let insert_operation = bulk_write_events[0] + .command + .get_array("ops") + .unwrap() + .first() + .unwrap() + .as_document() + .unwrap(); + assert!(insert_operation.contains_key("insert")); + let insert_document = insert_operation.get_document("document").unwrap(); + let (key, _) = insert_document.iter().next().unwrap(); + assert_eq!(key, "_id"); +} diff --git a/src/test/spec/crud_v1/aggregate.rs b/src/test/spec/crud_v1/aggregate.rs deleted file mode 100644 index fc1a67628..000000000 --- a/src/test/spec/crud_v1/aggregate.rs +++ /dev/null @@ -1,98 +0,0 @@ -use futures::stream::TryStreamExt; -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{AggregateOptions, Collation}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub pipeline: Vec, - pub batch_size: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_aggregate_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; - - let data = test_file.data; - - for test_case in test_file.tests { - if test_case.operation.name != "aggregate" { - continue; - } - - let coll = client - .init_db_and_coll( - function_name!(), - &test_case.description.replace('$', "%").replace(' ', "_"), - ) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome>> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = AggregateOptions { - batch_size: arguments.batch_size, - collation: arguments.collation, - ..Default::default() - }; - - { - let cursor = coll - .aggregate(arguments.pipeline, options) - .await - .expect(&test_case.description); - - let results = cursor - .try_collect::>() - .await - .expect(&test_case.description); - - assert_eq!( - outcome.result.unwrap_or_default(), - results, - "{}", - test_case.description, - ); - } - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "read"], run_aggregate_test).await; -} diff --git a/src/test/spec/crud_v1/count.rs b/src/test/spec/crud_v1/count.rs deleted file mode 100644 index f2990200d..000000000 --- a/src/test/spec/crud_v1/count.rs +++ /dev/null @@ -1,80 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, CountOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -struct Arguments { - pub filter: Option, - pub skip: Option, - pub limit: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_count_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - let lower_description = test_case.description.to_lowercase(); - - // old `count` not implemented, collation not implemented - if !test_case.operation.name.contains("count") || lower_description.contains("deprecated") { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - - if !data.is_empty() { - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - } - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let result = match test_case.operation.name.as_str() { - "countDocuments" => { - let options = CountOptions::builder() - .skip(arguments.skip) - .limit(arguments.limit) - .collation(arguments.collation) - .build(); - coll.count_documents(arguments.filter.unwrap_or_default(), options) - .await - } - "estimatedDocumentCount" => coll.estimated_document_count(None).await, - other => panic!("unexpected count operation: {}", other), - } - .expect(&test_case.description); - - assert_eq!(result, outcome.result, "{}", test_case.description); - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "read"], run_count_test).await; -} diff --git a/src/test/spec/crud_v1/delete_many.rs b/src/test/spec/crud_v1/delete_many.rs deleted file mode 100644 index 8680e57ae..000000000 --- a/src/test/spec/crud_v1/delete_many.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, DeleteOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub collation: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - pub deleted_count: u64, -} - -#[function_name::named] -async fn run_delete_many_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "deleteMany" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = DeleteOptions { - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .delete_many(arguments.filter, options) - .await - .expect(&test_case.description); - - assert_eq!( - outcome.result.deleted_count, result.deleted_count, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_delete_many_test).await; -} diff --git a/src/test/spec/crud_v1/delete_one.rs b/src/test/spec/crud_v1/delete_one.rs deleted file mode 100644 index 216393c6c..000000000 --- a/src/test/spec/crud_v1/delete_one.rs +++ /dev/null @@ -1,92 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, DeleteOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub collation: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - pub deleted_count: u64, -} - -#[function_name::named] -async fn run_delete_one_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "deleteOne" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let opts = DeleteOptions { - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .delete_one(arguments.filter, opts) - .await - .expect(&test_case.description); - - assert_eq!( - outcome.result.deleted_count, result.deleted_count, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_delete_one_test).await; -} diff --git a/src/test/spec/crud_v1/distinct.rs b/src/test/spec/crud_v1/distinct.rs deleted file mode 100644 index 91f02c6d9..000000000 --- a/src/test/spec/crud_v1/distinct.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, DistinctOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Option, - pub field_name: String, - pub collation: Option, -} - -#[function_name::named] -async fn run_distinct_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "distinct" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let opts = DistinctOptions { - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .distinct(&arguments.field_name, arguments.filter, opts) - .await - .expect(&test_case.description); - assert_eq!(result, outcome.result, "{}", test_case.description); - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "read"], run_distinct_test).await; -} diff --git a/src/test/spec/crud_v1/find.rs b/src/test/spec/crud_v1/find.rs deleted file mode 100644 index 8ae079755..000000000 --- a/src/test/spec/crud_v1/find.rs +++ /dev/null @@ -1,79 +0,0 @@ -use futures::stream::TryStreamExt; -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, FindOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub skip: Option, - pub limit: Option, - pub batch_size: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_find_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "find" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = FindOptions { - skip: arguments.skip, - limit: arguments.limit, - batch_size: arguments.batch_size, - collation: arguments.collation, - ..Default::default() - }; - - let cursor = coll - .find(arguments.filter, options) - .await - .expect(&test_case.description); - assert_eq!( - outcome.result, - cursor.try_collect::>().await.unwrap(), - "{}", - test_case.description, - ); - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "read"], run_find_test).await; -} diff --git a/src/test/spec/crud_v1/find_one_and_delete.rs b/src/test/spec/crud_v1/find_one_and_delete.rs deleted file mode 100644 index 208cb7fe1..000000000 --- a/src/test/spec/crud_v1/find_one_and_delete.rs +++ /dev/null @@ -1,85 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, FindOneAndDeleteOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub projection: Option, - pub sort: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_find_one_and_delete_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "findOneAndDelete" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = FindOneAndDeleteOptions { - projection: arguments.projection, - sort: arguments.sort, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .find_one_and_delete(arguments.filter, options) - .await - .expect(&test_case.description); - assert_eq!(result, outcome.result, "{}", test_case.description); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_find_one_and_delete_test).await; -} diff --git a/src/test/spec/crud_v1/find_one_and_replace.rs b/src/test/spec/crud_v1/find_one_and_replace.rs deleted file mode 100644 index e2ebbc157..000000000 --- a/src/test/spec/crud_v1/find_one_and_replace.rs +++ /dev/null @@ -1,104 +0,0 @@ -use std::cmp; - -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, FindOneAndReplaceOptions, ReturnDocument}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub replacement: Document, - pub bypass_document_validation: Option, - pub projection: Option, - pub return_document: Option, - pub sort: Option, - pub upsert: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_find_one_and_replace_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "findOneAndReplace" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - let sub = cmp::min(test_case.description.len(), 50); - let coll = client - .init_db_and_coll(function_name!(), &test_case.description[..sub]) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - let new = match arguments.return_document.as_ref() { - Some(s) if s == "Before" => Some(ReturnDocument::Before), - Some(s) if s == "After" => Some(ReturnDocument::After), - _ => None, - }; - - let options = FindOneAndReplaceOptions { - bypass_document_validation: arguments.bypass_document_validation, - projection: arguments.projection, - return_document: new, - sort: arguments.sort, - upsert: arguments.upsert, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .find_one_and_replace(arguments.filter, arguments.replacement, options) - .await - .expect(&test_case.description); - assert_eq!( - result, outcome.result, - "{} - ", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_find_one_and_replace_test).await; -} diff --git a/src/test/spec/crud_v1/find_one_and_update.rs b/src/test/spec/crud_v1/find_one_and_update.rs deleted file mode 100644 index 65ab5da3e..000000000 --- a/src/test/spec/crud_v1/find_one_and_update.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::cmp; - -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, FindOneAndUpdateOptions, ReturnDocument}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub update: Document, - pub array_filters: Option>, - pub bypass_document_validation: Option, - pub projection: Option, - pub return_document: Option, - pub sort: Option, - pub upsert: Option, - pub collation: Option, -} - -#[function_name::named] -async fn run_find_one_and_update_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "findOneAndUpdate" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - let sub = cmp::min(test_case.description.len(), 50); - let coll = client - .init_db_and_coll(function_name!(), &test_case.description[..sub]) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome> = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let new = match arguments.return_document.as_ref() { - Some(s) if s == "Before" => Some(ReturnDocument::Before), - Some(s) if s == "After" => Some(ReturnDocument::After), - _ => None, - }; - - let options = FindOneAndUpdateOptions { - bypass_document_validation: arguments.bypass_document_validation, - array_filters: arguments.array_filters, - projection: arguments.projection, - return_document: new, - sort: arguments.sort, - upsert: arguments.upsert, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .find_one_and_update(arguments.filter, arguments.update, options) - .await - .expect(&test_case.description); - assert_eq!( - result, outcome.result, - "{} - ", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_find_one_and_update_test).await; -} diff --git a/src/test/spec/crud_v1/insert_many.rs b/src/test/spec/crud_v1/insert_many.rs deleted file mode 100644 index 24039faea..000000000 --- a/src/test/spec/crud_v1/insert_many.rs +++ /dev/null @@ -1,110 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::InsertManyOptions, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -struct Arguments { - pub documents: Vec, - pub options: Options, -} - -#[derive(Debug, Deserialize)] -struct Options { - pub ordered: bool, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - inserted_ids: Option, -} - -#[function_name::named] -async fn run_insert_many_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for test_case in test_file.tests { - if test_case.operation.name != "insertMany" { - continue; - } - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - let options = InsertManyOptions::builder() - .ordered(arguments.options.ordered) - .build(); - - let result = match coll.insert_many(arguments.documents, options).await { - Ok(result) => { - assert_ne!(outcome.error, Some(true), "{}", test_case.description); - result.inserted_ids - } - Err(e) => { - assert!( - outcome.error.unwrap_or(false), - "{}: expected no error, got {:?}", - test_case.description, - e - ); - Default::default() - } - }; - - if let Some(outcome_result_inserted_ids) = outcome.result.inserted_ids { - let mut result_inserted_ids: Vec<_> = result - .into_iter() - .map(|(index, val)| (index.to_string(), val)) - .collect(); - result_inserted_ids.sort_by(|pair1, pair2| pair1.0.cmp(&pair2.0)); - - let mut outcome_result_inserted_ids: Vec<_> = - outcome_result_inserted_ids.into_iter().collect(); - outcome_result_inserted_ids.sort_by(|pair1, pair2| pair1.0.cmp(&pair2.0)); - - assert_eq!( - outcome_result_inserted_ids, result_inserted_ids, - "{}", - test_case.description - ); - } - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_insert_many_test).await; -} diff --git a/src/test/spec/crud_v1/insert_one.rs b/src/test/spec/crud_v1/insert_one.rs deleted file mode 100644 index 4ac02a6bd..000000000 --- a/src/test/spec/crud_v1/insert_one.rs +++ /dev/null @@ -1,83 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -struct Arguments { - pub document: Document, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - inserted_id: Bson, -} - -#[function_name::named] -async fn run_insert_one_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "insertOne" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let result = coll - .insert_one(arguments.document, None) - .await - .expect(&test_case.description); - assert_eq!( - outcome.result.inserted_id, result.inserted_id, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_insert_one_test).await; -} diff --git a/src/test/spec/crud_v1/mod.rs b/src/test/spec/crud_v1/mod.rs deleted file mode 100644 index 1b955d50d..000000000 --- a/src/test/spec/crud_v1/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -mod aggregate; -mod count; -mod delete_many; -mod delete_one; -mod distinct; -mod find; -mod find_one_and_delete; -mod find_one_and_replace; -mod find_one_and_update; -mod insert_many; -mod insert_one; -mod replace_one; -mod update_many; -mod update_one; - -use std::future::Future; - -use futures::stream::TryStreamExt; -use serde::Deserialize; - -use crate::{ - bson::{doc, Document}, - coll::options::FindOptions, - test::log_uncaptured, - Collection, -}; - -use super::{run_spec_test, Serverless}; - -#[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields, rename_all = "camelCase")] -pub struct TestFile { - pub data: Vec, - pub min_server_version: Option, - pub(crate) serverless: Option, - pub tests: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct TestCase { - pub description: String, - pub operation: Operation, - pub outcome: Document, -} - -#[derive(Debug, Deserialize)] -pub struct Operation { - name: String, - arguments: Document, -} - -#[derive(Debug, Deserialize)] -pub struct Outcome { - pub result: R, - pub error: Option, - pub collection: Option, -} - -#[derive(Debug, Deserialize)] -pub struct CollectionOutcome { - pub name: Option, - pub data: Vec, -} - -pub async fn find_all(coll: &Collection) -> Vec { - let options = FindOptions::builder().sort(doc! { "_id": 1 }).build(); - coll.find(None, options) - .await - .unwrap() - .try_collect() - .await - .unwrap() -} - -pub async fn run_crud_v1_test(spec: &[&str], run_test_file: F) -where - F: Fn(TestFile) -> G, - G: Future, -{ - run_spec_test(spec, |t: TestFile| async { - if let Some(ref serverless) = t.serverless { - if !serverless.can_run() { - log_uncaptured("skipping crud_v1_test"); - return; - } - } - - run_test_file(t).await - }) - .await -} diff --git a/src/test/spec/crud_v1/replace_one.rs b/src/test/spec/crud_v1/replace_one.rs deleted file mode 100644 index 6bfb91a5b..000000000 --- a/src/test/spec/crud_v1/replace_one.rs +++ /dev/null @@ -1,115 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, ReplaceOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub replacement: Document, - pub upsert: Option, - pub collation: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - pub matched_count: u64, - pub modified_count: u64, - pub upserted_count: Option, - pub upserted_id: Option, -} - -#[function_name::named] -async fn run_replace_one_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for test_case in test_file.tests { - if test_case.operation.name != "replaceOne" { - continue; - } - - let coll = client - .init_db_and_coll( - function_name!(), - &test_case.description.replace('$', "%").replace(' ', "_"), - ) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = ReplaceOptions { - upsert: arguments.upsert, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .replace_one(arguments.filter, arguments.replacement, options) - .await - .expect(&test_case.description); - assert_eq!( - outcome.result.matched_count, result.matched_count, - "{}", - test_case.description - ); - assert_eq!( - outcome.result.modified_count, result.modified_count, - "{}", - test_case.description - ); - - assert_eq!( - outcome.result.upserted_count.unwrap_or(0), - u64::from(result.upserted_id.is_some()), - "{}", - test_case.description - ); - assert_eq!( - outcome.result.upserted_id, result.upserted_id, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_replace_one_test).await; -} diff --git a/src/test/spec/crud_v1/update_many.rs b/src/test/spec/crud_v1/update_many.rs deleted file mode 100644 index 68f9bac6c..000000000 --- a/src/test/spec/crud_v1/update_many.rs +++ /dev/null @@ -1,116 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, UpdateOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub update: Document, - pub upsert: Option, - pub array_filters: Option>, - pub collation: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - pub matched_count: u64, - pub modified_count: u64, - pub upserted_count: Option, - pub upserted_id: Option, -} - -#[function_name::named] -async fn run_update_many_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "updateMany" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = UpdateOptions { - upsert: arguments.upsert, - array_filters: arguments.array_filters, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .update_many(arguments.filter, arguments.update, options) - .await - .expect(&test_case.description); - assert_eq!( - outcome.result.matched_count, result.matched_count, - "{}", - test_case.description - ); - assert_eq!( - outcome.result.modified_count, result.modified_count, - "{}", - test_case.description - ); - - assert_eq!( - outcome.result.upserted_count.unwrap_or(0), - u64::from(result.upserted_id.is_some()), - "{}", - test_case.description - ); - assert_eq!( - outcome.result.upserted_id, result.upserted_id, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_update_many_test).await; -} diff --git a/src/test/spec/crud_v1/update_one.rs b/src/test/spec/crud_v1/update_one.rs deleted file mode 100644 index 264d0c4c5..000000000 --- a/src/test/spec/crud_v1/update_one.rs +++ /dev/null @@ -1,116 +0,0 @@ -use serde::Deserialize; -use tokio::sync::RwLockReadGuard; - -use super::{run_crud_v1_test, Outcome, TestFile}; -use crate::{ - bson::{Bson, Document}, - options::{Collation, UpdateOptions}, - test::{util::TestClient, LOCK}, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct Arguments { - pub filter: Document, - pub update: Document, - pub upsert: Option, - pub array_filters: Option>, - pub collation: Option, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct ResultDoc { - pub matched_count: u64, - pub modified_count: u64, - pub upserted_count: Option, - pub upserted_id: Option, -} - -#[function_name::named] -async fn run_update_one_test(test_file: TestFile) { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - let data = test_file.data; - - for mut test_case in test_file.tests { - if test_case.operation.name != "updateOne" { - continue; - } - - test_case.description = test_case.description.replace('$', "%"); - - let coll = client - .init_db_and_coll(function_name!(), &test_case.description) - .await; - coll.insert_many(data.clone(), None) - .await - .expect(&test_case.description); - - let arguments: Arguments = bson::from_bson(Bson::Document(test_case.operation.arguments)) - .expect(&test_case.description); - let outcome: Outcome = - bson::from_bson(Bson::Document(test_case.outcome)).expect(&test_case.description); - - if let Some(ref c) = outcome.collection { - if let Some(ref name) = c.name { - client.drop_collection(function_name!(), name).await; - } - } - - let options = UpdateOptions { - upsert: arguments.upsert, - array_filters: arguments.array_filters, - collation: arguments.collation, - ..Default::default() - }; - - let result = coll - .update_one(arguments.filter, arguments.update, options) - .await - .expect(&test_case.description); - assert_eq!( - outcome.result.matched_count, result.matched_count, - "{}", - test_case.description - ); - assert_eq!( - outcome.result.modified_count, result.modified_count, - "{}", - test_case.description - ); - - assert_eq!( - outcome.result.upserted_count.unwrap_or(0), - u64::from(result.upserted_id.is_some()), - "{}", - test_case.description - ); - assert_eq!( - outcome.result.upserted_id, result.upserted_id, - "{}", - test_case.description - ); - - if let Some(c) = outcome.collection { - let outcome_coll = match c.name { - Some(ref name) => client.get_coll(function_name!(), name), - None => coll, - }; - - assert_eq!( - c.data, - super::find_all(&outcome_coll).await, - "{}", - test_case.description - ); - } - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - run_crud_v1_test(&["crud", "v1", "write"], run_update_one_test).await; -} diff --git a/src/test/spec/faas.rs b/src/test/spec/faas.rs index 8f4c42b05..2562a1ee1 100644 --- a/src/test/spec/faas.rs +++ b/src/test/spec/faas.rs @@ -1,8 +1,16 @@ use std::env; -use bson::doc; - -use crate::{test::LOCK, Client}; +use crate::bson::rawdoc; + +use crate::{ + cmap::establish::handshake::{ + ClientMetadata, + FaasEnvironmentName, + RuntimeEnvironment, + BASE_CLIENT_METADATA, + }, + Client, +}; type Result = anyhow::Result; @@ -35,88 +43,178 @@ impl Drop for TempVars { } } -async fn check_faas_handshake(vars: &[(&'static str, &str)]) -> Result<()> { - let _guard = LOCK.run_exclusively().await; +async fn check_faas_handshake( + vars: &[(&'static str, &str)], + expected: &ClientMetadata, +) -> Result<()> { let _tv = TempVars::set(vars); - let client = Client::test_builder().build().await; - client.list_database_names(doc! {}, None).await?; + let client = Client::for_test().await; + client.list_database_names().await?; + #[allow(clippy::incompatible_msrv)] + let metadata = crate::cmap::establish::handshake::TEST_METADATA + .get() + .unwrap(); + assert_eq!(expected, metadata); Ok(()) } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn valid_aws() -> Result<()> { - check_faas_handshake(&[ - ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), - ("AWS_REGION", "us-east-2"), - ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"), - ]) + check_faas_handshake( + &[ + ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), + ("AWS_REGION", "us-east-2"), + ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::AwsLambda), + runtime: Some("AWS_Lambda_java8".to_string()), + memory_mb: Some(1024), + region: Some("us-east-2".to_string()), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn valid_azure() -> Result<()> { - check_faas_handshake(&[("FUNCTIONS_WORKER_RUNTIME", "node")]).await + check_faas_handshake( + &[("FUNCTIONS_WORKER_RUNTIME", "node")], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::AzureFunc), + runtime: Some("node".to_string()), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) + .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn valid_gcp() -> Result<()> { - check_faas_handshake(&[ - ("K_SERVICE", "servicename"), - ("FUNCTION_MEMORY_MB", "1024"), - ("FUNCTION_TIMEOUT_SEC", "60"), - ("FUNCTION_REGION", "us-central1"), - ]) + check_faas_handshake( + &[ + ("K_SERVICE", "servicename"), + ("FUNCTION_MEMORY_MB", "1024"), + ("FUNCTION_TIMEOUT_SEC", "60"), + ("FUNCTION_REGION", "us-central1"), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::GcpFunc), + memory_mb: Some(1024), + timeout_sec: Some(60), + region: Some("us-central1".to_string()), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn valid_vercel() -> Result<()> { - check_faas_handshake(&[ - ("VERCEL", "1"), - ("VERCEL_URL", "*.vercel.app"), - ("VERCEL_REGION", "cdg1"), - ]) + check_faas_handshake( + &[ + ("VERCEL", "1"), + ("VERCEL_URL", "*.vercel.app"), + ("VERCEL_REGION", "cdg1"), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::Vercel), + region: Some("cdg1".to_string()), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn invalid_multiple_providers() -> Result<()> { - check_faas_handshake(&[ - ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), - ("FUNCTIONS_WORKER_RUNTIME", "node"), - ]) + check_faas_handshake( + &[ + ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), + ("FUNCTIONS_WORKER_RUNTIME", "node"), + ], + &BASE_CLIENT_METADATA, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn invalid_long_string() -> Result<()> { - check_faas_handshake(&[ - ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), - ("AWS_REGION", &"a".repeat(512)), - ]) + check_faas_handshake( + &[ + ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), + ("AWS_REGION", &"a".repeat(512)), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::AwsLambda), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn invalid_wrong_type() -> Result<()> { - check_faas_handshake(&[ - ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), - ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "big"), - ]) + check_faas_handshake( + &[ + ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), + ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "big"), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::AwsLambda), + runtime: Some("AWS_Lambda_java8".to_string()), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) .await } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn invalid_aws_not_lambda() -> Result<()> { - check_faas_handshake(&[("AWS_EXECUTION_ENV", "EC2")]).await + check_faas_handshake(&[("AWS_EXECUTION_ENV", "EC2")], &BASE_CLIENT_METADATA).await +} + +#[tokio::test] +async fn valid_container_and_faas() -> Result<()> { + check_faas_handshake( + &[ + ("AWS_EXECUTION_ENV", "AWS_Lambda_java8"), + ("AWS_REGION", "us-east-2"), + ("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "1024"), + ("KUBERNETES_SERVICE_HOST", "1"), + ], + &ClientMetadata { + env: Some(RuntimeEnvironment { + name: Some(FaasEnvironmentName::AwsLambda), + runtime: Some("AWS_Lambda_java8".to_string()), + region: Some("us-east-2".to_string()), + memory_mb: Some(1024), + container: Some(rawdoc! { "orchestrator": "kubernetes"}), + ..RuntimeEnvironment::UNSET + }), + ..BASE_CLIENT_METADATA.clone() + }, + ) + .await } diff --git a/src/test/spec/gridfs.rs b/src/test/spec/gridfs.rs index 10bfe7311..64ed53990 100644 --- a/src/test/spec/gridfs.rs +++ b/src/test/spec/gridfs.rs @@ -5,38 +5,30 @@ use futures_util::io::{AsyncReadExt, AsyncWriteExt}; use crate::{ bson::{doc, Bson, Document}, error::{Error, ErrorKind, GridFsErrorKind}, - gridfs::{GridFsBucket, GridFsUploadStream}, - options::{GridFsBucketOptions, GridFsUploadOptions}, + gridfs::{GridFsBucket, GridFsFindOneOptions, GridFsUploadStream}, + options::{FindOneOptions, GridFsBucketOptions, GridFsUploadOptions}, runtime, test::{ + fail_command_supported, + get_client_options, spec::unified_runner::run_unified_tests, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, + topology_is_sharded, + util::fail_point::{FailPoint, FailPointMode}, }, + Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run_unified() { - let _guard = LOCK.run_concurrently().await; run_unified_tests(&["gridfs"]) // The Rust driver doesn't support the disableMD5 option. .skip_files(&["upload-disableMD5.json"]) - // The Rust driver doesn't support the contentType option. - .skip_tests(&["upload when contentType is provided"]) .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn download_stream_across_buffers() { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let options = GridFsBucketOptions::builder().chunk_size_bytes(3).build(); let bucket = client @@ -45,12 +37,14 @@ async fn download_stream_across_buffers() { bucket.drop().await.unwrap(); let data: Vec = (0..20).collect(); - let id = bucket - .upload_from_futures_0_3_reader("test", &data[..], None) - .await - .unwrap(); + let id = { + let mut stream = bucket.open_upload_stream("test").await.unwrap(); + stream.write_all(&data[..]).await.unwrap(); + stream.close().await.unwrap(); + stream.id().clone() + }; - let mut download_stream = bucket.open_download_stream(id.into()).await.unwrap(); + let mut download_stream = bucket.open_download_stream(id).await.unwrap(); let mut buf = vec![0u8; 12]; // read in a partial chunk @@ -78,12 +72,9 @@ async fn download_stream_across_buffers() { assert_eq!(buf, data); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn upload_stream() { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let bucket_options = GridFsBucketOptions::builder().chunk_size_bytes(4).build(); let bucket = client .database("upload_stream") @@ -114,20 +105,26 @@ async fn upload_test(bucket: &GridFsBucket, data: &[u8], options: Option = (0..20).collect(); @@ -183,57 +180,55 @@ async fn upload_stream_multiple_buffers() { let mut uploaded = Vec::new(); bucket - .download_to_futures_0_3_writer(upload_stream.id().clone(), &mut uploaded) + .open_download_stream(upload_stream.id().clone()) + .await + .unwrap() + .read_to_end(&mut uploaded) .await .unwrap(); assert_eq!(uploaded, data); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn upload_stream_errors() { - let _guard = LOCK.run_exclusively().await; - - let client = TestClient::new().await; - let client = if client.is_sharded() { - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); + if topology_is_sharded().await { options.hosts.drain(1..); - TestClient::with_options(options).await - } else { - client - }; + } + let client = Client::for_test().options(options).await; let bucket = client.database("upload_stream_errors").gridfs_bucket(None); bucket.drop().await.unwrap(); // Error attempting to write to stream after closing. - let mut upload_stream = bucket.open_upload_stream("upload_stream_errors", None); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_errors") + .await + .unwrap(); upload_stream.close().await.unwrap(); assert_closed(&bucket, upload_stream).await; // Error attempting to write to stream after abort. - let mut upload_stream = bucket.open_upload_stream("upload_stream_errors", None); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_errors") + .await + .unwrap(); upload_stream.abort().await.unwrap(); assert_closed(&bucket, upload_stream).await; - if !client.supports_fail_command() { + if !fail_command_supported().await { return; } // Error attempting to write to stream after write failure. - let mut upload_stream = bucket.open_upload_stream( - "upload_stream_errors", - GridFsUploadOptions::builder().chunk_size_bytes(1).build(), - ); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_errors") + .chunk_size_bytes(1) + .await + .unwrap(); - let _fp_guard = FailPoint::fail_command( - &["insert"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(1234).build(), - ) - .enable(&client, None) - .await - .unwrap(); + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)).error_code(1234); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); let error = get_mongo_error(upload_stream.write_all(&[11]).await); assert_eq!(error.sdam_code(), Some(1234)); @@ -241,21 +236,16 @@ async fn upload_stream_errors() { assert_closed(&bucket, upload_stream).await; // Error attempting to write to stream after close failure. - let mut upload_stream = bucket.open_upload_stream( - "upload_stream_errors", - GridFsUploadOptions::builder().chunk_size_bytes(1).build(), - ); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_errors") + .chunk_size_bytes(1) + .await + .unwrap(); upload_stream.write_all(&[11]).await.unwrap(); - let _fp_guard = FailPoint::fail_command( - &["insert"], - FailPointMode::Times(1), - FailCommandOptions::builder().error_code(1234).build(), - ) - .enable(&client, None) - .await - .unwrap(); + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)).error_code(1234); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); let error = get_mongo_error(upload_stream.close().await); assert_eq!(error.sdam_code(), Some(1234)); @@ -263,16 +253,16 @@ async fn upload_stream_errors() { assert_closed(&bucket, upload_stream).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn drop_aborts() { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let bucket = client.database("upload_stream_abort").gridfs_bucket(None); bucket.drop().await.unwrap(); - let mut upload_stream = bucket.open_upload_stream("upload_stream_abort", None); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_abort") + .await + .unwrap(); let id = upload_stream.id().clone(); upload_stream.write_all(&[11]).await.unwrap(); drop(upload_stream); @@ -280,18 +270,18 @@ async fn drop_aborts() { assert_no_chunks_written(&bucket, &id).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn write_future_dropped() { - let _guard = LOCK.run_concurrently().await; - - let client = TestClient::new().await; + let client = Client::for_test().await; let bucket = client .database("upload_stream_abort") .gridfs_bucket(GridFsBucketOptions::builder().chunk_size_bytes(1).build()); bucket.drop().await.unwrap(); - let mut upload_stream = bucket.open_upload_stream("upload_stream_abort", None); + let mut upload_stream = bucket + .open_upload_stream("upload_stream_abort") + .await + .unwrap(); let chunks = vec![0u8; 100_000]; assert!( @@ -341,8 +331,52 @@ async fn assert_no_chunks_written(bucket: &GridFsBucket, id: &Bson) { assert!(bucket .chunks() .clone_with_type::() - .find_one(doc! { "files_id": id }, None) + .find_one(doc! { "files_id": id }) .await .unwrap() .is_none()); } + +#[tokio::test] +async fn test_gridfs_bucket_find_one() { + let data = &[1, 2, 3, 4]; + let client = Client::for_test().await; + + let options = GridFsBucketOptions::default(); + let bucket = client.database("gridfs_find_one").gridfs_bucket(options); + + let filename = String::from("somefile"); + let mut upload_stream = bucket.open_upload_stream(&filename).await.unwrap(); + upload_stream.write_all(data).await.unwrap(); + upload_stream.close().await.unwrap(); + + let found = bucket + .find_one(doc! { "_id": upload_stream.id() }) + .await + .unwrap() + .unwrap(); + + assert_eq!(&found.id, upload_stream.id()); + assert_eq!(found.length, 4); + assert_eq!(found.filename, Some(filename)); +} + +#[test] +fn test_gridfs_find_one_options_from() { + let default_options = GridFsFindOneOptions::default(); + let find_one_options = FindOneOptions::from(default_options); + assert_eq!(find_one_options.max_time, None); + assert_eq!(find_one_options.skip, None); + assert_eq!(find_one_options.sort, None); + + let options = GridFsFindOneOptions::builder() + .sort(doc! { "foo": -1 }) + .skip(1) + .max_time(Duration::from_millis(42)) + .build(); + + let find_one_options = FindOneOptions::from(options); + assert_eq!(find_one_options.max_time, Some(Duration::from_millis(42))); + assert_eq!(find_one_options.skip, Some(1)); + assert_eq!(find_one_options.sort, Some(doc! {"foo": -1})); +} diff --git a/src/test/spec/handshake.rs b/src/test/spec/handshake.rs new file mode 100644 index 000000000..89d563f0b --- /dev/null +++ b/src/test/spec/handshake.rs @@ -0,0 +1,41 @@ +use std::time::Instant; + +use crate::bson::oid::ObjectId; + +use crate::{ + cmap::{ + conn::PendingConnection, + establish::{ConnectionEstablisher, EstablisherOptions}, + }, + event::cmap::CmapEventEmitter, + test::get_client_options, +}; + +// Prose test 1: Test that the driver accepts an arbitrary auth mechanism +#[tokio::test] +async fn arbitrary_auth_mechanism() { + let client_options = get_client_options().await; + let mut options = EstablisherOptions::from(client_options); + options.test_patch_reply = Some(|reply| { + reply + .as_mut() + .unwrap() + .command_response + .sasl_supported_mechs + .get_or_insert_with(Vec::new) + .push("ArBiTrArY!".to_string()); + }); + let establisher = ConnectionEstablisher::new(options).unwrap(); + let pending = PendingConnection { + id: 0, + address: client_options.hosts[0].clone(), + generation: crate::cmap::PoolGeneration::normal(), + event_emitter: CmapEventEmitter::new(None, ObjectId::new()), + time_created: Instant::now(), + cancellation_receiver: None, + }; + establisher + .establish_connection(pending, None) + .await + .unwrap(); +} diff --git a/src/test/spec/initial_dns_seedlist_discovery.rs b/src/test/spec/initial_dns_seedlist_discovery.rs index 580bb4463..4bb2ac2e2 100644 --- a/src/test/spec/initial_dns_seedlist_discovery.rs +++ b/src/test/spec/initial_dns_seedlist_discovery.rs @@ -1,14 +1,20 @@ use std::time::{Duration, Instant}; use serde::Deserialize; -use tokio::sync::RwLockReadGuard; use crate::{ bson::doc, client::Client, - options::{ClientOptions, ResolverConfig}, - runtime, - test::{log_uncaptured, run_spec_test, TestClient, CLIENT_OPTIONS, LOCK}, + options::{ClientOptions, ResolverConfig, ServerAddress}, + srv::{DomainMismatch, LookupHosts}, + test::{ + get_client_options, + log_uncaptured, + run_spec_test, + topology_is_load_balanced, + topology_is_replica_set, + topology_is_sharded, + }, }; #[derive(Debug, Deserialize)] @@ -63,27 +69,6 @@ struct ParsedOptions { } async fn run_test(mut test_file: TestFile) { - if let Some(ref options) = test_file.options { - // TODO RUST-933: Remove this skip. - let skip = if options.srv_max_hosts.is_some() { - Some("srvMaxHosts") - // TODO RUST-911: Remove this skip. - } else if options.srv_service_name.is_some() { - Some("srvServiceName") - } else { - None - }; - - if let Some(skip) = skip { - log_uncaptured(format!( - "skipping initial_dns_seedlist_discovery test case due to unsupported connection \ - string option: {}", - skip, - )); - return; - } - } - // "encoded-userinfo-and-db.json" specifies a database name with a question mark which is // disallowed on Windows. See // @@ -95,18 +80,19 @@ async fn run_test(mut test_file: TestFile) { } let result = if cfg!(target_os = "windows") { - ClientOptions::parse_with_resolver_config(&test_file.uri, ResolverConfig::cloudflare()) + ClientOptions::parse(&test_file.uri) + .resolver_config(ResolverConfig::cloudflare()) .await } else { ClientOptions::parse(&test_file.uri).await }; if let Some(true) = test_file.error { - assert!(matches!(result, Err(_)), "{}", test_file.comment.unwrap()); + assert!(result.is_err(), "{}", test_file.comment.unwrap()); return; } - assert!(matches!(result, Ok(_)), "non-Ok result: {:?}", result); + assert!(result.is_ok(), "non-Ok result: {:?}", result); let options = result.unwrap(); @@ -131,7 +117,7 @@ async fn run_test(mut test_file: TestFile) { Some(ref options) => options.ssl, None => true, }; - let client = TestClient::new().await; + let client = Client::for_test().await; if requires_tls != client.options().tls_options().is_some() { log_uncaptured( "skipping initial_dns_seedlist_discovery test case due to TLS requirement mismatch", @@ -139,14 +125,16 @@ async fn run_test(mut test_file: TestFile) { } else { let mut options_with_tls = options.clone(); if requires_tls { - options_with_tls.tls = CLIENT_OPTIONS.get().await.tls.clone(); + options_with_tls + .tls + .clone_from(&get_client_options().await.tls); } let client = Client::with_options(options_with_tls).unwrap(); if test_file.ping == Some(true) { client .database("db") - .run_command(doc! { "ping" : 1 }, None) + .run_command(doc! { "ping" : 1 }) .await .unwrap(); } @@ -178,7 +166,7 @@ async fn run_test(mut test_file: TestFile) { ) } - runtime::delay_for(Duration::from_millis(500)).await; + tokio::time::sleep(Duration::from_millis(500)).await; } } @@ -205,19 +193,17 @@ async fn run_test(mut test_file: TestFile) { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn replica_set() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; - let skip = - if client.is_replica_set() && client.options().repl_set_name.as_deref() != Some("repl0") { - Some("repl_set_name != repl0") - } else if !client.is_replica_set() { - Some("not a replica set") - } else { - None - }; + let skip = if topology_is_replica_set().await + && get_client_options().await.repl_set_name.as_deref() != Some("repl0") + { + Some("repl_set_name != repl0") + } else if !topology_is_replica_set().await { + Some("not a replica set") + } else { + None + }; if let Some(skip) = skip { log_uncaptured(format!( "skipping initial_dns_seedlist_discovery::replica_set due to unmet topology \ @@ -230,12 +216,9 @@ async fn replica_set() { run_spec_test(&["initial-dns-seedlist-discovery", "replica-set"], run_test).await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn load_balanced() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; - if !client.is_load_balanced() { + if !topology_is_load_balanced().await { log_uncaptured( "skipping initial_dns_seedlist_discovery::load_balanced due to unmet topology \ requirement (not a load balanced cluster)", @@ -249,12 +232,9 @@ async fn load_balanced() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn sharded() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - let client = TestClient::new().await; - if !client.is_sharded() { + if !topology_is_sharded().await { log_uncaptured( "skipping initial_dns_seedlist_discovery::sharded due to unmet topology requirement \ (not a sharded cluster)", @@ -263,3 +243,45 @@ async fn sharded() { } run_spec_test(&["initial-dns-seedlist-discovery", "sharded"], run_test).await; } + +fn validate_srv(original: &str, resolved: &str) -> crate::error::Result<()> { + LookupHosts { + hosts: vec![ServerAddress::Tcp { + host: resolved.to_string(), + port: Some(42), + }], + min_ttl: Duration::from_secs(1), + } + .validate(original, DomainMismatch::Error) + .map(|_| ()) +} + +// Prose test 1. Allow SRVs with fewer than 3 `.` separated parts +#[test] +fn short_srv_domains_valid() { + validate_srv("localhost", "test.localhost").unwrap(); + validate_srv("mongo.local", "test.mongo.local").unwrap(); +} + +// Prose test 2. Throw when return address does not end with SRV domain +#[test] +fn short_srv_domains_invalid_end() { + assert!(validate_srv("localhost", "localhost.mongodb").is_err()); + assert!(validate_srv("mongo.local", "test_1.evil.local").is_err()); + assert!(validate_srv("blogs.mongodb.com", "blogs.evil.com").is_err()); +} + +// Prose test 3. Throw when return address is identical to SRV hostname +#[test] +fn short_srv_domains_invalid_identical() { + assert!(validate_srv("localhost", "localhost").is_err()); + assert!(validate_srv("mongo.local", "mongo.local").is_err()); +} + +// Prose test 4. Throw when return address does not contain `.` separating shared part of domain +#[test] +fn short_srv_domains_invalid_no_dot() { + assert!(validate_srv("localhost", "test_1.cluster_1localhost").is_err()); + assert!(validate_srv("mongo.local", "test_1.my_hostmongo.local").is_err()); + assert!(validate_srv("blogs.mongodb.com", "cluster.testmongodb.com").is_err()); +} diff --git a/src/test/spec/json/auth/README.md b/src/test/spec/json/auth/README.md new file mode 100644 index 000000000..c4b3eec74 --- /dev/null +++ b/src/test/spec/json/auth/README.md @@ -0,0 +1,47 @@ +# Auth Tests + +## Introduction + +This document describes the format of the driver spec tests included in the JSON and YAML files included in the `legacy` +sub-directory. Tests in the `unified` directory are written using the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +The YAML and JSON files in the `legacy` directory tree are platform-independent tests that drivers can use to prove +their conformance to the Auth Spec at least with respect to connection string URI input. + +Drivers should do additional unit testing if there are alternate ways of configuring credentials on a client. + +Driver must also conduct the prose tests in the Auth Spec test plan section. + +## Format + +Each YAML file contains an object with a single `tests` key. This key is an array of test case objects, each of which +have the following keys: + +- `description`: A string describing the test. +- `uri`: A string containing the URI to be parsed. +- `valid:` A boolean indicating if the URI should be considered valid. +- `credential`: If null, the credential must not be considered configured for the the purpose of deciding if the driver + should authenticate to the topology. If non-null, it is an object containing one or more of the following properties + of a credential: + - `username`: A string containing the username. For auth mechanisms that do not utilize a password, this may be the + entire `userinfo` token from the connection string. + - `password`: A string containing the password. + - `source`: A string containing the authentication database. + - `mechanism`: A string containing the authentication mechanism. A null value for this key is used to indicate that a + mechanism wasn't specified and that mechanism negotiation is required. Test harnesses should modify the mechanism + test as needed to assert this condition. + - `mechanism_properties`: A document containing mechanism-specific properties. It specifies a subset of properties + that must match. If a key exists in the test data, it must exist with the corresponding value in the credential. + Other values may exist in the credential without failing the test. + +If any key is missing, no assertion about that key is necessary. Except as specified explicitly above, if a key is +present, but the test value is null, the observed value for that key must be uninitialized (whatever that means for a +given driver and data type). + +## Implementation notes + +Testing whether a URI is valid or not should simply be a matter of checking whether URI parsing (or MongoClient +construction) raises an error or exception. + +If a credential is configured, its properties must be compared to the `credential` field. diff --git a/src/test/spec/json/auth/README.rst b/src/test/spec/json/auth/README.rst deleted file mode 100644 index 3bf86f4fb..000000000 --- a/src/test/spec/json/auth/README.rst +++ /dev/null @@ -1,53 +0,0 @@ -========== -Auth Tests -========== - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the Auth Spec at least with -respect to connection string URI input. - -Drivers should do additional unit testing if there are alternate ways of -configuring credentials on a client. - -Driver must also conduct the prose tests in the Auth Spec test plan section. - -Format ------- - -Each YAML file contains an object with a single ``tests`` key. This key is an -array of test case objects, each of which have the following keys: - -- ``description``: A string describing the test. -- ``uri``: A string containing the URI to be parsed. -- ``valid:`` A boolean indicating if the URI should be considered valid. -- ``credential``: If null, the credential must not be considered configured for the - the purpose of deciding if the driver should authenticate to the topology. If non-null, - it is an object containing one or more of the following properties of a credential: - - - ``username``: A string containing the username. For auth mechanisms - that do not utilize a password, this may be the entire ``userinfo`` token - from the connection string. - - ``password``: A string containing the password. - - ``source``: A string containing the authentication database. - - ``mechanism``: A string containing the authentication mechanism. A null value for - this key is used to indicate that a mechanism wasn't specified and that mechanism - negotiation is required. Test harnesses should modify the mechanism test as needed - to assert this condition. - - ``mechanism_properties``: A document containing mechanism-specific properties. It - specifies a subset of properties that must match. If a key exists in the test data, - it must exist with the corresponding value in the credential. Other values may - exist in the credential without failing the test. - -If any key is missing, no assertion about that key is necessary. Except as -specified explicitly above, if a key is present, but the test value is null, -the observed value for that key must be uninitialized (whatever that means for -a given driver and data type). - -Implementation notes -==================== - -Testing whether a URI is valid or not should simply be a matter of checking -whether URI parsing (or MongoClient construction) raises an error or exception. - -If a credential is configured, its properties must be compared to the -``credential`` field. diff --git a/src/test/spec/json/auth/connection-string.json b/src/test/spec/json/auth/connection-string.json deleted file mode 100644 index 2a37ae8df..000000000 --- a/src/test/spec/json/auth/connection-string.json +++ /dev/null @@ -1,449 +0,0 @@ -{ - "tests": [ - { - "description": "should use the default source and mechanism", - "uri": "mongodb://user:password@localhost", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "admin", - "mechanism": null, - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified", - "uri": "mongodb://user:password@localhost/foo", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": null, - "mechanism_properties": null - } - }, - { - "description": "should use the authSource when specified", - "uri": "mongodb://user:password@localhost/foo?authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": null, - "mechanism_properties": null - } - }, - { - "description": "should recognise the mechanism (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI", - "valid": true, - "credential": { - "username": "user@DOMAIN.COM", - "password": null, - "source": "$external", - "mechanism": "GSSAPI", - "mechanism_properties": { - "SERVICE_NAME": "mongodb" - } - } - }, - { - "description": "should ignore the database (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI", - "valid": true, - "credential": { - "username": "user@DOMAIN.COM", - "password": null, - "source": "$external", - "mechanism": "GSSAPI", - "mechanism_properties": { - "SERVICE_NAME": "mongodb" - } - } - }, - { - "description": "should accept valid authSource (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external", - "valid": true, - "credential": { - "username": "user@DOMAIN.COM", - "password": null, - "source": "$external", - "mechanism": "GSSAPI", - "mechanism_properties": { - "SERVICE_NAME": "mongodb" - } - } - }, - { - "description": "should accept generic mechanism property (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true", - "valid": true, - "credential": { - "username": "user@DOMAIN.COM", - "password": null, - "source": "$external", - "mechanism": "GSSAPI", - "mechanism_properties": { - "SERVICE_NAME": "other", - "CANONICALIZE_HOST_NAME": true - } - } - }, - { - "description": "should accept the password (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external", - "valid": true, - "credential": { - "username": "user@DOMAIN.COM", - "password": "password", - "source": "$external", - "mechanism": "GSSAPI", - "mechanism_properties": { - "SERVICE_NAME": "mongodb" - } - } - }, - { - "description": "must raise an error when the authSource is empty", - "uri": "mongodb://user:password@localhost/foo?authSource=", - "valid": false - }, - { - "description": "must raise an error when the authSource is empty without credentials", - "uri": "mongodb://localhost/admin?authSource=", - "valid": false - }, - { - "description": "should throw an exception if authSource is invalid (GSSAPI)", - "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo", - "valid": false - }, - { - "description": "should throw an exception if no username (GSSAPI)", - "uri": "mongodb://localhost/?authMechanism=GSSAPI", - "valid": false - }, - { - "description": "should recognize the mechanism (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-CR", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "admin", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should use the authSource when specified (MONGODB-CR)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR&authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": "MONGODB-CR", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if no username is supplied (MONGODB-CR)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-CR", - "valid": false - }, - { - "description": "should recognize the mechanism (MONGODB-X509)", - "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509", - "valid": true, - "credential": { - "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", - "password": null, - "source": "$external", - "mechanism": "MONGODB-X509", - "mechanism_properties": null - } - }, - { - "description": "should ignore the database (MONGODB-X509)", - "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509", - "valid": true, - "credential": { - "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", - "password": null, - "source": "$external", - "mechanism": "MONGODB-X509", - "mechanism_properties": null - } - }, - { - "description": "should accept valid authSource (MONGODB-X509)", - "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external", - "valid": true, - "credential": { - "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", - "password": null, - "source": "$external", - "mechanism": "MONGODB-X509", - "mechanism_properties": null - } - }, - { - "description": "should recognize the mechanism with no username (MONGODB-X509)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-X509", - "valid": true, - "credential": { - "username": null, - "password": null, - "source": "$external", - "mechanism": "MONGODB-X509", - "mechanism_properties": null - } - }, - { - "description": "should recognize the mechanism with no username when auth source is explicitly specified (MONGODB-X509)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external", - "valid": true, - "credential": { - "username": null, - "password": null, - "source": "$external", - "mechanism": "MONGODB-X509", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if supplied a password (MONGODB-X509)", - "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-X509", - "valid": false - }, - { - "description": "should throw an exception if authSource is invalid (MONGODB-X509)", - "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar", - "valid": false - }, - { - "description": "should recognize the mechanism (PLAIN)", - "uri": "mongodb://user:password@localhost/?authMechanism=PLAIN", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "$external", - "mechanism": "PLAIN", - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified (PLAIN)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=PLAIN", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": "PLAIN", - "mechanism_properties": null - } - }, - { - "description": "should use the authSource when specified (PLAIN)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": "PLAIN", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if no username (PLAIN)", - "uri": "mongodb://localhost/?authMechanism=PLAIN", - "valid": false - }, - { - "description": "should recognize the mechanism (SCRAM-SHA-1)", - "uri": "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "admin", - "mechanism": "SCRAM-SHA-1", - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified (SCRAM-SHA-1)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": "SCRAM-SHA-1", - "mechanism_properties": null - } - }, - { - "description": "should accept valid authSource (SCRAM-SHA-1)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": "SCRAM-SHA-1", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if no username (SCRAM-SHA-1)", - "uri": "mongodb://localhost/?authMechanism=SCRAM-SHA-1", - "valid": false - }, - { - "description": "should recognize the mechanism (SCRAM-SHA-256)", - "uri": "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "admin", - "mechanism": "SCRAM-SHA-256", - "mechanism_properties": null - } - }, - { - "description": "should use the database when no authSource is specified (SCRAM-SHA-256)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "foo", - "mechanism": "SCRAM-SHA-256", - "mechanism_properties": null - } - }, - { - "description": "should accept valid authSource (SCRAM-SHA-256)", - "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "bar", - "mechanism": "SCRAM-SHA-256", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if no username (SCRAM-SHA-256)", - "uri": "mongodb://localhost/?authMechanism=SCRAM-SHA-256", - "valid": false - }, - { - "description": "URI with no auth-related info doesn't create credential", - "uri": "mongodb://localhost/", - "valid": true, - "credential": null - }, - { - "description": "database in URI path doesn't create credentials", - "uri": "mongodb://localhost/foo", - "valid": true, - "credential": null - }, - { - "description": "authSource without username doesn't create credential (default mechanism)", - "uri": "mongodb://localhost/?authSource=foo", - "valid": true, - "credential": null - }, - { - "description": "should throw an exception if no username provided (userinfo implies default mechanism)", - "uri": "mongodb://@localhost.com/", - "valid": false - }, - { - "description": "should throw an exception if no username/password provided (userinfo implies default mechanism)", - "uri": "mongodb://:@localhost.com/", - "valid": false - }, - { - "description": "should recognise the mechanism (MONGODB-AWS)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS", - "valid": true, - "credential": { - "username": null, - "password": null, - "source": "$external", - "mechanism": "MONGODB-AWS", - "mechanism_properties": null - } - }, - { - "description": "should recognise the mechanism when auth source is explicitly specified (MONGODB-AWS)", - "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external", - "valid": true, - "credential": { - "username": null, - "password": null, - "source": "$external", - "mechanism": "MONGODB-AWS", - "mechanism_properties": null - } - }, - { - "description": "should throw an exception if username and no password (MONGODB-AWS)", - "uri": "mongodb://user@localhost/?authMechanism=MONGODB-AWS", - "valid": false, - "credential": null - }, - { - "description": "should use username and password if specified (MONGODB-AWS)", - "uri": "mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS", - "valid": true, - "credential": { - "username": "user!@#$%^&*()_+", - "password": "pass!@#$%^&*()_+", - "source": "$external", - "mechanism": "MONGODB-AWS", - "mechanism_properties": null - } - }, - { - "description": "should use username, password and session token if specified (MONGODB-AWS)", - "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B", - "valid": true, - "credential": { - "username": "user", - "password": "password", - "source": "$external", - "mechanism": "MONGODB-AWS", - "mechanism_properties": { - "AWS_SESSION_TOKEN": "token!@#$%^&*()_+" - } - } - } - ] -} diff --git a/src/test/spec/json/auth/connection-string.yml b/src/test/spec/json/auth/connection-string.yml deleted file mode 100644 index 41dca8fab..000000000 --- a/src/test/spec/json/auth/connection-string.yml +++ /dev/null @@ -1,366 +0,0 @@ -tests: - - - description: "should use the default source and mechanism" - uri: "mongodb://user:password@localhost" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: ~ - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified" - uri: "mongodb://user:password@localhost/foo" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: ~ - mechanism_properties: ~ - - - description: "should use the authSource when specified" - uri: "mongodb://user:password@localhost/foo?authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: ~ - mechanism_properties: ~ - - - description: "should recognise the mechanism (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should ignore the database (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should accept valid authSource (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "should accept generic mechanism property (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true" - valid: true - credential: - username: "user@DOMAIN.COM" - password: ~ - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "other" - CANONICALIZE_HOST_NAME: true - - - description: "should accept the password (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external" - valid: true - credential: - username: "user@DOMAIN.COM" - password: "password" - source: "$external" - mechanism: "GSSAPI" - mechanism_properties: - SERVICE_NAME: "mongodb" - - - description: "must raise an error when the authSource is empty" - uri: "mongodb://user:password@localhost/foo?authSource=" - valid: false - - - description: "must raise an error when the authSource is empty without credentials" - uri: "mongodb://localhost/admin?authSource=" - valid: false - - - description: "should throw an exception if authSource is invalid (GSSAPI)" - uri: "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo" - valid: false - - - description: "should throw an exception if no username (GSSAPI)" - uri: "mongodb://localhost/?authMechanism=GSSAPI" - valid: false - - - description: "should recognize the mechanism (MONGODB-CR)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-CR" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (MONGODB-CR)" - uri: "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should use the authSource when specified (MONGODB-CR)" - uri: "mongodb://user:password@localhost/foo?authMechanism=MONGODB-CR&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "MONGODB-CR" - mechanism_properties: ~ - - - description: "should throw an exception if no username is supplied (MONGODB-CR)" - uri: "mongodb://localhost/?authMechanism=MONGODB-CR" - valid: false - - - description: "should recognize the mechanism (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should ignore the database (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should accept valid authSource (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external" - valid: true - credential: - username: "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry" - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should recognize the mechanism with no username (MONGODB-X509)" - uri: "mongodb://localhost/?authMechanism=MONGODB-X509" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should recognize the mechanism with no username when auth source is explicitly specified (MONGODB-X509)" - uri: "mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-X509" - mechanism_properties: ~ - - - description: "should throw an exception if supplied a password (MONGODB-X509)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-X509" - valid: false - - - description: "should throw an exception if authSource is invalid (MONGODB-X509)" - uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar" - valid: false - - - description: "should recognize the mechanism (PLAIN)" - uri: "mongodb://user:password@localhost/?authMechanism=PLAIN" - valid: true - credential: - username: "user" - password: "password" - source: "$external" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (PLAIN)" - uri: "mongodb://user:password@localhost/foo?authMechanism=PLAIN" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should use the authSource when specified (PLAIN)" - uri: "mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "PLAIN" - mechanism_properties: ~ - - - description: "should throw an exception if no username (PLAIN)" - uri: "mongodb://localhost/?authMechanism=PLAIN" - valid: false - - - description: "should recognize the mechanism (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should accept valid authSource (SCRAM-SHA-1)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "SCRAM-SHA-1" - mechanism_properties: ~ - - - description: "should throw an exception if no username (SCRAM-SHA-1)" - uri: "mongodb://localhost/?authMechanism=SCRAM-SHA-1" - valid: false - - - description: "should recognize the mechanism (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256" - valid: true - credential: - username: "user" - password: "password" - source: "admin" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should use the database when no authSource is specified (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256" - valid: true - credential: - username: "user" - password: "password" - source: "foo" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should accept valid authSource (SCRAM-SHA-256)" - uri: "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar" - valid: true - credential: - username: "user" - password: "password" - source: "bar" - mechanism: "SCRAM-SHA-256" - mechanism_properties: ~ - - - description: "should throw an exception if no username (SCRAM-SHA-256)" - uri: "mongodb://localhost/?authMechanism=SCRAM-SHA-256" - valid: false - - - description: "URI with no auth-related info doesn't create credential" - uri: "mongodb://localhost/" - valid: true - credential: ~ - - - description: "database in URI path doesn't create credentials" - uri: "mongodb://localhost/foo" - valid: true - credential: ~ - - - description: "authSource without username doesn't create credential (default mechanism)" - uri: "mongodb://localhost/?authSource=foo" - valid: true - credential: ~ - - - description: "should throw an exception if no username provided (userinfo implies default mechanism)" - uri: "mongodb://@localhost.com/" - valid: false - - - description: "should throw an exception if no username/password provided (userinfo implies default mechanism)" - uri: "mongodb://:@localhost.com/" - valid: false - - - description: "should recognise the mechanism (MONGODB-AWS)" - uri: "mongodb://localhost/?authMechanism=MONGODB-AWS" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should recognise the mechanism when auth source is explicitly specified (MONGODB-AWS)" - uri: "mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external" - valid: true - credential: - username: ~ - password: ~ - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should throw an exception if username and no password (MONGODB-AWS)" - uri: "mongodb://user@localhost/?authMechanism=MONGODB-AWS" - valid: false - credential: ~ - - - description: "should use username and password if specified (MONGODB-AWS)" - uri: "mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS" - valid: true - credential: - username: "user!@#$%^&*()_+" - password: "pass!@#$%^&*()_+" - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: ~ - - - description: "should use username, password and session token if specified (MONGODB-AWS)" - uri: "mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B" - valid: true - credential: - username: "user" - password: "password" - source: "$external" - mechanism: "MONGODB-AWS" - mechanism_properties: - AWS_SESSION_TOKEN: "token!@#$%^&*()_+" diff --git a/src/test/spec/json/auth/legacy/connection-string.json b/src/test/spec/json/auth/legacy/connection-string.json new file mode 100644 index 000000000..3a099c813 --- /dev/null +++ b/src/test/spec/json/auth/legacy/connection-string.json @@ -0,0 +1,651 @@ +{ + "tests": [ + { + "description": "should use the default source and mechanism", + "uri": "mongodb://user:password@localhost", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "admin", + "mechanism": null, + "mechanism_properties": null + } + }, + { + "description": "should use the database when no authSource is specified", + "uri": "mongodb://user:password@localhost/foo", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "foo", + "mechanism": null, + "mechanism_properties": null + } + }, + { + "description": "should use the authSource when specified", + "uri": "mongodb://user:password@localhost/foo?authSource=bar", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "bar", + "mechanism": null, + "mechanism_properties": null + } + }, + { + "description": "should recognise the mechanism (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "mongodb" + } + } + }, + { + "description": "should ignore the database (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "mongodb" + } + } + }, + { + "description": "should accept valid authSource (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "mongodb" + } + } + }, + { + "description": "should accept generic mechanism property (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "other", + "SERVICE_HOST": "example.com", + "CANONICALIZE_HOST_NAME": "forward" + } + } + }, + { + "description": "should accept forwardAndReverse hostname canonicalization (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "other", + "CANONICALIZE_HOST_NAME": "forwardAndReverse" + } + } + }, + { + "description": "should accept no hostname canonicalization (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": null, + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "other", + "CANONICALIZE_HOST_NAME": "none" + } + } + }, + { + "description": "must raise an error when the hostname canonicalization is invalid", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid", + "valid": false + }, + { + "description": "should accept the password (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external", + "valid": true, + "credential": { + "username": "user@DOMAIN.COM", + "password": "password", + "source": "$external", + "mechanism": "GSSAPI", + "mechanism_properties": { + "SERVICE_NAME": "mongodb" + } + } + }, + { + "description": "must raise an error when the authSource is empty", + "uri": "mongodb://user:password@localhost/foo?authSource=", + "valid": false + }, + { + "description": "must raise an error when the authSource is empty without credentials", + "uri": "mongodb://localhost/admin?authSource=", + "valid": false + }, + { + "description": "should throw an exception if authSource is invalid (GSSAPI)", + "uri": "mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo", + "valid": false + }, + { + "description": "should throw an exception if no username (GSSAPI)", + "uri": "mongodb://localhost/?authMechanism=GSSAPI", + "valid": false + }, + { + "description": "should recognize the mechanism (MONGODB-X509)", + "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509", + "valid": true, + "credential": { + "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", + "password": null, + "source": "$external", + "mechanism": "MONGODB-X509", + "mechanism_properties": null + } + }, + { + "description": "should ignore the database (MONGODB-X509)", + "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509", + "valid": true, + "credential": { + "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", + "password": null, + "source": "$external", + "mechanism": "MONGODB-X509", + "mechanism_properties": null + } + }, + { + "description": "should accept valid authSource (MONGODB-X509)", + "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external", + "valid": true, + "credential": { + "username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry", + "password": null, + "source": "$external", + "mechanism": "MONGODB-X509", + "mechanism_properties": null + } + }, + { + "description": "should recognize the mechanism with no username (MONGODB-X509)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-X509", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-X509", + "mechanism_properties": null + } + }, + { + "description": "should recognize the mechanism with no username when auth source is explicitly specified (MONGODB-X509)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-X509", + "mechanism_properties": null + } + }, + { + "description": "should throw an exception if supplied a password (MONGODB-X509)", + "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-X509", + "valid": false + }, + { + "description": "should throw an exception if authSource is invalid (MONGODB-X509)", + "uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar", + "valid": false + }, + { + "description": "should recognize the mechanism (PLAIN)", + "uri": "mongodb://user:password@localhost/?authMechanism=PLAIN", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "$external", + "mechanism": "PLAIN", + "mechanism_properties": null + } + }, + { + "description": "should use the database when no authSource is specified (PLAIN)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=PLAIN", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "foo", + "mechanism": "PLAIN", + "mechanism_properties": null + } + }, + { + "description": "should use the authSource when specified (PLAIN)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "bar", + "mechanism": "PLAIN", + "mechanism_properties": null + } + }, + { + "description": "should throw an exception if no username (PLAIN)", + "uri": "mongodb://localhost/?authMechanism=PLAIN", + "valid": false + }, + { + "description": "should recognize the mechanism (SCRAM-SHA-1)", + "uri": "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "admin", + "mechanism": "SCRAM-SHA-1", + "mechanism_properties": null + } + }, + { + "description": "should use the database when no authSource is specified (SCRAM-SHA-1)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "foo", + "mechanism": "SCRAM-SHA-1", + "mechanism_properties": null + } + }, + { + "description": "should accept valid authSource (SCRAM-SHA-1)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "bar", + "mechanism": "SCRAM-SHA-1", + "mechanism_properties": null + } + }, + { + "description": "should throw an exception if no username (SCRAM-SHA-1)", + "uri": "mongodb://localhost/?authMechanism=SCRAM-SHA-1", + "valid": false + }, + { + "description": "should recognize the mechanism (SCRAM-SHA-256)", + "uri": "mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "admin", + "mechanism": "SCRAM-SHA-256", + "mechanism_properties": null + } + }, + { + "description": "should use the database when no authSource is specified (SCRAM-SHA-256)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "foo", + "mechanism": "SCRAM-SHA-256", + "mechanism_properties": null + } + }, + { + "description": "should accept valid authSource (SCRAM-SHA-256)", + "uri": "mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "bar", + "mechanism": "SCRAM-SHA-256", + "mechanism_properties": null + } + }, + { + "description": "should throw an exception if no username (SCRAM-SHA-256)", + "uri": "mongodb://localhost/?authMechanism=SCRAM-SHA-256", + "valid": false + }, + { + "description": "URI with no auth-related info doesn't create credential", + "uri": "mongodb://localhost/", + "valid": true, + "credential": null + }, + { + "description": "database in URI path doesn't create credentials", + "uri": "mongodb://localhost/foo", + "valid": true, + "credential": null + }, + { + "description": "authSource without username doesn't create credential (default mechanism)", + "uri": "mongodb://localhost/?authSource=foo", + "valid": true, + "credential": null + }, + { + "description": "should throw an exception if no username provided (userinfo implies default mechanism)", + "uri": "mongodb://@localhost.com/", + "valid": false + }, + { + "description": "should throw an exception if no username/password provided (userinfo implies default mechanism)", + "uri": "mongodb://:@localhost.com/", + "valid": false + }, + { + "description": "should recognise the mechanism (MONGODB-AWS)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-AWS", + "mechanism_properties": null + } + }, + { + "description": "should recognise the mechanism when auth source is explicitly specified (MONGODB-AWS)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-AWS", + "mechanism_properties": null + } + }, + { + "description": "should throw an exception if username and no password (MONGODB-AWS)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-AWS", + "valid": false, + "credential": null + }, + { + "description": "should use username and password if specified (MONGODB-AWS)", + "uri": "mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS", + "valid": true, + "credential": { + "username": "user!@#$%^&*()_+", + "password": "pass!@#$%^&*()_+", + "source": "$external", + "mechanism": "MONGODB-AWS", + "mechanism_properties": null + } + }, + { + "description": "should use username, password and session token if specified (MONGODB-AWS)", + "uri": "mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B", + "valid": true, + "credential": { + "username": "user", + "password": "password", + "source": "$external", + "mechanism": "MONGODB-AWS", + "mechanism_properties": { + "AWS_SESSION_TOKEN": "token!@#$%^&*()_+" + } + } + }, + { + "description": "should recognise the mechanism with test environment (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "test" + } + } + }, + { + "description": "should recognise the mechanism when auth source is explicitly specified and with environment (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authSource=$external&authMechanismProperties=ENVIRONMENT:test", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "test" + } + } + }, + { + "description": "should throw an exception if supplied a password (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if username is specified for test (MONGODB-OIDC)", + "uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if specified environment is not supported (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:invalid", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception when unsupported auth property is specified (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with azure provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should accept a username with azure provider (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "mongodb://test-cluster" + } + } + }, + { + "description": "should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "mongodb://test-cluster" + } + } + }, + { + "description": "should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abcd%25ef%3Ag%26hi", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "abcd%ef:g&hi" + } + } + }, + { + "description": "should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)", + "uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b", + "valid": true, + "credential": { + "username": "user", + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "azure", + "TOKEN_RESOURCE": "a$b" + } + } + }, + { + "description": "should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo", + "valid": false, + "credential": null + }, + { + "description": "should throw an exception if no token audience is given for azure provider (MONGODB-OIDC)", + "uri": "mongodb://username@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "gcp", + "TOKEN_RESOURCE": "foo" + } + } + }, + { + "description": "should throw an error for a username and password with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo", + "valid": false, + "credential": null + }, + { + "description": "should throw an error if not TOKEN_RESOURCE with gcp provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp", + "valid": false, + "credential": null + }, + { + "description": "should recognise the mechanism with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": true, + "credential": { + "username": null, + "password": null, + "source": "$external", + "mechanism": "MONGODB-OIDC", + "mechanism_properties": { + "ENVIRONMENT": "k8s" + } + } + }, + { + "description": "should throw an error for a username and password with k8s provider (MONGODB-OIDC)", + "uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s", + "valid": false, + "credential": null + } + ] +} diff --git a/src/test/spec/json/auth/legacy/connection-string.yml b/src/test/spec/json/auth/legacy/connection-string.yml new file mode 100644 index 000000000..1f5d47004 --- /dev/null +++ b/src/test/spec/json/auth/legacy/connection-string.yml @@ -0,0 +1,471 @@ +--- +tests: +- description: should use the default source and mechanism + uri: mongodb://user:password@localhost + valid: true + credential: + username: user + password: password + source: admin + mechanism: + mechanism_properties: +- description: should use the database when no authSource is specified + uri: mongodb://user:password@localhost/foo + valid: true + credential: + username: user + password: password + source: foo + mechanism: + mechanism_properties: +- description: should use the authSource when specified + uri: mongodb://user:password@localhost/foo?authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: + mechanism_properties: +- description: should recognise the mechanism (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should ignore the database (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/foo?authMechanism=GSSAPI + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should accept valid authSource (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=$external + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: should accept generic mechanism property (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + SERVICE_HOST: example.com + CANONICALIZE_HOST_NAME: forward +- description: should accept forwardAndReverse hostname canonicalization (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forwardAndReverse + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + CANONICALIZE_HOST_NAME: forwardAndReverse +- description: should accept no hostname canonicalization (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:none + valid: true + credential: + username: user@DOMAIN.COM + password: + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: other + CANONICALIZE_HOST_NAME: none +- description: must raise an error when the hostname canonicalization is invalid + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:invalid + valid: false +- description: should accept the password (GSSAPI) + uri: mongodb://user%40DOMAIN.COM:password@localhost/?authMechanism=GSSAPI&authSource=$external + valid: true + credential: + username: user@DOMAIN.COM + password: password + source: "$external" + mechanism: GSSAPI + mechanism_properties: + SERVICE_NAME: mongodb +- description: must raise an error when the authSource is empty + uri: mongodb://user:password@localhost/foo?authSource= + valid: false +- description: must raise an error when the authSource is empty without credentials + uri: mongodb://localhost/admin?authSource= + valid: false +- description: should throw an exception if authSource is invalid (GSSAPI) + uri: mongodb://user%40DOMAIN.COM@localhost/?authMechanism=GSSAPI&authSource=foo + valid: false +- description: should throw an exception if no username (GSSAPI) + uri: mongodb://localhost/?authMechanism=GSSAPI + valid: false +- description: should recognize the mechanism (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509 + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should ignore the database (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509 + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should accept valid authSource (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509&authSource=$external + valid: true + credential: + username: CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should recognize the mechanism with no username (MONGODB-X509) + uri: mongodb://localhost/?authMechanism=MONGODB-X509 + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should recognize the mechanism with no username when auth source is + explicitly specified (MONGODB-X509) + uri: mongodb://localhost/?authMechanism=MONGODB-X509&authSource=$external + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-X509 + mechanism_properties: +- description: should throw an exception if supplied a password (MONGODB-X509) + uri: mongodb://user:password@localhost/?authMechanism=MONGODB-X509 + valid: false +- description: should throw an exception if authSource is invalid (MONGODB-X509) + uri: mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/foo?authMechanism=MONGODB-X509&authSource=bar + valid: false +- description: should recognize the mechanism (PLAIN) + uri: mongodb://user:password@localhost/?authMechanism=PLAIN + valid: true + credential: + username: user + password: password + source: "$external" + mechanism: PLAIN + mechanism_properties: +- description: should use the database when no authSource is specified (PLAIN) + uri: mongodb://user:password@localhost/foo?authMechanism=PLAIN + valid: true + credential: + username: user + password: password + source: foo + mechanism: PLAIN + mechanism_properties: +- description: should use the authSource when specified (PLAIN) + uri: mongodb://user:password@localhost/foo?authMechanism=PLAIN&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: PLAIN + mechanism_properties: +- description: should throw an exception if no username (PLAIN) + uri: mongodb://localhost/?authMechanism=PLAIN + valid: false +- description: should recognize the mechanism (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-1 + valid: true + credential: + username: user + password: password + source: admin + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should use the database when no authSource is specified (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1 + valid: true + credential: + username: user + password: password + source: foo + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should accept valid authSource (SCRAM-SHA-1) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-1&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: SCRAM-SHA-1 + mechanism_properties: +- description: should throw an exception if no username (SCRAM-SHA-1) + uri: mongodb://localhost/?authMechanism=SCRAM-SHA-1 + valid: false +- description: should recognize the mechanism (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/?authMechanism=SCRAM-SHA-256 + valid: true + credential: + username: user + password: password + source: admin + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should use the database when no authSource is specified (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256 + valid: true + credential: + username: user + password: password + source: foo + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should accept valid authSource (SCRAM-SHA-256) + uri: mongodb://user:password@localhost/foo?authMechanism=SCRAM-SHA-256&authSource=bar + valid: true + credential: + username: user + password: password + source: bar + mechanism: SCRAM-SHA-256 + mechanism_properties: +- description: should throw an exception if no username (SCRAM-SHA-256) + uri: mongodb://localhost/?authMechanism=SCRAM-SHA-256 + valid: false +- description: URI with no auth-related info doesn't create credential + uri: mongodb://localhost/ + valid: true + credential: +- description: database in URI path doesn't create credentials + uri: mongodb://localhost/foo + valid: true + credential: +- description: authSource without username doesn't create credential (default mechanism) + uri: mongodb://localhost/?authSource=foo + valid: true + credential: +- description: should throw an exception if no username provided (userinfo implies + default mechanism) + uri: mongodb://@localhost.com/ + valid: false +- description: should throw an exception if no username/password provided (userinfo + implies default mechanism) + uri: mongodb://:@localhost.com/ + valid: false +- description: should recognise the mechanism (MONGODB-AWS) + uri: mongodb://localhost/?authMechanism=MONGODB-AWS + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should recognise the mechanism when auth source is explicitly specified + (MONGODB-AWS) + uri: mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=$external + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should throw an exception if username and no password (MONGODB-AWS) + uri: mongodb://user@localhost/?authMechanism=MONGODB-AWS + valid: false + credential: +- description: should use username and password if specified (MONGODB-AWS) + uri: mongodb://user%21%40%23%24%25%5E%26%2A%28%29_%2B:pass%21%40%23%24%25%5E%26%2A%28%29_%2B@localhost/?authMechanism=MONGODB-AWS + valid: true + credential: + username: user!@#$%^&*()_+ + password: pass!@#$%^&*()_+ + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: +- description: should use username, password and session token if specified (MONGODB-AWS) + uri: mongodb://user:password@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:token%21%40%23%24%25%5E%26%2A%28%29_%2B + valid: true + credential: + username: user + password: password + source: "$external" + mechanism: MONGODB-AWS + mechanism_properties: + AWS_SESSION_TOKEN: token!@#$%^&*()_+ +- description: should recognise the mechanism with test environment (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: test +- description: should recognise the mechanism when auth source is explicitly specified and with environment (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authSource=$external&authMechanismProperties=ENVIRONMENT:test + valid: true + credential: + username: + password: + source: "$external" + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: test +- description: should throw an exception if supplied a password (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: false + credential: +- description: should throw an exception if username is specified for test (MONGODB-OIDC) + uri: mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test + valid: false + credential: +- description: should throw an exception if specified environment is not supported (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:invalid + valid: false + credential: +- description: should throw an exception if neither environment nor callbacks specified (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC + valid: false + credential: +- description: should throw an exception when unsupported auth property is specified (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=UnsupportedProperty:unexisted + valid: false + credential: +- description: should recognise the mechanism with azure provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: foo +- description: should accept a username with azure provider (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: foo +- description: should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'mongodb://test-cluster' +- description: should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'mongodb://test-cluster' +- description: should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abcd%25ef%3Ag%26hi + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: 'abcd%ef:g&hi' +- description: should url-encode a TOKEN_RESOURCE (MONGODB-OIDC) + uri: mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b + valid: true + credential: + username: user + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: azure + TOKEN_RESOURCE: a$b +- description: should accept a username and throw an error for a password with azure provider (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo + valid: false + credential: null +- description: should throw an exception if no token audience is given for azure provider (MONGODB-OIDC) + uri: mongodb://username@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure + valid: false + credential: null +- description: should recognise the mechanism with gcp provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: gcp + TOKEN_RESOURCE: foo +- description: should throw an error for a username and password with gcp provider + (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp,TOKEN_RESOURCE:foo + valid: false + credential: null +- description: should throw an error if not TOKEN_RESOURCE with gcp provider (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:gcp + valid: false + credential: null +- description: should recognise the mechanism with k8s provider (MONGODB-OIDC) + uri: mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: true + credential: + username: null + password: null + source: $external + mechanism: MONGODB-OIDC + mechanism_properties: + ENVIRONMENT: k8s +- description: should throw an error for a username and password with k8s provider + (MONGODB-OIDC) + uri: mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:k8s + valid: false + credential: null diff --git a/src/test/spec/json/auth/mongodb-aws.md b/src/test/spec/json/auth/mongodb-aws.md new file mode 100644 index 000000000..6e166d285 --- /dev/null +++ b/src/test/spec/json/auth/mongodb-aws.md @@ -0,0 +1,169 @@ +# MongoDB AWS + +Drivers MUST test the following scenarios: + +1. `Regular Credentials`: Auth via an `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` pair +2. `EC2 Credentials`: Auth from an EC2 instance via temporary credentials assigned to the machine +3. `ECS Credentials`: Auth from an ECS instance via temporary credentials assigned to the task +4. `Assume Role`: Auth via temporary credentials obtained from an STS AssumeRole request +5. `Assume Role with Web Identity`: Auth via temporary credentials obtained from an STS AssumeRoleWithWebIdentity + request +6. `AWS Lambda`: Auth via environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`. +7. Caching of AWS credentials fetched by the driver. + +For brevity, this section gives the values ``, `` and `` in place of a valid access +key ID, secret access key and session token (also known as a security token). Note that if these values are passed into +the URI they MUST be URL encoded. Sample values are below. + +```text +AccessKeyId=AKIAI44QH8DHBEXAMPLE +SecretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +Token=AQoDYXdzEJr... +``` + +## Regular credentials + +Drivers MUST be able to authenticate by providing a valid access key id and secret access key pair as the username and +password, respectively, in the MongoDB URI. An example of a valid URI would be: + +```text +mongodb://:@localhost/?authMechanism=MONGODB-AWS +``` + +## EC2 Credentials + +Drivers MUST be able to authenticate from an EC2 instance via temporary credentials assigned to the machine. A sample +URI on an EC2 machine would be: + +```text +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +> [!NOTE] +> No username, password or session token is passed into the URI. Drivers MUST query the EC2 instance endpoint to obtain +> these credentials. + +## ECS instance + +Drivers MUST be able to authenticate from an ECS container via temporary credentials. A sample URI in an ECS container +would be: + +```text +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +> [!NOTE] +> No username, password or session token is passed into the URI. Drivers MUST query the ECS container endpoint to obtain +> these credentials. + +## AssumeRole + +Drivers MUST be able to authenticate using temporary credentials returned from an assume role request. These temporary +credentials consist of an access key ID, a secret access key, and a security token passed into the URI. A sample URI +would be: + +```text +mongodb://:@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN: +``` + +## Assume Role with Web Identity + +Drivers MUST be able to authentiate using a valid OIDC token and associated role ARN taken from environment variables, +respectively: + +```text +AWS_WEB_IDENTITY_TOKEN_FILE +AWS_ROLE_ARN +AWS_ROLE_SESSION_NAME (optional) +``` + +A sample URI in for a web identity test would be: + +```text +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +Drivers MUST test with and without AWS_ROLE_SESSION_NAME set. + +> [!NOTE] +> No username, password or session token is passed into the URI. + +Drivers MUST check the environment variables listed above and make an +[AssumeRoleWithWebIdentity request](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) +to obtain credentials. + +## AWS Lambda + +Drivers MUST be able to authenticate via an access key ID, secret access key and optional session token taken from the +environment variables, respectively: + +```text +AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY +AWS_SESSION_TOKEN +``` + +Sample URIs both with and without optional session tokens set are shown below. Drivers MUST test both cases. + +```bash +# without a session token +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" + +URI="mongodb://localhost/?authMechanism=MONGODB-AWS" +``` + +```bash +# with a session token +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" +export AWS_SESSION_TOKEN="" + +URI="mongodb://localhost/?authMechanism=MONGODB-AWS" +``` + +> [!NOTE] +> No username, password or session token is passed into the URI. Drivers MUST check the environment variables listed +> above for these values. If the session token is set Drivers MUST use it. + +## Cached Credentials + +Drivers MUST ensure that they are testing the ability to cache credentials. Drivers will need to be able to query and +override the cached credentials to verify usage. To determine whether to run the cache tests, the driver can check for +the absence of the AWS_ACCESS_KEY_ID environment variable and of credentials in the URI. + +1. Clear the cache. +2. Create a new client. +3. Ensure that a `find` operation adds credentials to the cache. +4. Override the cached credentials with an "Expiration" that is within one minute of the current UTC time. +5. Create a new client. +6. Ensure that a `find` operation updates the credentials in the cache. +7. Poison the cache with an invalid access key id. +8. Create a new client. +9. Ensure that a `find` operation results in an error. +10. Ensure that the cache has been cleared. +11. Ensure that a subsequent `find` operation succeeds. +12. Ensure that the cache has been set. + +If the drivers's language supports dynamically setting environment variables, add the following tests. Note that if +integration tests are run in parallel for the driver, then these tests must be run as unit tests interacting with the +auth provider directly instead of using a client. + +1. Clear the cache. +2. Create a new client. +3. Ensure that a `find` operation adds credentials to the cache. +4. Set the AWS environment variables based on the cached credentials. +5. Clear the cache. +6. Create a new client. +7. Ensure that a `find` operation succeeds and does not add credentials to the cache. +8. Set the AWS environment variables to invalid values. +9. Create a new client. +10. Ensure that a `find` operation results in an error. +11. Clear the AWS environment variables. +12. Clear the cache. +13. Create a new client. +14. Ensure that a `find` operation adds credentials to the cache. +15. Set the AWS environment variables to invalid values. +16. Create a new client. +17. Ensure that a `find` operation succeeds. +18. Clear the AWS environment variables. diff --git a/src/test/spec/json/auth/mongodb-aws.rst b/src/test/spec/json/auth/mongodb-aws.rst deleted file mode 100644 index 1a256b560..000000000 --- a/src/test/spec/json/auth/mongodb-aws.rst +++ /dev/null @@ -1,94 +0,0 @@ -=========== -MongoDB AWS -=========== - -There are 5 scenarios drivers MUST test: - -#. ``Regular Credentials``: Auth via an ``ACCESS_KEY_ID`` and ``SECRET_ACCESS_KEY`` pair -#. ``EC2 Credentials``: Auth from an EC2 instance via temporary credentials assigned to the machine -#. ``ECS Credentials``: Auth from an ECS instance via temporary credentials assigned to the task -#. ``Assume Role``: Auth via temporary credentials obtained from an STS AssumeRole request -#. ``AWS Lambda``: Auth via environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN``. - -For brevity, this section gives the values ````, ```` and ```` in place of a valid access key ID, secret access key and session token (also known as a security token). Note that if these values are passed into the URI they MUST be URL encoded. Sample values are below. - -.. code-block:: - - AccessKeyId=AKIAI44QH8DHBEXAMPLE - SecretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - Token=AQoDYXdzEJr... -| -.. sectnum:: - -Regular credentials -====================== - -Drivers MUST be able to authenticate by providing a valid access key id and secret access key pair as the username and password, respectively, in the MongoDB URI. An example of a valid URI would be: - -.. code-block:: - - mongodb://:@localhost/?authMechanism=MONGODB-AWS -| -EC2 Credentials -=============== - -Drivers MUST be able to authenticate from an EC2 instance via temporary credentials assigned to the machine. A sample URI on an EC2 machine would be: - -.. code-block:: - - mongodb://localhost/?authMechanism=MONGODB-AWS -| -.. note:: No username, password or session token is passed into the URI. Drivers MUST query the EC2 instance endpoint to obtain these credentials. - -ECS instance -============ - -Drivers MUST be able to authenticate from an ECS container via temporary credentials. A sample URI in an ECS container would be: - -.. code-block:: - - mongodb://localhost/?authMechanism=MONGODB-AWS -| -.. note:: No username, password or session token is passed into the URI. Drivers MUST query the ECS container endpoint to obtain these credentials. - -AssumeRole -========== - -Drivers MUST be able to authenticate using temporary credentials returned from an assume role request. These temporary credentials consist of an access key ID, a secret access key, and a security token passed into the URI. A sample URI would be: - -.. code-block:: - - mongodb://:@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN: -| -AWS Lambda -========== - -Drivers MUST be able to authenticate via an access key ID, secret access key and optional session token taken from the environment variables, respectively: - -.. code-block:: - - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN -| - -Sample URIs both with and without optional session tokens set are shown below. Drivers MUST test both cases. - -.. code-block:: bash - - # without a session token - export AWS_ACCESS_KEY_ID="" - export AWS_SECRET_ACCESS_KEY="" - - URI="mongodb://localhost/?authMechanism=MONGODB-AWS" -| -.. code-block:: bash - - # with a session token - export AWS_ACCESS_KEY_ID="" - export AWS_SECRET_ACCESS_KEY="" - export AWS_SESSION_TOKEN="" - - URI="mongodb://localhost/?authMechanism=MONGODB-AWS" -| -.. note:: No username, password or session token is passed into the URI. Drivers MUST check the environment variables listed above for these values. If the session token is set Drivers MUST use it. diff --git a/src/test/spec/json/auth/mongodb-oidc.md b/src/test/spec/json/auth/mongodb-oidc.md new file mode 100644 index 000000000..e95f45e68 --- /dev/null +++ b/src/test/spec/json/auth/mongodb-oidc.md @@ -0,0 +1,557 @@ +# MongoDB OIDC + +## Local Testing + +See the detailed instructions in +[drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md) +for how to set up your environment for OIDC testing. + +______________________________________________________________________ + +## Unified Spec Tests + +Drivers MUST run the unified spec tests in all supported OIDC environments. Drivers MUST set the placeholder +authMechanism properties (`ENVIRONMENT` and `TOKEN_RESOURCE`, if applicable). These will typically be read from +environment variables set by the test runner, e,g. `AZUREOIDC_RESOURCE`. + +______________________________________________________________________ + +## Machine Authentication Flow Prose Tests + +Drivers MUST run the machine prose tests when `OIDC_TOKEN_DIR` is set. Drivers can either set the `ENVIRONMENT:test` +auth mechanism property, or use a custom callback that also reads the file. + +Drivers can also choose to run the machine prose tests on GCP or Azure VMs, or on the Kubernetes clusters. + +Drivers MUST implement all prose tests in this section. Unless otherwise noted, all `MongoClient` instances MUST be +configured with `retryReads=false`. + +> [!NOTE] +> For test cases that create fail points, drivers MUST either use a unique `appName` or explicitly remove the fail point +> callback to prevent interaction between test cases. + +After setting up your OIDC +[environment](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md), +source the `secrets-export.sh` file and use the associated env variables in your tests. + +### Callback Authentication + +**1.1 Callback is called during authentication** + +- Create an OIDC configured client. +- Perform a `find` operation that succeeds. +- Assert that the callback was called 1 time. +- Close the client. + +**1.2 Callback is called once for multiple connections** + +- Create an OIDC configured client. +- Start 10 threads and run 100 `find` operations in each thread that all succeed. +- Assert that the callback was called 1 time. +- Close the client. + +### (2) OIDC Callback Validation + +**2.1 Valid Callback Inputs** + +- Create an OIDC configured client with an OIDC callback that validates its inputs and returns a valid access token. +- Perform a `find` operation that succeeds. +- Assert that the OIDC callback was called with the appropriate inputs, including the timeout parameter if possible. +- Close the client. + +**2.2 OIDC Callback Returns Null** + +- Create an OIDC configured client with an OIDC callback that returns `null`. +- Perform a `find` operation that fails. +- Close the client. + +**2.3 OIDC Callback Returns Missing Data** + +- Create an OIDC configured client with an OIDC callback that returns data not conforming to the `OIDCCredential` with + missing fields. +- Perform a `find` operation that fails. +- Close the client. + +**2.4 Invalid Client Configuration with Callback** + +- Create an OIDC configured client with an OIDC callback and auth mechanism property `ENVIRONMENT:test`. +- Assert it returns a client configuration error upon client creation, or client connect if your driver validates on + connection. + +**2.5 Invalid use of ALLOWED_HOSTS** + +- Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "azure", "ALLOWED_HOSTS": []}`. +- Assert it returns a client configuration error upon client creation, or client connect if your driver validates on + connection. + +### (3) Authentication Failure + +**3.1 Authentication failure with cached tokens fetch a new token and retry auth** + +- Create an OIDC configured client. +- Poison the *Client Cache* with an invalid access token. +- Perform a `find` operation that succeeds. +- Assert that the callback was called 1 time. +- Close the client. + +**3.2 Authentication failures without cached tokens return an error** + +- Create an OIDC configured client with an OIDC callback that always returns invalid access tokens. +- Perform a `find` operation that fails. +- Assert that the callback was called 1 time. +- Close the client. + +**3.3 Unexpected error code does not clear the cache** + +- Create a `MongoClient` with an OIDC callback that returns a valid token. +- Set a fail point for `saslStart` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "saslStart" + ], + errorCode: 20 // IllegalOperation + } +} +``` + +- Perform a `find` operation that fails. +- Assert that the callback has been called once. +- Perform a `find` operation that succeeds. +- Assert that the callback has been called once. +- Close the client. + +### (4) Reauthentication + +#### 4.1 Reauthentication Succeeds + +- Create an OIDC configured client. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the callback was called 2 times (once during the connection handshake, and again during reauthentication). +- Close the client. + +#### 4.2 Read Commands Fail If Reauthentication Fails + +- Create a `MongoClient` whose OIDC callback returns one good token and then bad tokens after the first call. +- Perform a `find` operation that succeeds. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that fails. +- Assert that the callback was called 2 times. +- Close the client. + +#### 4.3 Write Commands Fail If Reauthentication Fails + +- Create a `MongoClient` whose OIDC callback returns one good token and then bad tokens after the first call. +- Perform an `insert` operation that succeeds. +- Set a fail point for `insert` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "insert" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that fails. +- Assert that the callback was called 2 times. +- Close the client. + +#### 4.4 Speculative Authentication should be ignored on Reauthentication + +- Create an OIDC configured client. +- Populate the *Client Cache* with a valid access token to enforce Speculative Authentication. +- Perform an `insert` operation that succeeds. +- Assert that the callback was not called. +- Assert there were no `SaslStart` commands executed. +- Set a fail point for `insert` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "insert" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform an `insert` operation that succeeds. +- Assert that the callback was called once. +- Assert there were `SaslStart` commands executed. +- Close the client. + +## (5) Azure Tests + +Drivers MUST only run the Azure tests when testing on an Azure VM. See instructions in +[Drivers Evergreen Tools](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/azure/README.md) +for test setup. + +# 5.1 Azure With No Username + +- Create an OIDC configured client with `ENVIRONMENT:azure` and a valid `TOKEN_RESOURCE` and no username. +- Perform a `find` operation that succeeds. +- Close the client. + +# 5.2 Azure with Bad Username + +- Create an OIDC configured client with `ENVIRONMENT:azure` and a valid `TOKEN_RESOURCE` and a username of `"bad"`. +- Perform a `find` operation that fails. +- Close the client. + +______________________________________________________________________ + +## Human Authentication Flow Prose Tests + +Drivers that support the [Human Authentication Flow](../auth.md#human-authentication-flow) MUST implement all prose +tests in this section. Unless otherwise noted, all `MongoClient` instances MUST be configured with `retryReads=false`. + +The human workflow tests MUST only be run when `OIDC_TOKEN_DIR` is set. + +> [!NOTE] +> For test cases that create fail points, drivers MUST either use a unique `appName` or explicitly remove the fail point +> after the test to prevent interaction between test cases. + +Drivers MUST be able to authenticate against a server configured with either one or two configured identity providers. + +Unless otherwise specified, use `MONGODB_URI_SINGLE` and the `test_user1` token in the `OIDC_TOKEN_DIR` as the +"access_token", and a dummy "refresh_token" for all tests. + +When using an explicit username for the client, we use the token name and the domain name given by `OIDC_DOMAIN`, e.g. +`test_user1@${OIDC_DOMAIN}`. + +### (1) OIDC Human Callback Authentication + +Drivers MUST be able to authenticate using OIDC callback(s) when there is one principal configured. + +**1.1 Single Principal Implicit Username** + +- Create an OIDC configured client. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.2 Single Principal Explicit Username** + +- Create an OIDC configured client with `MONGODB_URI_SINGLE` and a username of `test_user1@${OIDC_DOMAIN}`. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.3 Multiple Principal User 1** + +- Create an OIDC configured client with `MONGODB_URI_MULTI` and username of `test_user1@${OIDC_DOMAIN}`. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.4 Multiple Principal User 2** + +- Create an OIDC configured client with `MONGODB_URI_MULTI` and username of `test_user2@${OIDC_DOMAIN}`. that reads the + `test_user2` token file. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.5 Multiple Principal No User** + +- Create an OIDC configured client with `MONGODB_URI_MULTI` and no username. +- Assert that a `find` operation fails. +- Close the client. + +**1.6 Allowed Hosts Blocked** + +- Create an OIDC configured client with an `ALLOWED_HOSTS` that is an empty list. +- Assert that a `find` operation fails with a client-side error. +- Close the client. +- Create a client that uses the URL `mongodb://localhost/?authMechanism=MONGODB-OIDC&ignored=example.com`, a human + callback, and an `ALLOWED_HOSTS` that contains `["example.com"]`. +- Assert that a `find` operation fails with a client-side error. +- Close the client. + +**1.7 Allowed Hosts in Connection String Ignored** + +- Create an OIDC configured client with the connection string: + `mongodb+srv://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=ALLOWED_HOSTS:%5B%22example.com%22%5D` + and a Human Callback. +- Assert that the creation of the client raises a configuration error. + +**1.8 Machine IdP with Human Callback** + +This test MUST only be run when `OIDC_IS_LOCAL` is set. This indicates that the server is local and not using Atlas. In +this case, `MONGODB_URI_SINGLE` will be configured with a human user `test_user1`, and a machine user `test_machine`. +This test uses the machine user with a human callback, ensuring that the missing `clientId` in the +`PrincipalStepRequest` response is handled by the driver. + +- Create an OIDC configured client with `MONGODB_URI_SINGLE` and a username of `test_machine` that uses the + `test_machine` token. +- Perform a find operation that succeeds. +- Close the client. + +### (2) OIDC Human Callback Validation + +**2.1 Valid Callback Inputs** + +- Create an OIDC configured client with a human callback that validates its inputs and returns a valid access token. +- Perform a `find` operation that succeeds. Verify that the human callback was called with the appropriate inputs, + including the timeout parameter if possible. +- Close the client. + +**2.2 Human Callback Returns Missing Data** + +- Create an OIDC configured client with a human callback that returns data not conforming to the `OIDCCredential` with + missing fields. +- Perform a `find` operation that fails. +- Close the client. + +**2.3 Refresh Token Is Passed To The Callback** + +- Create a `MongoClient` with a human callback that checks for the presence of a refresh token. +- Perform a find operation that succeeds. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the callback has been called twice. +- Assert that the refresh token was provided to the callback once. + +### (3) Speculative Authentication + +**3.1 Uses speculative authentication if there is a cached token** + +- Create an OIDC configured client with a human callback that returns a valid token. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + closeConnection: true + } +} +``` + +- Perform a `find` operation that fails. +- Set a fail point for `saslStart` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "saslStart" + ], + errorCode: 18 + } +} +``` + +- Perform a `find` operation that succeeds. +- Close the client. + +**3.2 Does not use speculative authentication if there is no cached token** + +- Create an OIDC configured client with a human callback that returns a valid token. +- Set a fail point for `saslStart` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "saslStart" + ], + errorCode: 18 + } +} +``` + +- Perform a `find` operation that fails. +- Close the client. + +### (4) Reauthentication + +**4.1 Succeeds** + +- Create an OIDC configured client and add an event listener. The following assumes that the driver does not emit + `saslStart` or `saslContinue` events. If the driver does emit those events, ignore/filter them for the purposes of + this test. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Clear the listener state if possible. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform another find operation that succeeds. +- Assert that the human callback has been called twice. +- Assert that the ordering of list started events is \[`find`\], , `find`. Note that if the listener stat could not be + cleared then there will and be extra `find` command. +- Assert that the list of command succeeded events is \[`find`\]. +- Assert that a `find` operation failed once during the command execution. +- Close the client. + +**4.2 Succeeds no refresh** + +- Create an OIDC configured client with a human callback that does not return a refresh token. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called twice. +- Close the client. + +**4.3 Succeeds after refresh fails** + +- Create an OIDC configured client with a callback that returns the `test_user1` access token and a bad refresh token. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find", + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called 2 times. +- Close the client. + +**4.4 Fails** + +- Create an OIDC configured client that returns invalid refresh tokens and returns invalid access tokens after the first + access. +- Perform a find operation that succeeds (to force a speculative auth). +- Assert that the human callback has been called once. +- Force a reauthenication using a failCommand of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find", + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a find operation that fails. +- Assert that the human callback has been called three times. +- Close the client. diff --git a/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.json b/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.json new file mode 100644 index 000000000..0a8658455 --- /dev/null +++ b/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.json @@ -0,0 +1,422 @@ +{ + "description": "MONGODB-OIDC authentication with retry disabled", + "schemaVersion": "1.19", + "runOnRequirements": [ + { + "minServerVersion": "7.0", + "auth": true, + "authMechanism": "MONGODB-OIDC", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "failPointClient", + "useMultipleMongoses": false + } + }, + { + "client": { + "id": "client0", + "uriOptions": { + "authMechanism": "MONGODB-OIDC", + "authMechanismProperties": { + "$$placeholder": 1 + }, + "retryReads": false, + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collName" + } + } + ], + "initialData": [ + { + "collectionName": "collName", + "databaseName": "test", + "documents": [] + } + ], + "tests": [ + { + "description": "A read operation should succeed", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectResult": [] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collName", + "filter": {} + } + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "A write operation should succeed", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collName", + "documents": [ + { + "_id": 1, + "x": 1 + } + ] + } + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "Read commands should reauthenticate and retry when a ReauthenticationRequired error happens", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 391 + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": {} + }, + "expectResult": [] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collName", + "filter": {} + } + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "collName", + "filter": {} + } + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + } + ] + } + ] + }, + { + "description": "Write commands should reauthenticate and retry when a ReauthenticationRequired error happens", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 391 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collName", + "documents": [ + { + "_id": 1, + "x": 1 + } + ] + } + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "collName", + "documents": [ + { + "_id": 1, + "x": 1 + } + ] + } + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "Handshake with cached token should use speculative authentication", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 1 + } + }, + "expectError": { + "isClientError": true + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslStart" + ], + "errorCode": 18 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collName", + "documents": [ + { + "_id": 1, + "x": 1 + } + ] + } + } + }, + { + "commandFailedEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "collName", + "documents": [ + { + "_id": 1, + "x": 1 + } + ] + } + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "Handshake without cached token should not use speculative authentication", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslStart" + ], + "errorCode": 18 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 1 + } + }, + "expectError": { + "errorCode": 18 + } + } + ] + } + ] +} diff --git a/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.yml b/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.yml new file mode 100644 index 000000000..339f88174 --- /dev/null +++ b/src/test/spec/json/auth/unified/mongodb-oidc-no-retry.yml @@ -0,0 +1,229 @@ +--- +description: "MONGODB-OIDC authentication with retry disabled" +schemaVersion: "1.19" +runOnRequirements: +- minServerVersion: "7.0" + auth: true + authMechanism: "MONGODB-OIDC" + serverless: forbid +createEntities: +- client: + id: &failPointClient failPointClient + useMultipleMongoses: false +- client: + id: client0 + uriOptions: + authMechanism: "MONGODB-OIDC" + # The $$placeholder document should be replaced by auth mechanism + # properties that enable OIDC auth on the target cloud platform. For + # example, when running the test on EC2, replace the $$placeholder + # document with {"ENVIRONMENT": "test"}. + authMechanismProperties: { $$placeholder: 1 } + retryReads: false + retryWrites: false + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent +- database: + id: database0 + client: client0 + databaseName: test +- collection: + id: collection0 + database: database0 + collectionName: collName +initialData: +- collectionName: collName + databaseName: test + documents: [] +tests: +- description: A read operation should succeed + operations: + - name: find + object: collection0 + arguments: + filter: {} + expectResult: [] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + find: collName + filter: {} + - commandSucceededEvent: + commandName: find +- description: A write operation should succeed + operations: + - name: insertOne + object: collection0 + arguments: + document: + _id: 1 + x: 1 + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + insert: collName + documents: + - _id: 1 + x: 1 + - commandSucceededEvent: + commandName: insert +- description: Read commands should reauthenticate and retry when a ReauthenticationRequired error happens + operations: + - name: failPoint + object: testRunner + arguments: + client: failPointClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 391 # ReauthenticationRequired + - name: find + object: collection0 + arguments: + filter: {} + expectResult: [] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + find: collName + filter: {} + - commandFailedEvent: + commandName: find + - commandStartedEvent: + command: + find: collName + filter: {} + - commandSucceededEvent: + commandName: find +- description: Write commands should reauthenticate and retry when a ReauthenticationRequired error happens + operations: + - name: failPoint + object: testRunner + arguments: + client: failPointClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - insert + errorCode: 391 # ReauthenticationRequired + - name: insertOne + object: collection0 + arguments: + document: + _id: 1 + x: 1 + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + insert: collName + documents: + - _id: 1 + x: 1 + - commandFailedEvent: + commandName: insert + - commandStartedEvent: + command: + insert: collName + documents: + - _id: 1 + x: 1 + - commandSucceededEvent: + commandName: insert +- description: Handshake with cached token should use speculative authentication + operations: + - name: failPoint + object: testRunner + arguments: + client: failPointClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - insert + closeConnection: true + - name: insertOne + object: collection0 + arguments: + document: + _id: 1 + x: 1 + expectError: + isClientError: true + - name: failPoint + object: testRunner + arguments: + client: failPointClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslStart + errorCode: 18 + - name: insertOne + object: collection0 + arguments: + document: + _id: 1 + x: 1 + expectEvents: + - client: client0 + events: + - commandStartedEvent: + command: + insert: collName + documents: + - _id: 1 + x: 1 + - commandFailedEvent: + commandName: insert + - commandStartedEvent: + command: + insert: collName + documents: + - _id: 1 + x: 1 + - commandSucceededEvent: + commandName: insert +- description: Handshake without cached token should not use speculative authentication + operations: + - name: failPoint + object: testRunner + arguments: + client: failPointClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslStart + errorCode: 18 + - name: insertOne + object: collection0 + arguments: + document: + _id: 1 + x: 1 + expectError: + errorCode: 18 \ No newline at end of file diff --git a/src/test/spec/json/change-streams/README.md b/src/test/spec/json/change-streams/README.md new file mode 100644 index 000000000..16ebee98d --- /dev/null +++ b/src/test/spec/json/change-streams/README.md @@ -0,0 +1,262 @@ +# Change Streams + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests that drivers can use to prove their conformance +to the Change Streams Spec. + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +### Subdirectories for Test Formats + +This document describes the legacy format for change streams tests. Tests in this legacy format are located under +`./legacy/`. + +New change streams tests should be written in the +[unified test format](../../unified-test-format/unified-test-format.md) and placed under `./unified/`. + +## Spec Test Format + +Each YAML file has the following keys: + +- `database_name`: The default database +- `collection_name`: The default collection +- `database2_name`: Another database +- `collection2_name`: Another collection +- `tests`: An array of tests that are to be run independently of each other. Each test will have some of the following + fields: + - `description`: The name of the test. + - `minServerVersion`: The minimum server version to run this test against. If not present, assume there is no minimum + server version. + - `maxServerVersion`: Reserved for later use + - `failPoint`: Optional configureFailPoint command document to run to configure a fail point on the primary server. + - `target`: The entity on which to run the change stream. Valid values are: + - `collection`: Watch changes on collection `database_name.collection_name` + - `database`: Watch changes on database `database_name` + - `client`: Watch changes on entire clusters + - `topology`: An array of server topologies against which to run the test. Valid topologies are `single`, + `replicaset`, `sharded`, and `load-balanced`. + - `changeStreamPipeline`: An array of additional aggregation pipeline stages to add to the change stream + - `changeStreamOptions`: Additional options to add to the changeStream + - `operations`: Array of documents, each describing an operation. Each document has the following fields: + - `database`: Database against which to run the operation + - `collection`: Collection against which to run the operation + - `name`: Name of the command to run + - `arguments` (optional): Object of arguments for the command (ex: document to insert) + - `expectations`: Optional list of command-started events in Extended JSON format + - `result`: Document with ONE of the following fields: + - `error`: Describes an error received during the test + - `success`: An Extended JSON array of documents expected to be received from the changeStream + +## Spec Test Match Function + +The definition of MATCH or MATCHES in the Spec Test Runner is as follows: + +- MATCH takes two values, `expected` and `actual` +- Notation is "Assert \[actual\] MATCHES \[expected\] +- Assertion passes if `expected` is a subset of `actual`, with the value `42` acting as placeholders for "any value" + +Pseudocode implementation of `actual` MATCHES `expected`: + +```text +If expected is "42" or 42: + Assert that actual exists (is not null or undefined) +Else: + Assert that actual is of the same JSON type as expected + If expected is a JSON array: + For every idx/value in expected: + Assert that actual[idx] MATCHES value + Else if expected is a JSON object: + For every key/value in expected + Assert that actual[key] MATCHES value + Else: + Assert that expected equals actual +``` + +The expected values for `result.success` and `expectations` are written in Extended JSON. Drivers may adopt any of the +following approaches to comparisons, as long as they are consistent: + +- Convert `actual` to Extended JSON and compare to `expected` +- Convert `expected` and `actual` to BSON, and compare them +- Convert `expected` and `actual` to native equivalents of JSON, and compare them + +## Spec Test Runner + +Before running the tests + +- Create a MongoClient `globalClient`, and connect to the server. When executing tests against a sharded cluster, + `globalClient` must only connect to one mongos. This is because tests that set failpoints will only work + consistently if both the `configureFailPoint` and failing commands are sent to the same mongos. + +For each YAML file, for each element in `tests`: + +- If `topology` does not include the topology of the server instance(s), skip this test. +- Use `globalClient` to + - Drop the database `database_name` + - Drop the database `database2_name` + - Create the database `database_name` and the collection `database_name.collection_name` + - Create the database `database2_name` and the collection `database2_name.collection2_name` + - If the the `failPoint` field is present, configure the fail point on the primary server. See + [Server Fail Point](../../transactions/tests/legacy-test-format.md#server-fail-point) in the Transactions spec + test documentation for more information. +- Create a new MongoClient `client` +- Begin monitoring all APM events for `client`. (If the driver uses global listeners, filter out all events that do not + originate with `client`). Filter out any "internal" commands (e.g. `hello` or legacy hello) +- Using `client`, create a changeStream `changeStream` against the specified `target`. Use `changeStreamPipeline` and + `changeStreamOptions` if they are non-empty. Capture any error. +- If there was no error, use `globalClient` and run every operation in `operations` in serial against the server until + all operations have been executed or an error is thrown. Capture any error. +- If there was no error and `result.error` is set, iterate `changeStream` once and capture any error. +- If there was no error and `result.success` is non-empty, iterate `changeStream` until it returns as many changes as + there are elements in the `result.success` array or an error is thrown. Capture any error. +- Close `changeStream` +- If there was an error: + - Assert that an error was expected for the test. + - Assert that the error MATCHES `result.error` +- Else: + - Assert that no error was expected for the test + - Assert that the changes received from `changeStream` MATCH the results in `result.success` +- If there are any `expectations` + - For each (`expected`, `idx`) in `expectations` + - If `actual[idx]` is a `killCursors` event, skip it and move to `actual[idx+1]`. + - Else assert that `actual[idx]` MATCHES `expected` + - Note: the change stream test command event expectations cover a prefix subset of all command events published by the + driver. The test runner MUST verify that, if there are N expectations, that the first N events published by the + driver match the expectations, and MUST NOT inspect any subsequent events published by the driver. +- Close the MongoClient `client` + +After running all tests + +- Close the MongoClient `globalClient` +- Drop database `database_name` +- Drop database `database2_name` + +### Iterating the Change Stream + +Although synchronous drivers must provide a +[non-blocking mode of iteration](../change-streams.md#not-blocking-on-iteration), asynchronous drivers may not have such +a mechanism. Those drivers with only a blocking mode of iteration should be careful not to iterate the change stream +unnecessarily, as doing so could cause the test runner to block indefinitely. For this reason, the test runner procedure +above advises drivers to take a conservative approach to iteration. + +If the test expects an error and one was not thrown by either creating the change stream or executing the test's +operations, iterating the change stream once allows for an error to be thrown by a `getMore` command. If the test does +not expect any error, the change stream should be iterated only until it returns as many result documents as are +expected by the test. + +### Testing on Sharded Clusters + +When writing data on sharded clusters, majority-committed data does not always show up in the response of the first +`getMore` command after the data is written. This is because in sharded clusters, no data from shard A may be returned +until all other shard reports an entry that sorts after the change in shard A. + +To account for this, drivers MUST NOT rely on change stream documents in certain batches. For example, if expecting two +documents in a change stream, these may not be part of the same `getMore` response, or even be produced in two +subsequent `getMore` responses. Drivers MUST allow for a `getMore` to produce empty batches when testing on a sharded +cluster. By default, this can take up to 10 seconds, but can be controlled by enabling the `writePeriodicNoops` server +parameter and configuring the `periodNoopIntervalSecs` parameter. Choosing lower values allows for running change stream +tests with smaller timeouts. + +## Prose Tests + +The following tests have not yet been automated, but MUST still be tested. All tests SHOULD be run on both replica sets +and sharded clusters unless otherwise specified: + +1. `ChangeStream` must continuously track the last seen `resumeToken` + +2. `ChangeStream` will throw an exception if the server response is missing the resume token (if wire version is \< 8, + this is a driver-side error; for 8+, this is a server-side error) + +3. After receiving a `resumeToken`, `ChangeStream` will automatically resume one time on a resumable error with the + initial pipeline and options, except for the addition/update of a `resumeToken`. + +4. `ChangeStream` will not attempt to resume on any error encountered while executing an `aggregate` command. Note that + retryable reads may retry `aggregate` commands. Drivers should be careful to distinguish retries from resume + attempts. Alternatively, drivers may specify `retryReads=false` or avoid using a + [retryable error](../../retryable-reads/retryable-reads.md#retryable-error) for this test. + +5. **Removed** + +6. `ChangeStream` will perform server selection before attempting to resume, using initial `readPreference` + +7. Ensure that a cursor returned from an aggregate command with a cursor id and an initial empty batch is not closed on + the driver side. + +8. The `killCursors` command sent during the "Resume Process" must not be allowed to throw an exception. + +9. `$changeStream` stage for `ChangeStream` against a server `>=4.0` and `<4.0.7` that has not received any results yet + MUST include a `startAtOperationTime` option when resuming a change stream. + +10. **Removed** + +11. For a `ChangeStream` under these conditions: + + - Running against a server `>=4.0.7`. + - The batch is empty or has been iterated to the last document. + + Expected result: + + - `getResumeToken` must return the `postBatchResumeToken` from the current command response. + +12. For a `ChangeStream` under these conditions: + + - Running against a server `<4.0.7`. + - The batch is empty or has been iterated to the last document. + + Expected result: + + - `getResumeToken` must return the `_id` of the last document returned if one exists. + - `getResumeToken` must return `resumeAfter` from the initial aggregate if the option was specified. + - If `resumeAfter` was not specified, the `getResumeToken` result must be empty. + +13. For a `ChangeStream` under these conditions: + + - The batch is not empty. + - The batch has been iterated up to but not including the last element. + + Expected result: + + - `getResumeToken` must return the `_id` of the previous document returned. + +14. For a `ChangeStream` under these conditions: + + - The batch is not empty. + - The batch hasn’t been iterated at all. + - Only the initial `aggregate` command has been executed. + + Expected result: + + - `getResumeToken` must return `startAfter` from the initial aggregate if the option was specified. + - `getResumeToken` must return `resumeAfter` from the initial aggregate if the option was specified. + - If neither the `startAfter` nor `resumeAfter` options were specified, the `getResumeToken` result must be empty. + + Note that this test cannot be run against sharded topologies because in that case the initial `aggregate` command + only establishes cursors on the shards and always returns an empty `firstBatch`. + +15. **Removed** + +16. **Removed** + +17. `$changeStream` stage for `ChangeStream` started with `startAfter` against a server `>=4.1.1` that has not received + any results yet MUST include a `startAfter` option and MUST NOT include a `resumeAfter` option when resuming a + change stream. + +18. `$changeStream` stage for `ChangeStream` started with `startAfter` against a server `>=4.1.1` that has received at + least one result MUST include a `resumeAfter` option and MUST NOT include a `startAfter` option when resuming a + change stream. + +19. Validate that large `ChangeStream` events are split when using `$changeStreamSplitLargeEvent`: + + 1. Run only against servers `>=6.0.9 && <6.1` or `>=7.0`. + 2. Create a new collection `_[C]()` with `changeStreamPreAndPostImages` enabled. + 3. Insert into `_[C]()` a document at least 10mb in size, e.g. `{ "value": "q"*10*1024*1024 }` + 4. Create a change stream `_[S]()` by calling `watch` on `_[C]()` with pipeline + `[{ "$changeStreamSplitLargeEvent": {} }]` and `fullDocumentBeforeChange=required`. + 5. Call `updateOne` on `_[C]()` with an empty `query` and an update setting the field to a new large value, e.g. + `{ "$set": { "value": "z"*10*1024*1024 } }`. + 6. Collect two events from `_[S]()`. + 7. Assert that the events collected have `splitEvent` fields `{ "fragment": 1, "of": 2 }` and + `{ "fragment": 2, "of": 2 }`, in that order. diff --git a/src/test/spec/json/change-streams/README.rst b/src/test/spec/json/change-streams/README.rst deleted file mode 100644 index 7ff88912e..000000000 --- a/src/test/spec/json/change-streams/README.rst +++ /dev/null @@ -1,241 +0,0 @@ -.. role:: javascript(code) - :language: javascript - -============== -Change Streams -============== - -.. contents:: - --------- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests that -drivers can use to prove their conformance to the Change Streams Spec. - -Several prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Subdirectories for Test Formats -------------------------------- - -This document describes the legacy format for change streams tests. -Tests in this legacy format are located under ``./legacy/``. - -New change streams tests should be written in the `unified test format <../../unified-test-format/unified-test-format.rst>`__ -and placed under ``./unified/``. - -Spec Test Format -================ - -Each YAML file has the following keys: - -- ``database_name``: The default database -- ``collection_name``: The default collection -- ``database2_name``: Another database -- ``collection2_name``: Another collection -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some of the following fields: - - - ``description``: The name of the test. - - ``minServerVersion``: The minimum server version to run this test against. If not present, assume there is no minimum server version. - - ``maxServerVersion``: Reserved for later use - - ``failPoint``: Optional configureFailPoint command document to run to configure a fail point on the primary server. - - ``target``: The entity on which to run the change stream. Valid values are: - - - ``collection``: Watch changes on collection ``database_name.collection_name`` - - ``database``: Watch changes on database ``database_name`` - - ``client``: Watch changes on entire clusters - - ``topology``: An array of server topologies against which to run the test. - Valid topologies are ``single``, ``replicaset``, ``sharded``, and ``load-balanced``. - - ``changeStreamPipeline``: An array of additional aggregation pipeline stages to add to the change stream - - ``changeStreamOptions``: Additional options to add to the changeStream - - ``operations``: Array of documents, each describing an operation. Each document has the following fields: - - - ``database``: Database against which to run the operation - - ``collection``: Collection against which to run the operation - - ``name``: Name of the command to run - - ``arguments`` (optional): Object of arguments for the command (ex: document to insert) - - - ``expectations``: Optional list of command-started events in Extended JSON format - - ``result``: Document with ONE of the following fields: - - - ``error``: Describes an error received during the test - - ``success``: An Extended JSON array of documents expected to be received from the changeStream - -Spec Test Match Function -======================== - -The definition of MATCH or MATCHES in the Spec Test Runner is as follows: - -- MATCH takes two values, ``expected`` and ``actual`` -- Notation is "Assert [actual] MATCHES [expected] -- Assertion passes if ``expected`` is a subset of ``actual``, with the value ``42`` acting as placeholders for "any value" - -Pseudocode implementation of ``actual`` MATCHES ``expected``: - -:: - - If expected is "42" or 42: - Assert that actual exists (is not null or undefined) - Else: - Assert that actual is of the same JSON type as expected - If expected is a JSON array: - For every idx/value in expected: - Assert that actual[idx] MATCHES value - Else if expected is a JSON object: - For every key/value in expected - Assert that actual[key] MATCHES value - Else: - Assert that expected equals actual - -The expected values for ``result.success`` and ``expectations`` are written in Extended JSON. Drivers may adopt any of the following approaches to comparisons, as long as they are consistent: - -- Convert ``actual`` to Extended JSON and compare to ``expected`` -- Convert ``expected`` and ``actual`` to BSON, and compare them -- Convert ``expected`` and ``actual`` to native equivalents of JSON, and compare them - -Spec Test Runner -================ - -Before running the tests - -- Create a MongoClient ``globalClient``, and connect to the server. - When executing tests against a sharded cluster, ``globalClient`` must only connect to one mongos. This is because tests - that set failpoints will only work consistently if both the ``configureFailPoint`` and failing commands are sent to the - same mongos. - -For each YAML file, for each element in ``tests``: - -- If ``topology`` does not include the topology of the server instance(s), skip this test. -- Use ``globalClient`` to - - - Drop the database ``database_name`` - - Drop the database ``database2_name`` - - Create the database ``database_name`` and the collection ``database_name.collection_name`` - - Create the database ``database2_name`` and the collection ``database2_name.collection2_name`` - - If the the ``failPoint`` field is present, configure the fail point on the primary server. See - `Server Fail Point <../../transactions/tests#server-fail-point>`_ in the - Transactions spec test documentation for more information. - -- Create a new MongoClient ``client`` -- Begin monitoring all APM events for ``client``. (If the driver uses global listeners, filter out all events that do not originate with ``client``). Filter out any "internal" commands (e.g. ``hello`` or legacy hello) -- Using ``client``, create a changeStream ``changeStream`` against the specified ``target``. Use ``changeStreamPipeline`` and ``changeStreamOptions`` if they are non-empty. Capture any error. -- If there was no error, use ``globalClient`` and run every operation in ``operations`` in serial against the server until all operations have been executed or an error is thrown. Capture any error. -- If there was no error and ``result.error`` is set, iterate ``changeStream`` once and capture any error. -- If there was no error and ``result.success`` is non-empty, iterate ``changeStream`` until it returns as many changes as there are elements in the ``result.success`` array or an error is thrown. Capture any error. -- Close ``changeStream`` -- If there was an error: - - - Assert that an error was expected for the test. - - Assert that the error MATCHES ``result.error`` - -- Else: - - - Assert that no error was expected for the test - - Assert that the changes received from ``changeStream`` MATCH the results in ``result.success`` - -- If there are any ``expectations`` - - - For each (``expected``, ``idx``) in ``expectations`` - - If ``actual[idx]`` is a ``killCursors`` event, skip it and move to ``actual[idx+1]``. - - Else assert that ``actual[idx]`` MATCHES ``expected`` - - Note: the change stream test command event expectations cover a - prefix subset of all command events published by the driver. - The test runner MUST verify that, if there are N expectations, that the - first N events published by the driver match the expectations, and - MUST NOT inspect any subsequent events published by the driver. - -- Close the MongoClient ``client`` - -After running all tests - -- Close the MongoClient ``globalClient`` -- Drop database ``database_name`` -- Drop database ``database2_name`` - -Iterating the Change Stream ---------------------------- - -Although synchronous drivers must provide a `non-blocking mode of iteration <../change-streams.rst#not-blocking-on-iteration>`_, asynchronous drivers may not have such a mechanism. Those drivers with only a blocking mode of iteration should be careful not to iterate the change stream unnecessarily, as doing so could cause the test runner to block indefinitely. For this reason, the test runner procedure above advises drivers to take a conservative approach to iteration. - -If the test expects an error and one was not thrown by either creating the change stream or executing the test's operations, iterating the change stream once allows for an error to be thrown by a ``getMore`` command. If the test does not expect any error, the change stream should be iterated only until it returns as many result documents as are expected by the test. - -Testing on Sharded Clusters ---------------------------- - -When writing data on sharded clusters, majority-committed data does not always show up in the response of the first -``getMore`` command after the data is written. This is because in sharded clusters, no data from shard A may be returned -until all other shard reports an entry that sorts after the change in shard A. - -To account for this, drivers MUST NOT rely on change stream documents in certain batches. For example, if expecting two -documents in a change stream, these may not be part of the same ``getMore`` response, or even be produced in two -subsequent ``getMore`` responses. Drivers MUST allow for a ``getMore`` to produce empty batches when testing on a -sharded cluster. By default, this can take up to 10 seconds, but can be controlled by enabling the ``writePeriodicNoops`` -server parameter and configuring the ``periodNoopIntervalSecs`` parameter. Choosing lower values allows for running -change stream tests with smaller timeouts. - -Prose Tests -=========== - -The following tests have not yet been automated, but MUST still be tested. All tests SHOULD be run on both replica sets and sharded clusters unless otherwise specified: - -#. ``ChangeStream`` must continuously track the last seen ``resumeToken`` -#. ``ChangeStream`` will throw an exception if the server response is missing the resume token (if wire version is < 8, this is a driver-side error; for 8+, this is a server-side error) -#. After receiving a ``resumeToken``, ``ChangeStream`` will automatically resume one time on a resumable error with the initial pipeline and options, except for the addition/update of a ``resumeToken``. -#. ``ChangeStream`` will not attempt to resume on any error encountered while executing an ``aggregate`` command. Note that retryable reads may retry ``aggregate`` commands. Drivers should be careful to distinguish retries from resume attempts. Alternatively, drivers may specify ``retryReads=false`` or avoid using a `retryable error <../../retryable-reads/retryable-reads.rst#retryable-error>`_ for this test. -#. **Removed** -#. ``ChangeStream`` will perform server selection before attempting to resume, using initial ``readPreference`` -#. Ensure that a cursor returned from an aggregate command with a cursor id and an initial empty batch is not closed on the driver side. -#. The ``killCursors`` command sent during the "Resume Process" must not be allowed to throw an exception. -#. ``$changeStream`` stage for ``ChangeStream`` against a server ``>=4.0`` and ``<4.0.7`` that has not received any results yet MUST include a ``startAtOperationTime`` option when resuming a change stream. -#. **Removed** -#. For a ``ChangeStream`` under these conditions: - - - Running against a server ``>=4.0.7``. - - The batch is empty or has been iterated to the last document. - - Expected result: - - - ``getResumeToken`` must return the ``postBatchResumeToken`` from the current command response. - -#. For a ``ChangeStream`` under these conditions: - - - Running against a server ``<4.0.7``. - - The batch is empty or has been iterated to the last document. - - Expected result: - - - ``getResumeToken`` must return the ``_id`` of the last document returned if one exists. - - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. - - If ``resumeAfter`` was not specified, the ``getResumeToken`` result must be empty. - -#. For a ``ChangeStream`` under these conditions: - - - The batch is not empty. - - The batch has been iterated up to but not including the last element. - - Expected result: - - - ``getResumeToken`` must return the ``_id`` of the previous document returned. - -#. For a ``ChangeStream`` under these conditions: - - - The batch is not empty. - - The batch hasn’t been iterated at all. - - Only the initial ``aggregate`` command has been executed. - - Expected result: - - - ``getResumeToken`` must return ``startAfter`` from the initial aggregate if the option was specified. - - ``getResumeToken`` must return ``resumeAfter`` from the initial aggregate if the option was specified. - - If neither the ``startAfter`` nor ``resumeAfter`` options were specified, the ``getResumeToken`` result must be empty. - - Note that this test cannot be run against sharded topologies because in that case the initial ``aggregate`` command only establishes cursors on the shards and always returns an empty ``firstBatch``. - -#. **Removed** -#. **Removed** -#. ``$changeStream`` stage for ``ChangeStream`` started with ``startAfter`` against a server ``>=4.1.1`` that has not received any results yet MUST include a ``startAfter`` option and MUST NOT include a ``resumeAfter`` option when resuming a change stream. -#. ``$changeStream`` stage for ``ChangeStream`` started with ``startAfter`` against a server ``>=4.1.1`` that has received at least one result MUST include a ``resumeAfter`` option and MUST NOT include a ``startAfter`` option when resuming a change stream. diff --git a/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.json b/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.json index e6cc5ef66..a8667b543 100644 --- a/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.json +++ b/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.json @@ -42,70 +42,6 @@ } ], "tests": [ - { - "description": "disambiguatedPaths is not present when showExpandedEvents is false/unset", - "operations": [ - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "_id": 1, - "a": { - "1": 1 - } - } - } - }, - { - "name": "createChangeStream", - "object": "collection0", - "arguments": { - "pipeline": [] - }, - "saveResultAsEntity": "changeStream0" - }, - { - "name": "updateOne", - "object": "collection0", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "a.1": 2 - } - } - } - }, - { - "name": "iterateUntilDocumentOrError", - "object": "changeStream0", - "expectResult": { - "operationType": "update", - "ns": { - "db": "database0", - "coll": "collection0" - }, - "updateDescription": { - "updatedFields": { - "$$exists": true - }, - "removedFields": { - "$$exists": true - }, - "truncatedArrays": { - "$$exists": true - }, - "disambiguatedPaths": { - "$$exists": false - } - } - } - } - ] - }, { "description": "disambiguatedPaths is present on updateDescription when an ambiguous path is present", "operations": [ diff --git a/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.yml b/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.yml index 9ca9abf2e..7996c45f2 100644 --- a/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.yml +++ b/src/test/spec/json/change-streams/unified/change-streams-disambiguatedPaths.yml @@ -24,32 +24,6 @@ initialData: documents: [] tests: - - description: "disambiguatedPaths is not present when showExpandedEvents is false/unset" - operations: - - name: insertOne - object: *collection0 - arguments: - document: { _id: 1, 'a': { '1': 1 } } - - name: createChangeStream - object: *collection0 - arguments: { pipeline: [] } - saveResultAsEntity: &changeStream0 changeStream0 - - name: updateOne - object: *collection0 - arguments: - filter: { _id: 1 } - update: { $set: { 'a.1': 2 } } - - name: iterateUntilDocumentOrError - object: *changeStream0 - expectResult: - operationType: "update" - ns: { db: *database0, coll: *collection0 } - updateDescription: - updatedFields: { $$exists: true } - removedFields: { $$exists: true } - truncatedArrays: { $$exists: true } - disambiguatedPaths: { $$exists: false } - - description: "disambiguatedPaths is present on updateDescription when an ambiguous path is present" operations: - name: insertOne diff --git a/src/test/spec/json/change-streams/unified/change-streams-nsType.json b/src/test/spec/json/change-streams/unified/change-streams-nsType.json new file mode 100644 index 000000000..1861c9a5e --- /dev/null +++ b/src/test/spec/json/change-streams/unified/change-streams-nsType.json @@ -0,0 +1,145 @@ +{ + "description": "change-streams-nsType", + "schemaVersion": "1.7", + "runOnRequirements": [ + { + "minServerVersion": "8.1.0", + "topologies": [ + "replicaset", + "sharded" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + } + ], + "tests": [ + { + "description": "nsType is present when creating collections", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "foo" + } + }, + { + "name": "createChangeStream", + "object": "database0", + "arguments": { + "pipeline": [], + "showExpandedEvents": true + }, + "saveResultAsEntity": "changeStream0" + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "foo" + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "changeStream0", + "expectResult": { + "operationType": "create", + "nsType": "collection" + } + } + ] + }, + { + "description": "nsType is present when creating timeseries", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "foo" + } + }, + { + "name": "createChangeStream", + "object": "database0", + "arguments": { + "pipeline": [], + "showExpandedEvents": true + }, + "saveResultAsEntity": "changeStream0" + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "foo", + "timeseries": { + "timeField": "time", + "metaField": "meta", + "granularity": "minutes" + } + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "changeStream0", + "expectResult": { + "operationType": "create", + "nsType": "timeseries" + } + } + ] + }, + { + "description": "nsType is present when creating views", + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "foo" + } + }, + { + "name": "createChangeStream", + "object": "database0", + "arguments": { + "pipeline": [], + "showExpandedEvents": true + }, + "saveResultAsEntity": "changeStream0" + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "foo", + "viewOn": "testName" + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "changeStream0", + "expectResult": { + "operationType": "create", + "nsType": "view" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/change-streams/unified/change-streams-nsType.yml b/src/test/spec/json/change-streams/unified/change-streams-nsType.yml new file mode 100644 index 000000000..9885c4aaf --- /dev/null +++ b/src/test/spec/json/change-streams/unified/change-streams-nsType.yml @@ -0,0 +1,86 @@ +description: "change-streams-nsType" +schemaVersion: "1.7" +runOnRequirements: + - minServerVersion: "8.1.0" + topologies: [ replicaset, sharded ] + serverless: forbid +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + +tests: + - description: "nsType is present when creating collections" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: &collection0 foo + - name: createChangeStream + object: *database0 + arguments: + pipeline: [] + showExpandedEvents: true + saveResultAsEntity: &changeStream0 changeStream0 + - name: createCollection + object: *database0 + arguments: + collection: *collection0 + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: create + nsType: collection + + - description: "nsType is present when creating timeseries" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: &collection0 foo + - name: createChangeStream + object: *database0 + arguments: + pipeline: [] + showExpandedEvents: true + saveResultAsEntity: &changeStream0 changeStream0 + - name: createCollection + object: *database0 + arguments: + collection: *collection0 + timeseries: + timeField: "time" + metaField: "meta" + granularity: "minutes" + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: create + nsType: timeseries + + - description: "nsType is present when creating views" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: &collection0 foo + - name: createChangeStream + object: *database0 + arguments: + pipeline: [] + showExpandedEvents: true + saveResultAsEntity: &changeStream0 changeStream0 + - name: createCollection + object: *database0 + arguments: + collection: *collection0 + viewOn: testName + - name: iterateUntilDocumentOrError + object: *changeStream0 + expectResult: + operationType: create + nsType: view \ No newline at end of file diff --git a/src/test/spec/json/change-streams/unified/change-streams.json b/src/test/spec/json/change-streams/unified/change-streams.json index c8b60ed4e..a155d85b6 100644 --- a/src/test/spec/json/change-streams/unified/change-streams.json +++ b/src/test/spec/json/change-streams/unified/change-streams.json @@ -181,7 +181,12 @@ "field": "array", "newSize": 2 } - ] + ], + "disambiguatedPaths": { + "$$unsetOrMatches": { + "$$exists": true + } + } } } } @@ -1408,6 +1413,11 @@ "$$unsetOrMatches": { "$$exists": true } + }, + "disambiguatedPaths": { + "$$unsetOrMatches": { + "$$exists": true + } } } } diff --git a/src/test/spec/json/change-streams/unified/change-streams.yml b/src/test/spec/json/change-streams/unified/change-streams.yml index 3235533b5..7f824623a 100644 --- a/src/test/spec/json/change-streams/unified/change-streams.yml +++ b/src/test/spec/json/change-streams/unified/change-streams.yml @@ -115,7 +115,8 @@ tests: "field": "array", "newSize": 2 } - ] + ], + disambiguatedPaths: { $$unsetOrMatches: { $$exists: true } } } } @@ -722,6 +723,7 @@ tests: updatedFields: { x: 2 } removedFields: [] truncatedArrays: { $$unsetOrMatches: { $$exists: true } } + disambiguatedPaths: { $$unsetOrMatches: { $$exists: true } } - name: iterateUntilDocumentOrError object: *changeStream0 expectResult: diff --git a/src/test/spec/json/client-side-encryption/README.md b/src/test/spec/json/client-side-encryption/README.md new file mode 100644 index 000000000..b56160d62 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/README.md @@ -0,0 +1,3766 @@ +# Client Side Encryption Tests + +______________________________________________________________________ + +## Introduction + +This document describes the format of the driver spec tests included in the JSON and YAML files included in the `legacy` +sub-directory. Tests in the `unified` directory are written using the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +The `timeoutMS.yml`/`timeoutMS.json` files in this directory contain tests for the `timeoutMS` option and its +application to the client-side encryption feature. Drivers MUST only run these tests after implementing the +[Client Side Operations Timeout](../../client-side-operations-timeout/client-side-operations-timeout.md) specification. + +Additional prose tests, that are not represented in the spec tests, are described and MUST be implemented by all +drivers. + +Running spec and prose tests require that the driver and server both support Client-Side Field Level Encryption. CSFLE +is supported when all of the following are true: + +- Server version is 4.2.0 or higher. Legacy spec test runners can rely on `runOn.minServerVersion` for this check. +- Driver has libmongocrypt enabled +- At least one of [crypt_shared](../client-side-encryption.md#crypt_shared) and/or + [mongocryptd](../client-side-encryption.md#mongocryptd) is available. + +## Spec Test Format + +The spec tests format is an extension of the +[transactions spec legacy test format](../../transactions/tests/legacy-test-format.md) with some additions: + +- A `json_schema` to set on the collection used for operations. +- An `encrypted_fields` to set on the collection used for operations. +- A `key_vault_data` of data that should be inserted in the key vault collection before each test. +- Introduction `autoEncryptOpts` to `clientOptions` +- Addition of `$db`to command in`command_started_event` +- Addition of `$$type` to `command_started_event` and outcome. + +The semantics of `$$type` is that any actual value matching one of the types indicated by either a BSON type string or +an array of BSON type strings is considered a match. + +For example, the following matches a command_started_event for an insert of a document where `random` must be of type +`binData`: + +```text +- command_started_event: + command: + insert: *collection_name + documents: + - { random: { $$type: "binData" } } + ordered: true + command_name: insert +``` + +The following matches a command_started_event for an insert of a document where `random` must be of type `binData` or +`string`: + +```text +- command_started_event: + command: + insert: *collection_name + documents: + - { random: { $$type: ["binData", "string"] } } + ordered: true + command_name: insert +``` + +The values of `$$type` correspond to +[these documented string representations of BSON types](https://www.mongodb.com/docs/manual/reference/bson-types/). + +Each YAML file has the following keys: + +- `runOn` Unchanged from Transactions spec tests. +- `database_name` Unchanged from Transactions spec tests. +- `collection_name` Unchanged from Transactions spec tests. +- `data` Unchanged from Transactions spec tests. +- `json_schema` A JSON Schema that should be set on the collection (using `createCollection`) before each test run. +- `encrypted_fields` An encryptedFields option that should be set on the collection (using `createCollection`) before + each test run. +- `key_vault_data` The data that should exist in the key vault collection under test before each test run. +- `tests`: An array of tests that are to be run independently of each other. Each test will have some or all of the + following fields: + - `description`: Unchanged from Transactions spec tests. + - `skipReason`: Unchanged from Transactions spec tests. + - `useMultipleMongoses`: Unchanged from Transactions spec tests. + - `failPoint`: Unchanged from Transactions spec tests. + - `clientOptions`: Optional, parameters to pass to MongoClient(). + - `autoEncryptOpts`: Optional + - `kmsProviders` A dictionary of KMS providers to set on the key vault ("aws" or "local") + - `aws` The AWS KMS provider. An empty object. Drivers MUST fill in AWS credentials (`accessKeyId`, + `secretAccessKey`) from the environment. + - `azure` The Azure KMS provider credentials. An empty object. Drivers MUST fill in Azure credentials + (`tenantId`, `clientId`, and `clientSecret`) from the environment. + - `gcp` The GCP KMS provider credentials. An empty object. Drivers MUST fill in GCP credentials (`email`, + `privateKey`) from the environment. + - `local` or `local:name2` The local KMS provider. + - `key` A 96 byte local key. + - `kmip` The KMIP KMS provider credentials. An empty object. Drivers MUST fill in KMIP credentials (`endpoint`, + and TLS options). + - `schemaMap`: Optional, a map from namespaces to local JSON schemas. + - `keyVaultNamespace`: Optional, a namespace to the key vault collection. Defaults to "keyvault.datakeys". + - `bypassAutoEncryption`: Optional, a boolean to indicate whether or not auto encryption should be bypassed. + Defaults to `false`. + - `encryptedFieldsMap` An optional document. The document maps collection namespace to `EncryptedFields` + documents. + - `operations`: Array of documents, each describing an operation to be executed. Each document has the following + fields: + - `name`: Unchanged from Transactions spec tests. + - `object`: Unchanged from Transactions spec tests.. Defaults to "collection" if omitted. + - `collectionOptions`: Unchanged from Transactions spec tests. + - `command_name`: Unchanged from Transactions spec tests. + - `arguments`: Unchanged from Transactions spec tests. + - `result`: Same as the Transactions spec test format with one addition: if the operation is expected to return an + error, the `result` document may contain an `isTimeoutError` boolean field. If `true`, the test runner MUST + assert that the error represents a timeout due to the use of the `timeoutMS` option. If `false`, the test runner + MUST assert that the error does not represent a timeout. + - `expectations`: Unchanged from Transactions spec tests. + - `outcome`: Unchanged from Transactions spec tests. + +## Credentials + +Test credentials are available in AWS Secrets Manager. See + for more background +on how the secrets are managed. + +Test credentials to KMS are located in "drivers/csfle". + +Test credentials to create environments are available in "drivers/gcpkms" and "drivers/azurekms". + +## Use as integration tests + +Do the following before running spec tests: + +- If available for the platform under test, obtain a [crypt_shared](../client-side-encryption.md#crypt_shared) binary + and place it in a location accessible to the tests. Refer to: + [Using crypt_shared](../client-side-encryption.md#enabling-crypt_shared) +- Start the mongocryptd process. +- Start a mongod process with **server version 4.2.0 or later**. +- Place credentials somewhere in the environment outside of tracked code. (If testing on evergreen, project variables + are a good place). +- Start a KMIP test server on port 5698 by running + [drivers-evergreen-tools/.evergreen/csfle/kms_kmip_server.py](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_kmip_server.py). + +Load each YAML (or JSON) file using a Canonical Extended JSON parser. + +If the test file name matches the regular expression `fle2-Range-.*-Correctness`, drivers MAY skip the test on macOS. +The `fle2-Range` tests are very slow on macOS and do not provide significant additional test coverage. + +Then for each element in `tests`: + +1. If the `skipReason` field is present, skip this test completely. + +2. If the `key_vault_data` field is present: + + 1. Drop the `keyvault.datakeys` collection using writeConcern "majority". + 2. Insert the data specified into the `keyvault.datakeys` with write concern "majority". + +3. Create a MongoClient. + +4. Create a collection object from the MongoClient, using the `database_name` and `collection_name` fields from the YAML + file. Drop the collection with writeConcern "majority". If a `json_schema` is defined in the test, use the + `createCollection` command to explicitly create the collection: + + ```typescript + {"create": , "validator": {"$jsonSchema": }} + ``` + + If `encrypted_fields` is defined in the test, the required collections and index described in + [Create and Drop Collection Helpers](../client-side-encryption.md#queryable-encryption-create-and-drop-collection-helpers) + must be created: + + - Use the `dropCollection` helper with `encrypted_fields` as an option and writeConcern "majority". + - Use the `createCollection` helper with `encrypted_fields` as an option. + +5. If the YAML file contains a `data` array, insert the documents in `data` into the test collection, using writeConcern + "majority". + +6. Create a **new** MongoClient using `clientOptions`. + + 1. If `autoEncryptOpts` includes `aws`, `awsTemporary`, `awsTemporaryNoSessionToken`, `azure`, `gcp`, and/or `kmip` + as a KMS provider, pass in credentials from the environment. + - `awsTemporary`, and `awsTemporaryNoSessionToken` require temporary AWS credentials. These can be retrieved using + the csfle + [set-temp-creds.sh](https://github.com/mongodb-labs/drivers-evergreen-tools/tree/master/.evergreen/csfle) + script. + + - `aws`, `awsTemporary`, and `awsTemporaryNoSessionToken` are mutually exclusive. + + `aws` should be substituted with: + + ```javascript + "aws": { + "accessKeyId": , + "secretAccessKey": + } + ``` + + `awsTemporary` should be substituted with: + + ```javascript + "aws": { + "accessKeyId": , + "secretAccessKey": + "sessionToken": + } + ``` + + `awsTemporaryNoSessionToken` should be substituted with: + + ```javascript + "aws": { + "accessKeyId": , + "secretAccessKey": + } + ``` + + `gcp` should be substituted with: + + ```javascript + "gcp": { + "email": , + "privateKey": , + } + ``` + + `azure` should be substituted with: + + ```javascript + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + } + ``` + + `local` should be substituted with: + + ```javascript + "local": { "key": } + ``` + + `kmip` should be substituted with: + + ```javascript + "kmip": { "endpoint": "localhost:5698" } + ``` + + Configure KMIP TLS connections to use the following options: + + - `tlsCAFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). + This MAY be configured system-wide. + - `tlsCertificateKeyFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem). + + The method of passing TLS options for KMIP TLS connections is driver dependent. + 2. If `autoEncryptOpts` does not include `keyVaultNamespace`, default it to `keyvault.datakeys`. + +7. For each element in `operations`: + + - Enter a "try" block or your programming language's closest equivalent. + + - Create a Database object from the MongoClient, using the `database_name` field at the top level of the test file. + + - Create a Collection object from the Database, using the `collection_name` field at the top level of the test file. + If `collectionOptions` is present create the Collection object with the provided options. Otherwise create the + object with the default options. + + - Execute the named method on the provided `object`, passing the arguments listed. + + - If the driver throws an exception / returns an error while executing this series of operations, store the error + message and server error code. + + - If the result document has an "errorContains" field, verify that the method threw an exception or returned an + error, and that the value of the "errorContains" field matches the error string. "errorContains" is a substring + (case-insensitive) of the actual error message. + + If the result document has an "errorCodeName" field, verify that the method threw a command failed exception or + returned an error, and that the value of the "errorCodeName" field matches the "codeName" in the server error + response. + + If the result document has an "errorLabelsContain" field, verify that the method threw an exception or returned an + error. Verify that all of the error labels in "errorLabelsContain" are present in the error or exception using + the `hasErrorLabel` method. + + If the result document has an "errorLabelsOmit" field, verify that the method threw an exception or returned an + error. Verify that none of the error labels in "errorLabelsOmit" are present in the error or exception using the + `hasErrorLabel` method. + + - If the operation returns a raw command response, eg from `runCommand`, then compare only the fields present in the + expected result document. Otherwise, compare the method's return value to `result` using the same logic as the + CRUD Spec Tests runner. + +8. If the test includes a list of command-started events in `expectations`, compare them to the actual command-started + events using the same logic as the + [Command Monitoring spec legacy test runner](../../command-logging-and-monitoring/tests/README.md). + +9. For each element in `outcome`: + + - If `name` is "collection", create a new MongoClient *without encryption* and verify that the test collection + contains exactly the documents in the `data` array. Ensure this find reads the latest data by using **primary + read preference** with **local read concern** even when the MongoClient is configured with another read + preference or read concern. + +The spec test MUST be run with *and* without auth. + +## Using `crypt_shared` + +On platforms where [crypt_shared](../client-side-encryption.md#crypt_shared) is available, drivers should prefer to test +with the `crypt_shared` library instead of spawning mongocryptd. + +[crypt_shared](../client-side-encryption.md#crypt_shared) is released alongside the server. +[crypt_shared](../client-side-encryption.md#crypt_shared) is only available in versions 6.0 and above. + +mongocryptd is released alongside the server. mongocryptd is available in versions 4.2 and above. + +Drivers MUST run all tests with mongocryptd on at least one platform for all tested server versions. + +Drivers MUST run all tests with [crypt_shared](../client-side-encryption.md#crypt_shared) on at least one platform for +all tested server versions. For server versions < 6.0, drivers MUST test with the latest major release of +[crypt_shared](../client-side-encryption.md#crypt_shared). Using the latest major release of +[crypt_shared](../client-side-encryption.md#crypt_shared) is supported with older server versions. + +Note that some tests assert on mongocryptd-related behaviors (e.g. the `mongocryptdBypassSpawn` test). + +Drivers under test should load the [crypt_shared](../client-side-encryption.md#crypt_shared) library using either the +`cryptSharedLibPath` public API option (as part of the AutoEncryption `extraOptions`), or by setting a special search +path instead. + +Some tests will require *not* using [crypt_shared](../client-side-encryption.md#crypt_shared). For such tests, one +should ensure that `crypt_shared` will not be loaded. Refer to the client-side-encryption documentation for information +on "disabling" `crypt_shared` and setting library search paths. + +> [!NOTE] +> The [crypt_shared](../client-side-encryption.md#crypt_shared) dynamic library can be obtained using the +> [mongodl](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/mongodl.py) Python script +> from [drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/): +> +> ```shell +> $ python3 mongodl.py --component=crypt_shared --version= --out=./crypt_shared/ +> ``` +> +> Other versions of `crypt_shared` are also available. Please use the `--list` option to see versions. + +## Prose Tests + +Tests for the ClientEncryption type are not included as part of the YAML tests. + +In the prose tests LOCAL_MASTERKEY refers to the following base64: + +```text +Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk +``` + +Perform all applicable operations on key vault collections (e.g. inserting an example data key, or running a find +command) with readConcern/writeConcern "majority". + +### 1. Custom Key Material Test + +1. Create a `MongoClient` object (referred to as `client`). + +2. Using `client`, drop the collection `keyvault.datakeys`. + +3. Create a `ClientEncryption` object (referred to as `client_encryption`) with `client` set as the `keyVaultClient`. + +4. Using `client_encryption`, create a data key with a `local` KMS provider and the following custom key material (given + as base64): + + ```text + xPTAjBRG5JiPm+d3fj6XLi2q5DMXUS/f1f+SMAlhhwkhDRL0kr8r9GDLIGTAGlvC+HVjSIgdL+RKwZCvpXSyxTICWSXTUYsWYPyu3IoHbuBZdmw2faM3WhcRIgbMReU5 + ``` + +5. Find the resulting key document in `keyvault.datakeys`, save a copy of the key document, then remove the key document + from the collection. + +6. Replace the `_id` field in the copied key document with a UUID with base64 value `AAAAAAAAAAAAAAAAAAAAAA==` (16 bytes + all equal to `0x00`) and insert the modified key document into `keyvault.datakeys` with majority write concern. + +7. Using `client_encryption`, encrypt the string `"test"` with the modified data key using the + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic` algorithm and assert the resulting value is equal to the following + (given as base64): + + ```text + AQAAAAAAAAAAAAAAAAAAAAACz0ZOLuuhEYi807ZXTdhbqhLaS2/t9wLifJnnNYwiw79d75QYIZ6M/aYC1h9nCzCjZ7pGUpAuNnkUhnIXM3PjrA== + ``` + +### 2. Data Key and Double Encryption + +First, perform the setup. + +1. Create a MongoClient without encryption enabled (referred to as `client`). Enable command monitoring to listen for + command_started events. + +2. Using `client`, drop the collections `keyvault.datakeys` and `db.coll`. + +3. Create the following: + + - A MongoClient configured with auto encryption (referred to as `client_encrypted`) + - A `ClientEncryption` object (referred to as `client_encryption`) + + Configure both objects with the following KMS providers: + + ```javascript + { + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + }, + "gcp": { + "email": , + "privateKey": , + } + "local": { "key": }, + "kmip": { "endpoint": "localhost:5698" } + } + ``` + + Configure KMIP TLS connections to use the following options: + + - `tlsCAFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). + This MAY be configured system-wide. + - `tlsCertificateKeyFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem). + + The method of passing TLS options for KMIP TLS connections is driver dependent. + + Configure both objects with `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure the `MongoClient` with the following `schema_map`: + + ```javascript + { + "db.coll": { + "bsonType": "object", + "properties": { + "encrypted_placeholder": { + "encrypt": { + "keyId": "/placeholder", + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + } + } + } + } + ``` + + Configure `client_encryption` with the `keyVaultClient` of the previously created `client`. + +For each KMS provider (`aws`, `azure`, `gcp`, `local`, and `kmip`), referred to as `provider_name`, run the following +test. + +1. Call `client_encryption.createDataKey()`. + - Set keyAltNames to `["_altname"]`. + + - Set the masterKey document based on `provider_name`. + + For "aws": + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + } + ``` + + For "azure": + + ```javascript + { + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + ``` + + For "gcp": + + ```javascript + { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + } + ``` + + For "kmip": + + ```javascript + {} + ``` + + For "local", do not set a masterKey document. + + - Expect a BSON binary with subtype 4 to be returned, referred to as `datakey_id`. + + - Use `client` to run a `find` on `keyvault.datakeys` by querying with the `_id` set to the `datakey_id`. + + - Expect that exactly one document is returned with the "masterKey.provider" equal to `provider_name`. + + - Check that `client` captured a command_started event for the `insert` command containing a majority writeConcern. +2. Call `client_encryption.encrypt()` with the value `"hello "`, the algorithm + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`, and the `key_id` of `datakey_id`. + - Expect the return value to be a BSON binary subtype 6, referred to as `encrypted`. + - Use `client_encrypted` to insert `{ _id: "", "value": }` into `db.coll`. + - Use `client_encrypted` to run a find querying with `_id` of `""` and expect `value` to be + `"hello "`. +3. Call `client_encryption.encrypt()` with the value `"hello "`, the algorithm + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`, and the `key_alt_name` of `_altname`. + - Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of `encrypted`. +4. Test explicit encrypting an auto encrypted field. + - Use `client_encrypted` to attempt to insert `{ "encrypted_placeholder": }` + - Expect an exception to be thrown, since this is an attempt to auto encrypt an already encrypted value. + +### 3. External Key Vault Test + +Run the following tests twice, parameterized by a boolean `withExternalKeyVault`. + +1. Create a MongoClient without encryption enabled (referred to as `client`). + +2. Using `client`, drop the collections `keyvault.datakeys` and `db.coll`. Insert the document + [external/external-key.json](../external/external-key.json) into `keyvault.datakeys`. + +3. Create the following: + + - A MongoClient configured with auto encryption (referred to as `client_encrypted`) + - A `ClientEncryption` object (referred to as `client_encryption`) + + Configure both objects with the `local` KMS providers as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure both objects with `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure `client_encrypted` to use the schema [external/external-schema.json](../external/external-schema.json) for + `db.coll` by setting a schema map like: `{ "db.coll": }` + + If `withExternalKeyVault == true`, configure both objects with an external key vault client. The external client MUST + connect to the same MongoDB cluster that is being tested against, except it MUST use the username `fake-user` and + password `fake-pwd`. + +4. Use `client_encrypted` to insert the document `{"encrypted": "test"}` into `db.coll`. If + `withExternalKeyVault == true`, expect an authentication exception to be thrown. Otherwise, expect the insert to + succeed. + +5. Use `client_encryption` to explicitly encrypt the string `"test"` with key ID `LOCALAAAAAAAAAAAAAAAAA==` and + deterministic algorithm. If `withExternalKeyVault == true`, expect an authentication exception to be thrown. + Otherwise, expect the insert to succeed. + +### 4. BSON Size Limits and Batch Splitting + +First, perform the setup. + +1. Create a MongoClient without encryption enabled (referred to as `client`). + +2. Using `client`, drop and create the collection `db.coll` configured with the included JSON schema + [limits/limits-schema.json](../limits/limits-schema.json). + +3. Using `client`, drop the collection `keyvault.datakeys`. Insert the document + [limits/limits-key.json](../limits/limits-key.json) + +4. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure with the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + +Using `client_encrypted` perform the following operations: + +1. Insert `{ "_id": "over_2mib_under_16mib", "unencrypted": }`. + + Expect this to succeed since this is still under the `maxBsonObjectSize` limit. + +2. Insert the document [limits/limits-doc.json](../limits/limits-doc.json) concatenated with + `{ "_id": "encryption_exceeds_2mib", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }` Note: + limits-doc.json is a 1005 byte BSON document that encrypts to a ~10,000 byte document. + + Expect this to succeed since after encryption this still is below the normal maximum BSON document size. Note, before + auto encryption this document is under the 2 MiB limit. After encryption it exceeds the 2 MiB limit, but does NOT + exceed the 16 MiB limit. + +3. Bulk insert the following: + + - `{ "_id": "over_2mib_1", "unencrypted": }` + - `{ "_id": "over_2mib_2", "unencrypted": }` + + Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using + [command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md). + +4. Bulk insert the following: + + - The document [limits/limits-doc.json](../limits/limits-doc.json) concatenated with + `{ "_id": "encryption_exceeds_2mib_1", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }` + - The document [limits/limits-doc.json](../limits/limits-doc.json) concatenated with + `{ "_id": "encryption_exceeds_2mib_2", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }` + + Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using + [command logging and monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md). + +5. Insert `{ "_id": "under_16mib", "unencrypted": `. + + Expect this to succeed since this is still (just) under the `maxBsonObjectSize` limit. + +6. Insert the document [limits/limits-doc.json](../limits/limits-doc.json) concatenated with + `{ "_id": "encryption_exceeds_16mib", "unencrypted": < the string "a" repeated (16777216 - 2000) times > }` + + Expect this to fail since encryption results in a document exceeding the `maxBsonObjectSize` limit. + +Optionally, if it is possible to mock the maxWriteBatchSize (i.e. the maximum number of documents in a batch) test that +setting maxWriteBatchSize=1 and inserting the two documents `{ "_id": "a" }, { "_id": "b" }` with `client_encrypted` +splits the operation into two inserts. + +### 5. Views Are Prohibited + +1. Create a MongoClient without encryption enabled (referred to as `client`). + +2. Using `client`, drop and create a view named `db.view` with an empty pipeline. E.g. using the command + `{ "create": "view", "viewOn": "coll" }`. + +3. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure with the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + +4. Using `client_encrypted`, attempt to insert a document into `db.view`. Expect an exception to be thrown containing + the message: "cannot auto encrypt a view". + +### 6. Corpus Test + +The corpus test exhaustively enumerates all ways to encrypt all BSON value types. Note, the test data includes BSON +binary subtype 4 (or standard UUID), which MUST be decoded and encoded as subtype 4. Run the test as follows. + +1. Create a MongoClient without encryption enabled (referred to as `client`). + +2. Using `client`, drop and create the collection `db.coll` configured with the included JSON schema + [corpus/corpus-schema.json](../corpus/corpus-schema.json). + +3. Using `client`, drop the collection `keyvault.datakeys`. Insert the documents + [corpus/corpus-key-local.json](../corpus/corpus-key-local.json), + [corpus/corpus-key-aws.json](../corpus/corpus-key-aws.json), + [corpus/corpus-key-azure.json](../corpus/corpus-key-azure.json), + [corpus/corpus-key-gcp.json](../corpus/corpus-key-gcp.json), and + [corpus/corpus-key-kmip.json](../corpus/corpus-key-kmip.json). + +4. Create the following: + + - A MongoClient configured with auto encryption (referred to as `client_encrypted`) + - A `ClientEncryption` object (referred to as `client_encryption`) + + Configure both objects with `aws`, `azure`, `gcp`, `local`, and `kmip` KMS providers as follows: + + ```javascript + { + "aws": { }, + "azure": { }, + "gcp": { }, + "local": { "key": }, + "kmip": { "endpoint": "localhost:5698" } + } + ``` + + Configure KMIP TLS connections to use the following options: + + - `tlsCAFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). + This MAY be configured system-wide. + - `tlsCertificateKeyFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem). + + The method of passing TLS options for KMIP TLS connections is driver dependent. + + Where LOCAL_MASTERKEY is the following base64: + + ```text + Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + ``` + + Configure both objects with `keyVaultNamespace` set to `keyvault.datakeys`. + +5. Load [corpus/corpus.json](../corpus/corpus.json) to a variable named `corpus`. The corpus contains subdocuments with + the following fields: + + - `kms` is `aws`, `azure`, `gcp`, `local`, or `kmip` + - `type` is a BSON type string + [names coming from here](https://www.mongodb.com/docs/manual/reference/operator/query/type/)) + - `algo` is either `rand` or `det` for random or deterministic encryption + - `method` is either `auto`, for automatic encryption or `explicit` for explicit encryption + - `identifier` is either `id` or `altname` for the key identifier + - `allowed` is a boolean indicating whether the encryption for the given parameters is permitted. + - `value` is the value to be tested. + + Create a new BSON document, named `corpus_copied`. Iterate over each field of `corpus`. + + - If the field name is `_id`, `altname_aws`, `altname_local`, `altname_azure`, `altname_gcp`, or `altname_kmip` copy + the field to `corpus_copied`. + + - If `method` is `auto`, copy the field to `corpus_copied`. + + - If `method` is `explicit`, use `client_encryption` to explicitly encrypt the value. + + - Encrypt with the algorithm described by `algo`. + - If `identifier` is `id` + - If `kms` is `local` set the key_id to the UUID with base64 value `LOCALAAAAAAAAAAAAAAAAA==`. + - If `kms` is `aws` set the key_id to the UUID with base64 value `AWSAAAAAAAAAAAAAAAAAAA==`. + - If `kms` is `azure` set the key_id to the UUID with base64 value `AZUREAAAAAAAAAAAAAAAAA==`. + - If `kms` is `gcp` set the key_id to the UUID with base64 value `GCPAAAAAAAAAAAAAAAAAAA==`. + - If `kms` is `kmip` set the key_id to the UUID with base64 value `KMIPAAAAAAAAAAAAAAAAAA==`. + - If `identifier` is `altname` + - If `kms` is `local` set the key_alt_name to "local". + - If `kms` is `aws` set the key_alt_name to "aws". + - If `kms` is `azure` set the key_alt_name to "azure". + - If `kms` is `gcp` set the key_alt_name to "gcp". + - If `kms` is `kmip` set the key_alt_name to "kmip". + + If `allowed` is true, copy the field and encrypted value to `corpus_copied`. If `allowed` is false. verify that an + exception is thrown. Copy the unencrypted value to to `corpus_copied`. + +6. Using `client_encrypted`, insert `corpus_copied` into `db.coll`. + +7. Using `client_encrypted`, find the inserted document from `db.coll` to a variable named `corpus_decrypted`. Since it + should have been automatically decrypted, assert the document exactly matches `corpus`. + +8. Load [corpus/corpus_encrypted.json](../corpus/corpus-encrypted.json) to a variable named `corpus_encrypted_expected`. + Using `client` find the inserted document from `db.coll` to a variable named `corpus_encrypted_actual`. + + Iterate over each field of `corpus_encrypted_expected` and check the following: + + - If the `algo` is `det`, that the value equals the value of the corresponding field in `corpus_encrypted_actual`. + - If the `algo` is `rand` and `allowed` is true, that the value does not equal the value of the corresponding field + in `corpus_encrypted_actual`. + - If `allowed` is true, decrypt the value with `client_encryption`. Decrypt the value of the corresponding field of + `corpus_encrypted` and validate that they are both equal. + - If `allowed` is false, validate the value exactly equals the value of the corresponding field of `corpus` (neither + was encrypted). + +9. Repeat steps 1-8 with a local JSON schema. I.e. amend step 4 to configure the schema on `client_encrypted` with the + `schema_map` option. + +### 7. Custom Endpoint Test + +#### Setup + +For each test cases, start by creating two `ClientEncryption` objects. Recreate the `ClientEncryption` objects for each +test case. + +Create a `ClientEncryption` object (referred to as `client_encryption`) + +Configure with `keyVaultNamespace` set to `keyvault.datakeys`, and a default MongoClient as the `keyVaultClient`. + +Configure with KMS providers as follows: + +```javascript +{ + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "login.microsoftonline.com:443" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "oauth2.googleapis.com:443" + }, + "kmip" { + "endpoint": "localhost:5698" + } +} +``` + +Create a `ClientEncryption` object (referred to as `client_encryption_invalid`) + +Configure with `keyVaultNamespace` set to `keyvault.datakeys`, and a default MongoClient as the `keyVaultClient`. + +Configure with KMS providers as follows: + +```javascript +{ + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "doesnotexist.invalid:443" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "doesnotexist.invalid:443" + }, + "kmip": { + "endpoint": "doesnotexist.invalid:5698" + } +} +``` + +Configure KMIP TLS connections to use the following options: + +- `tlsCAFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). + This MAY be configured system-wide. +- `tlsCertificateKeyFile` (or equivalent) set to + [drivers-evergreen-tools/.evergreen/x509gen/client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem). + +The method of passing TLS options for KMIP TLS connections is driver dependent. + +#### Test cases + +1. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + +2. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + endpoint: "kms.us-east-1.amazonaws.com" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + +3. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + endpoint: "kms.us-east-1.amazonaws.com:443" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + +4. Call `client_encryption.createDataKey()` with "kmip" as the provider and the following masterKey: + + ```javascript + { + "keyId": "1", + "endpoint": "localhost:12345" + } + ``` + + Expect this to fail with a socket connection error. + +5. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + endpoint: "kms.us-east-2.amazonaws.com" + } + ``` + + Expect this to fail with an exception. + +6. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + endpoint: "doesnotexist.invalid" + } + ``` + + Expect this to fail with a network exception indicating failure to resolve "doesnotexist.invalid". + +7. Call `client_encryption.createDataKey()` with "azure" as the provider and the following masterKey: + + ```javascript + { + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + + Call `client_encryption_invalid.createDataKey()` with the same masterKey. Expect this to fail with a network + exception indicating failure to resolve "doesnotexist.invalid". + +8. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey: + + ```javascript + { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle", + "endpoint": "cloudkms.googleapis.com:443" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + + Call `client_encryption_invalid.createDataKey()` with the same masterKey. Expect this to fail with a network + exception indicating failure to resolve "doesnotexist.invalid". + +9. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey: + + ```javascript + { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle", + "endpoint": "doesnotexist.invalid:443" + } + ``` + + Expect this to fail with an exception with a message containing the string: "Invalid KMS response". + +10. Call `client_encryption.createDataKey()` with "kmip" as the provider and the following masterKey: + + ```javascript + { + "keyId": "1" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + + Call `client_encryption_invalid.createDataKey()` with the same masterKey. Expect this to fail with a network + exception indicating failure to resolve "doesnotexist.invalid". + +11. Call `client_encryption.createDataKey()` with "kmip" as the provider and the following masterKey: + + ```javascript + { + "keyId": "1", + "endpoint": "localhost:5698" + } + ``` + + Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to + validate it works. + +12. Call `client_encryption.createDataKey()` with "kmip" as the provider and the following masterKey: + + ```javascript + { + "keyId": "1", + "endpoint": "doesnotexist.invalid:5698" + } + ``` + + Expect this to fail with a network exception indicating failure to resolve "doesnotexist.invalid". + +### 8. Bypass Spawning mongocryptd + +> [!NOTE] +> CONSIDER: To reduce the chances of tests interfering with each other, drivers MAY use a different port for each test +> in this group, and include it in `--pidfilepath`. The interference may come from the fact that once spawned by a test, +> `mongocryptd` stays up and running for some time. + +#### Via loading shared library + +The following tests that loading [crypt_shared](../client-side-encryption.md#crypt_shared) bypasses spawning +mongocryptd. + +> [!NOTE] +> IMPORTANT: This test requires the [crypt_shared](../client-side-encryption.md#crypt_shared) library be loaded. If the +> [crypt_shared](../client-side-encryption.md#crypt_shared) library is not available, skip the test. + +1. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure the required options. Use the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure `client_encrypted` to use the schema [external/external-schema.json](../external/external-schema.json) for + `db.coll` by setting a schema map like: `{ "db.coll": }` + + Configure the following `extraOptions`: + + ```javascript + { + "mongocryptdURI": "mongodb://localhost:27021/?serverSelectionTimeoutMS=1000", + "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], + "cryptSharedLibPath": "", + "cryptSharedLibRequired": true + } + ``` + + Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port + that should be free. + +2. Use `client_encrypted` to insert the document `{"unencrypted": "test"}` into `db.coll`. Expect this to succeed. + +3. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or whatever was passed via + `--port`) with serverSelectionTimeoutMS=1000. Run a handshake command and ensure it fails with a server selection + timeout. + +> [!NOTE] +> IMPORTANT: If [crypt_shared](../client-side-encryption.md#crypt_shared) is visible to the operating system's library +> search mechanism, the expected server error generated by the `Via mongocryptdBypassSpawn`, `Via bypassAutoEncryption`, +> `Via bypassQueryAnalysis` tests will not appear because libmongocrypt will load the `crypt_shared` library instead of +> consulting mongocryptd. For the following tests, it is required that libmongocrypt *not* load `crypt_shared`. Refer to +> the client-side-encryption document for more information on "disabling" `crypt_shared`. Take into account that once +> loaded, for example, by another test, `crypt_shared` cannot be unloaded and may be used by `MongoClient`, thus making +> the tests misbehave in unexpected ways. + +#### Via mongocryptdBypassSpawn + +The following tests that setting `mongocryptdBypassSpawn=true` really does bypass spawning mongocryptd. + +1. Insert the document [external/external-key.json](../external/external-key.json) into `keyvault.datakeys` with + majority write concern. This step is not required to run this test, and drivers MAY skip it. But if the driver + misbehaves, then not having the encryption fully set up may complicate the process of figuring out what is wrong. + +2. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure the required options. Use the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure `client_encrypted` to use the schema [external/external-schema.json](../external/external-schema.json) for + `db.coll` by setting a schema map like: `{ "db.coll": }` + + Configure the following `extraOptions`: + + ```javascript + { + "mongocryptdBypassSpawn": true + "mongocryptdURI": "mongodb://localhost:27021/?serverSelectionTimeoutMS=1000", + "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"] + } + ``` + + Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port + that should be free. + +3. Use `client_encrypted` to insert the document `{"encrypted": "test"}` into `db.coll`. Expect a server selection error + propagated from the internal MongoClient failing to connect to mongocryptd on port 27021. + +#### Via bypassAutoEncryption + +The following tests that setting `bypassAutoEncryption=true` really does bypass spawning mongocryptd. + +1. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure the required options. Use the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure with `bypassAutoEncryption=true`. + + Configure the following `extraOptions`: + + ```javascript + { + "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"] + } + ``` + + Drivers MAY pass a different value to `--port` if they expect their testing infrastructure to be using port 27021. + Pass a port that should be free. + +2. Use `client_encrypted` to insert the document `{"unencrypted": "test"}` into `db.coll`. Expect this to succeed. + +3. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or whatever was passed via + `--port`) with serverSelectionTimeoutMS=1000. Run a handshake command and ensure it fails with a server selection + timeout. + +#### Via bypassQueryAnalysis + +Repeat the steps from the "Via bypassAutoEncryption" test, replacing "bypassAutoEncryption=true" with +"bypassQueryAnalysis=true". + +### 9. Deadlock Tests + +The following tests only apply to drivers that have implemented a connection pool (see the +[Connection Monitoring and Pooling](../../connection-monitoring-and-pooling/connection-monitoring-and-pooling.md) +specification). + +There are multiple parameterized test cases. Before each test case, perform the setup. + +#### Setup + +Create a `MongoClient` for setup operations named `client_test`. + +Create a `MongoClient` for key vault operations with `maxPoolSize=1` named `client_keyvault`. Capture command started +events. + +Using `client_test`, drop the collections `keyvault.datakeys` and `db.coll`. + +Insert the document [external/external-key.json](../external/external-key.json) into `keyvault.datakeys` with majority +write concern. + +Create a collection `db.coll` configured with a JSON schema +[external/external-schema.json](../external/external-schema.json) as the validator, like so: + +```typescript +{"create": "coll", "validator": {"$jsonSchema": }} +``` + +Create a `ClientEncryption` object, named `client_encryption` configured with: - `keyVaultClient`=`client_test` - +`keyVaultNamespace`="keyvault.datakeys" - `kmsProviders`=`{ "local": { "key": } }` + +Use `client_encryption` to encrypt the value "string0" with `algorithm`="AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" +and `keyAltName`="local". Store the result in a variable named `ciphertext`. + +Proceed to run the test case. + +Each test case configures a `MongoClient` with automatic encryption (named `client_encrypted`). + +Each test must assert the number of unique `MongoClient` objects created. This can be accomplished by capturing +`TopologyOpeningEvent`, or by checking command started events for a client identifier (not possible in all drivers). + +#### Running a test case + +- Create a `MongoClient` named `client_encrypted` configured as follows: + + - Set `AutoEncryptionOpts`: + + - `keyVaultNamespace="keyvault.datakeys"` + - `kmsProviders`=`{ "local": { "key": } }` + - Append `TestCase.AutoEncryptionOpts` (defined below) + + - Capture command started events. + + - Set `maxPoolSize=TestCase.MaxPoolSize` + +- If the testcase sets `AutoEncryptionOpts.bypassAutoEncryption=true`: + + - Use `client_test` to insert `{ "_id": 0, "encrypted": }` into `db.coll`. + +- Otherwise: + + - Use `client_encrypted` to insert `{ "_id": 0, "encrypted": "string0" }`. + +- Use `client_encrypted` to run a `findOne` operation on `db.coll`, with the filter `{ "_id": 0 }`. + +- Expect the result to be `{ "_id": 0, "encrypted": "string0" }`. + +- Check captured events against `TestCase.Expectations`. + +- Check the number of unique `MongoClient` objects created is equal to `TestCase.ExpectedNumberOfClients`. + +#### Case 1 + +- MaxPoolSize: 1 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=false + - keyVaultClient=unset + +- Expectations: + + - Expect `client_encrypted` to have captured four `CommandStartedEvent`: + - a listCollections to "db". + - a find on "keyvault". + - an insert on "db". + - a find on "db" + +- ExpectedNumberOfClients: 2 + +#### Case 2 + +- MaxPoolSize: 1 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=false + - keyVaultClient=client_keyvault + +- Expectations: + + - Expect `client_encrypted` to have captured three `CommandStartedEvent`: + + - a listCollections to "db". + - an insert on "db". + - a find on "db" + + - Expect `client_keyvault` to have captured one `CommandStartedEvent`: + + - a find on "keyvault". + +- ExpectedNumberOfClients: 2 + +#### Case 3 + +- MaxPoolSize: 1 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=true + - keyVaultClient=unset + +- Expectations: + + - Expect `client_encrypted` to have captured three `CommandStartedEvent`: + - a find on "db" + - a find on "keyvault". + +- ExpectedNumberOfClients: 2 + +#### Case 4 + +- MaxPoolSize: 1 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=true + - keyVaultClient=client_keyvault + +- Expectations: + + - Expect `client_encrypted` to have captured two `CommandStartedEvent`: + + - a find on "db" + + - Expect `client_keyvault` to have captured one `CommandStartedEvent`: + + - a find on "keyvault". + +- ExpectedNumberOfClients: 1 + +#### Case 5 + +Drivers that do not support an unlimited maximum pool size MUST skip this test. + +- MaxPoolSize: 0 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=false + - keyVaultClient=unset + +- Expectations: + + - Expect `client_encrypted` to have captured five `CommandStartedEvent`: + - a listCollections to "db". + - a listCollections to "keyvault". + - a find on "keyvault". + - an insert on "db". + - a find on "db" + +- ExpectedNumberOfClients: 1 + +#### Case 6 + +Drivers that do not support an unlimited maximum pool size MUST skip this test. + +- MaxPoolSize: 0 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=false + - keyVaultClient=client_keyvault + +- Expectations: + + - Expect `client_encrypted` to have captured three `CommandStartedEvent`: + + - a listCollections to "db". + - an insert on "db". + - a find on "db" + + - Expect `client_keyvault` to have captured one `CommandStartedEvent`: + + - a find on "keyvault". + +- ExpectedNumberOfClients: 1 + +#### Case 7 + +Drivers that do not support an unlimited maximum pool size MUST skip this test. + +- MaxPoolSize: 0 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=true + - keyVaultClient=unset + +- Expectations: + + - Expect `client_encrypted` to have captured three `CommandStartedEvent`: + - a find on "db" + - a find on "keyvault". + +- ExpectedNumberOfClients: 1 + +#### Case 8 + +Drivers that do not support an unlimited maximum pool size MUST skip this test. + +- MaxPoolSize: 0 + +- AutoEncryptionOpts: + + - bypassAutoEncryption=true + - keyVaultClient=client_keyvault + +- Expectations: + + - Expect `client_encrypted` to have captured two `CommandStartedEvent`: + + - a find on "db" + + - Expect `client_keyvault` to have captured one `CommandStartedEvent`: + + - a find on "keyvault". + +- ExpectedNumberOfClients: 1 + +### 10. KMS TLS Tests + +The following tests that connections to KMS servers with TLS verify peer certificates. + +The two tests below make use of mock KMS servers which can be run on Evergreen using +[the mock KMS server script](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_http_server.py). +Drivers can set up their local Python environment for the mock KMS server by running +[the virtualenv activation script](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/activate-kmstlsvenv.sh). + +To start two mock KMS servers, one on port 9000 with +[ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA file and +[expired.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/expired.pem) as a +cert file, and one on port 9001 with +[ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA file and +[wrong-host.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/wrong-host.pem) +as a cert file, run the following commands from the `.evergreen/csfle` directory: + +```shell +. ./activate_venv.sh +python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 9000 & +python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 9001 & +``` + +#### Setup + +For both tests, do the following: + +1. Start a `mongod` process with **server version 4.2.0 or later**. +2. Create a `MongoClient` for key vault operations. +3. Create a `ClientEncryption` object (referred to as `client_encryption`) with `keyVaultNamespace` set to + `keyvault.datakeys`. + +#### Invalid KMS Certificate + +1. Start a mock KMS server on port 9000 with + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA + file and + [expired.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/expired.pem) + as a cert file. + +2. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + "region": "us-east-1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "endpoint": "127.0.0.1:9000", + } + ``` + + Expect this to fail with an exception with a message referencing an expired certificate. This message will be + language dependent. In Python, this message is "certificate verify failed: certificate has expired". In Go, this + message is "certificate has expired or is not yet valid". If the language of implementation has a single, generic + error message for all certificate validation errors, drivers may inspect other fields of the error to verify its + meaning. + +#### Invalid Hostname in KMS Certificate + +1. Start a mock KMS server on port 9001 with + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA + file and + [wrong-host.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/wrong-host.pem) + as a cert file. + +2. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: + + ```javascript + { + "region": "us-east-1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "endpoint": "127.0.0.1:9001", + } + ``` + + Expect this to fail with an exception with a message referencing an incorrect or unexpected host. This message will + be language dependent. In Python, this message is "certificate verify failed: IP address mismatch, certificate is + not valid for '127.0.0.1'". In Go, this message is "cannot validate certificate for 127.0.0.1 because it doesn't + contain any IP SANs". If the language of implementation has a single, generic error message for all certificate + validation errors, drivers may inspect other fields of the error to verify its meaning. + +### 11. KMS TLS Options Tests + +#### Setup + +Start a `mongod` process with **server version 4.2.0 or later**. + +Four mock KMS server processes must be running: + +1. The mock + [KMS HTTP server](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_http_server.py). + + Run on port 9000 with + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA + file and + [expired.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/expired.pem) + as a cert file. + + Example: + + ```shell + python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 9000 + ``` + +2. The mock + [KMS HTTP server](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_http_server.py). + + Run on port 9001 with + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA + file and + [wrong-host.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/wrong-host.pem) + as a cert file. + + Example: + + ```shell + python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 9001 + ``` + +3. The mock + [KMS HTTP server](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_http_server.py). + + Run on port 9002 with + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem) as a CA + file and + [server.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/server.pem) as + a cert file. + + Run with the `--require_client_cert` option. + + Example: + + ```shell + python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 9002 --require_client_cert + ``` + +4. The mock + [KMS KMIP server](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/kms_kmip_server.py). + +Create the following `ClientEncryption` objects. + +Configure each with `keyVaultNamespace` set to `keyvault.datakeys`, and a default MongoClient as the `keyVaultClient`. + +1. Create a `ClientEncryption` object named `client_encryption_no_client_cert` with the following KMS providers: + + ```javascript + { + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9002" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9002" + }, + "kmip" { + "endpoint": "127.0.0.1:5698" + } + } + ``` + + Add TLS options for the `aws`, `azure`, `gcp`, and `kmip` providers to use the following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + +2. Create a `ClientEncryption` object named `client_encryption_with_tls` with the following KMS providers: + + ```javascript + { + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9002" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9002" + }, + "kmip" { + "endpoint": "127.0.0.1:5698" + } + } + ``` + + Add TLS options for the `aws`, `azure`, `gcp`, and `kmip` providers to use the following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + - `tlsCertificateKeyFile` (or equivalent) set to + [client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem) + +3. Create a `ClientEncryption` object named `client_encryption_expired` with the following KMS providers: + + ```javascript + { + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9000" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9000" + }, + "kmip" { + "endpoint": "127.0.0.1:9000" + } + } + ``` + + Add TLS options for the `aws`, `azure`, `gcp`, and `kmip` providers to use the following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + +4. Create a `ClientEncryption` object named `client_encryption_invalid_hostname` with the following KMS providers: + + ```javascript + { + "aws": { + "accessKeyId": , + "secretAccessKey": + }, + "azure": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9001" + }, + "gcp": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9001" + }, + "kmip" { + "endpoint": "127.0.0.1:9001" + } + } + ``` + + Add TLS options for the `aws`, `azure`, `gcp`, and `kmip` providers to use the following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + +5. Create a `ClientEncryption` object named `client_encryption_with_names` with the following KMS providers: + + ```javascript + { + "aws:no_client_cert": { + "accessKeyId": , + "secretAccessKey": + }, + "azure:no_client_cert": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9002" + }, + "gcp:no_client_cert": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9002" + }, + "kmip:no_client_cert": { + "endpoint": "127.0.0.1:5698" + }, + "aws:with_tls": { + "accessKeyId": , + "secretAccessKey": + }, + "azure:with_tls": { + "tenantId": , + "clientId": , + "clientSecret": , + "identityPlatformEndpoint": "127.0.0.1:9002" + }, + "gcp:with_tls": { + "email": , + "privateKey": , + "endpoint": "127.0.0.1:9002" + }, + "kmip:with_tls": { + "endpoint": "127.0.0.1:5698" + } + } + ``` + + Support for named KMS providers requires libmongocrypt 1.9.0. + + Add TLS options for the `aws:no_client_cert`, `azure:no_client_cert`, `gcp:no_client_cert`, and `kmip:no_client_cert` + providers to use the following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + + Add TLS options for the `aws:with_tls`, `azure:with_tls`, `gcp:with_tls`, and `kmip:with_tls` providers to use the + following options: + + - `tlsCAFile` (or equivalent) set to + [ca.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem). This MAY + be configured system-wide. + - `tlsCertificateKeyFile` (or equivalent) set to + [client.pem](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem) + +#### Case 1: AWS + +Call `client_encryption_no_client_cert.createDataKey()` with "aws" as the provider and the following masterKey: + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + endpoint: "127.0.0.1:9002" +} +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_tls.createDataKey()` with "aws" as the provider and the following masterKey: + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + endpoint: "127.0.0.1:9002" +} +``` + +Expect an error from libmongocrypt with a message containing the string: "parse error". This implies TLS handshake +succeeded. + +Call `client_encryption_expired.createDataKey()` with "aws" as the provider and the following masterKey: + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + endpoint: "127.0.0.1:9000" +} +``` + +Expect an error indicating TLS handshake failed due to an expired certificate. + +Call `client_encryption_invalid_hostname.createDataKey()` with "aws" as the provider and the following masterKey: + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + endpoint: "127.0.0.1:9001" +} +``` + +Expect an error indicating TLS handshake failed due to an invalid hostname. + +#### Case 2: Azure + +Call `client_encryption_no_client_cert.createDataKey()` with "azure" as the provider and the following masterKey: + +```javascript +{ 'keyVaultEndpoint': 'doesnotexist.invalid', 'keyName': 'foo' } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_tls.createDataKey()` with "azure" as the provider and the same masterKey. + +Expect an error from libmongocrypt with a message containing the string: "HTTP status=404". This implies TLS handshake +succeeded. + +Call `client_encryption_expired.createDataKey()` with "azure" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an expired certificate. + +Call `client_encryption_invalid_hostname.createDataKey()` with "azure" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an invalid hostname. + +#### Case 3: GCP + +Call `client_encryption_no_client_cert.createDataKey()` with "gcp" as the provider and the following masterKey: + +```javascript +{ 'projectId': 'foo', 'location': 'bar', 'keyRing': 'baz', 'keyName': 'foo' } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_tls.createDataKey()` with "gcp" as the provider and the same masterKey. + +Expect an error from libmongocrypt with a message containing the string: "HTTP status=404". This implies TLS handshake +succeeded. + +Call `client_encryption_expired.createDataKey()` with "gcp" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an expired certificate. + +Call `client_encryption_invalid_hostname.createDataKey()` with "gcp" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an invalid hostname. + +#### Case 4: KMIP + +Call `client_encryption_no_client_cert.createDataKey()` with "kmip" as the provider and the following masterKey: + +```javascript +{ } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_tls.createDataKey()` with "kmip" as the provider and the same masterKey. + +Expect success. + +Call `client_encryption_expired.createDataKey()` with "kmip" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an expired certificate. + +Call `client_encryption_invalid_hostname.createDataKey()` with "kmip" as the provider and the same masterKey. + +Expect an error indicating TLS handshake failed due to an invalid hostname. + +#### Case 5: `tlsDisableOCSPEndpointCheck` is permitted + +This test does not apply if the driver does not support the the option `tlsDisableOCSPEndpointCheck`. + +Create a `ClientEncryption` object with the following KMS providers: + +> ```javascript +> { +> "aws": { +> "accessKeyId": "foo", +> "secretAccessKey": "bar" +> } +> } +> ``` +> +> Add TLS options for the `aws` with the following options: +> +> - `tlsDisableOCSPEndpointCheck` (or equivalent) set to `true`. + +Expect no error on construction. + +#### Case 6: named KMS providers apply TLS options + +##### Named AWS + +Call `client_encryption_with_names.createDataKey()` with "aws:no_client_cert" as the provider and the following +masterKey. + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + endpoint: "127.0.0.1:9002" +} +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_names.createDataKey()` with "aws:with_tls" as the provider and the same masterKey. + +Expect an error from libmongocrypt with a message containing the string: "parse error". This implies TLS handshake +succeeded. + +##### Named Azure + +Call `client_encryption_with_names.createDataKey()` with "azure:no_client_cert" as the provider and the following +masterKey: + +```javascript +{ 'keyVaultEndpoint': 'doesnotexist.invalid', 'keyName': 'foo' } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_names.createDataKey()` with "azure:with_tls" as the provider and the same masterKey. + +Expect an error from libmongocrypt with a message containing the string: "HTTP status=404". This implies TLS handshake +succeeded. + +##### Named GCP + +Call `client_encryption_with_names.createDataKey()` with "gcp:no_client_cert" as the provider and the following +masterKey: + +```javascript +{ 'projectId': 'foo', 'location': 'bar', 'keyRing': 'baz', 'keyName': 'foo' } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_names.createDataKey()` with "gcp:with_tls" as the provider and the same masterKey. + +Expect an error from libmongocrypt with a message containing the string: "HTTP status=404". This implies TLS handshake +succeeded. + +##### Named KMIP + +Call `client_encryption_with_names.createDataKey()` with "kmip:no_client_cert" as the provider and the following +masterKey: + +```javascript +{ } +``` + +Expect an error indicating TLS handshake failed. + +Call `client_encryption_with_names.createDataKey()` with "kmip:with_tls" as the provider and the same masterKey. + +Expect success. + +### 12. Explicit Encryption + +The Explicit Encryption tests require MongoDB server 7.0+. The tests must not run against a standalone. + +> [!NOTE] +> MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2. +> libmongocrypt 1.8.0 is configured to use the QEv2 protocol. + +Before running each of the following test cases, perform the following Test Setup. + +#### Test Setup + +Load the file +[encryptedFields.json](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/etc/data/encryptedFields.json) +as `encryptedFields`. + +Load the file +[key1-document.json](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/etc/data/keys/key1-document.json) +as `key1Document`. + +Read the `"_id"` field of `key1Document` as `key1ID`. + +Drop and create the collection `db.explicit_encryption` using `encryptedFields` as an option. See +[FLE 2 CreateCollection() and Collection.Drop()](../client-side-encryption.md#create-collection-helper). + +Drop and create the collection `keyvault.datakeys`. + +Insert `key1Document` in `keyvault.datakeys` with majority write concern. + +Create a MongoClient named `keyVaultClient`. + +Create a ClientEncryption object named `clientEncryption` with these options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, +} +``` + +Create a MongoClient named `encryptedClient` with these `AutoEncryptionOpts`: + +```typescript +class AutoEncryptionOpts { + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, + bypassQueryAnalysis: true, +} +``` + +#### Case 1: can insert encrypted indexed and find + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + contentionFactor: 0, +} +``` + +Store the result in `insertPayload`. + +Use `encryptedClient` to insert the document `{ "encryptedIndexed": }` into `db.explicit_encryption`. + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + queryType: "equality", + contentionFactor: 0, +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter +`{ "encryptedIndexed": }`. + +Assert one document is returned containing the field `{ "encryptedIndexed": "encrypted indexed value" }`. + +#### Case 2: can insert encrypted indexed and find with non-zero contention + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + contentionFactor: 10, +} +``` + +Store the result in `insertPayload`. + +Use `encryptedClient` to insert the document `{ "encryptedIndexed": }` into `db.explicit_encryption`. + +Repeat the above steps 10 times to insert 10 total documents. The `insertPayload` must be regenerated each iteration. + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + queryType: "equality", + contentionFactor: 0, +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter +`{ "encryptedIndexed": }`. + +Assert less than 10 documents are returned. 0 documents may be returned. Assert each returned document contains the +field `{ "encryptedIndexed": "encrypted indexed value" }`. + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + queryType: "equality", + contentionFactor: 10, +} +``` + +Store the result in `findPayload2`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter +`{ "encryptedIndexed": }`. + +Assert 10 documents are returned. Assert each returned document contains the field +`{ "encryptedIndexed": "encrypted indexed value" }`. + +#### Case 3: can insert encrypted unindexed + +Use `clientEncryption` to encrypt the value "encrypted unindexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Unindexed", +} +``` + +Store the result in `insertPayload`. + +Use `encryptedClient` to insert the document `{ "_id": 1, "encryptedUnindexed": }` into +`db.explicit_encryption`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter +`{ "_id": 1 }`. + +Assert one document is returned containing the field `{ "encryptedUnindexed": "encrypted unindexed value" }`. + +#### Case 4: can roundtrip encrypted indexed + +Use `clientEncryption` to encrypt the value "encrypted indexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Indexed", + contentionFactor: 0, +} +``` + +Store the result in `payload`. + +Use `clientEncryption` to decrypt `payload`. Assert the returned value equals "encrypted indexed value". + +#### Case 5: can roundtrip encrypted unindexed + +Use `clientEncryption` to encrypt the value "encrypted unindexed value" with these `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Unindexed", +} +``` + +Store the result in `payload`. + +Use `clientEncryption` to decrypt `payload`. Assert the returned value equals "encrypted unindexed value". + +### 13. Unique Index on keyAltNames + +The following setup must occur before running each of the following test cases. + +#### Setup + +1. Create a `MongoClient` object (referred to as `client`). + +2. Using `client`, drop the collection `keyvault.datakeys`. + +3. Using `client`, create a unique index on `keyAltNames` with a partial index filter for only documents where + `keyAltNames` exists using writeConcern "majority". + + The command should be equivalent to: + + ```typescript + db.runCommand( + { + createIndexes: "datakeys", + indexes: [ + { + name: "keyAltNames_1", + key: { "keyAltNames": 1 }, + unique: true, + partialFilterExpression: { keyAltNames: { $exists: true } } + } + ], + writeConcern: { w: "majority" } + } + ) + ``` + +4. Create a `ClientEncryption` object (referred to as `client_encryption`) with `client` set as the `keyVaultClient`. + +5. Using `client_encryption`, create a data key with a `local` KMS provider and the keyAltName "def". + +#### Case 1: createDataKey() + +1. Use `client_encryption` to create a new local data key with a keyAltName "abc" and assert the operation does not + fail. +2. Repeat Step 1 and assert the operation fails due to a duplicate key server error (error code 11000). +3. Use `client_encryption` to create a new local data key with a keyAltName "def" and assert the operation fails due to + a duplicate key server error (error code 11000). + +#### Case 2: addKeyAltName() + +1. Use `client_encryption` to create a new local data key and assert the operation does not fail. +2. Use `client_encryption` to add a keyAltName "abc" to the key created in Step 1 and assert the operation does not + fail. +3. Repeat Step 2, assert the operation does not fail, and assert the returned key document contains the keyAltName "abc" + added in Step 2. +4. Use `client_encryption` to add a keyAltName "def" to the key created in Step 1 and assert the operation fails due to + a duplicate key server error (error code 11000). +5. Use `client_encryption` to add a keyAltName "def" to the existing key, assert the operation does not fail, and assert + the returned key document contains the keyAltName "def" added during Setup. + +### 14. Decryption Events + +Before running each of the following test cases, perform the following Test Setup. + +#### Test Setup + +Create a MongoClient named `setupClient`. + +Drop and create the collection `db.decryption_events`. + +Create a ClientEncryption object named `clientEncryption` with these options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, +} +``` + +Create a data key with the "local" KMS provider. Storing the result in a variable named `keyID`. + +Use `clientEncryption` to encrypt the string "hello" with the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId: , + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", +} +``` + +Store the result in a variable named `ciphertext`. + +Copy `ciphertext` into a variable named `malformedCiphertext`. Change the last byte to a different value. This will +produce an invalid HMAC tag. + +Create a MongoClient named `encryptedClient` with these `AutoEncryptionOpts`: + +```typescript +class AutoEncryptionOpts { + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, +} +``` + +Configure `encryptedClient` with "retryReads=false". Register a listener for CommandSucceeded events on +`encryptedClient`. The listener must store the most recent `CommandSucceededEvent` reply for the "aggregate" command. +The listener must store the most recent `CommandFailedEvent` error for the "aggregate" command. + +#### Case 1: Command Error + +Use `setupClient` to configure the following failpoint: + +```typescript +{ + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 123, + "failCommands": [ + "aggregate" + ] + } +} +``` + +Use `encryptedClient` to run an aggregate on `db.decryption_events`. + +Expect an exception to be thrown from the command error. Expect a `CommandFailedEvent`. + +#### Case 2: Network Error + +Use `setupClient` to configure the following failpoint: + +```typescript +{ + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "errorCode": 123, + "closeConnection": true, + "failCommands": [ + "aggregate" + ] + } +} +``` + +Use `encryptedClient` to run an aggregate on `db.decryption_events`. + +Expect an exception to be thrown from the network error. Expect a `CommandFailedEvent`. + +#### Case 3: Decrypt Error + +Use `encryptedClient` to insert the document `{ "encrypted": }` into `db.decryption_events`. + +Use `encryptedClient` to run an aggregate on `db.decryption_events` with an empty pipeline. + +Expect an exception to be thrown from the decryption error. Expect a `CommandSucceededEvent`. Expect the +`CommandSucceededEvent.reply` to contain BSON binary for the field `cursor.firstBatch.encrypted`. + +#### Case 4: Decrypt Success + +Use `encryptedClient` to insert the document `{ "encrypted": }` into `db.decryption_events`. + +Use `encryptedClient` to run an aggregate on `db.decryption_events` with an empty pipeline. + +Expect no exception. Expect a `CommandSucceededEvent`. Expect the `CommandSucceededEvent.reply` to contain BSON binary +for the field `cursor.firstBatch.encrypted`. + +### 15. On-demand AWS Credentials + +These tests require valid AWS credentials. Refer: +[Automatic AWS Credentials](../client-side-encryption.md#automatic-credentials). + +For these cases, create a [ClientEncryption](../client-side-encryption.md#clientencryption) object $C$ with the +following options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "aws": {} }, +} +``` + +#### Case 1: Failure + +Do not run this test case in an environment where AWS credentials are available (e.g. via environment variables or a +metadata URL). (Refer: [Obtaining credentials for AWS](../../auth/auth.md#obtaining-credentials)) + +Attempt to create a datakey with $C$ using the `"aws"` KMS provider. Expect this to fail due to a lack of KMS provider +credentials. + +#### Case 2: Success + +For this test case, the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` must be defined and set to +a valid set of AWS credentials. + +Use the client encryption to create a datakey using the `"aws"` KMS provider. This should successfully load and use the +AWS credentials that were defined in the environment. + +### 16. Rewrap + +#### Case 1: Rewrap with separate ClientEncryption + +When the following test case requests setting `masterKey`, use the following values based on the KMS provider: + +For "aws": + +```javascript +{ + "region": "us-east-1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" +} +``` + +For "azure": + +```javascript +{ + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" +} +``` + +For "gcp": + +```javascript +{ + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" +} +``` + +For "kmip": + +```javascript +{} +``` + +For "local", do not set a masterKey document. + +Run the following test case for each pair of KMS providers (referred to as `srcProvider` and `dstProvider`). Include +pairs where `srcProvider` equals `dstProvider`. + +1. Drop the collection `keyvault.datakeys`. + +2. Create a `ClientEncryption` object named `clientEncryption1` with these options: + + ```typescript + class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: , + } + ``` + +3. Call `clientEncryption1.createDataKey` with `srcProvider` and these options: + + ```typescript + class DataKeyOpts { + masterKey: , + } + ``` + + Store the return value in `keyID`. + +4. Call `clientEncryption1.encrypt` with the value "test" and these options: + + ```typescript + class EncryptOpts { + keyId : keyID, + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", + } + ``` + + Store the return value in `ciphertext`. + +5. Create a `ClientEncryption` object named `clientEncryption2` with these options: + + ```typescript + class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: , + } + ``` + +6. Call `clientEncryption2.rewrapManyDataKey` with an empty `filter` and these options: + + ```typescript + class RewrapManyDataKeyOpts { + provider: dstProvider, + masterKey: , + } + ``` + + Assert that the returned `RewrapManyDataKeyResult.bulkWriteResult.modifiedCount` is 1. + +7. Call `clientEncryption1.decrypt` with the `ciphertext`. Assert the return value is "test". + +8. Call `clientEncryption2.decrypt` with the `ciphertext`. Assert the return value is "test". + +#### Case 2: RewrapManyDataKeyOpts.provider is not optional + +Drivers MAY chose not to implement this prose test if their implementation of `RewrapManyDataKeyOpts` makes it +impossible by design to omit `RewrapManyDataKeyOpts.provider` when `RewrapManyDataKeyOpts.masterKey` is set. + +1. Create a `ClientEncryption` object named `clientEncryption` with these options: + + ```typescript + class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: , + } + ``` + +2. Call `clientEncryption.rewrapManyDataKey` with an empty `filter` and these options: + + ```typescript + class RewrapManyDataKeyOpts { + masterKey: {} + } + ``` + + Assert that `clientEncryption.rewrapManyDataKey` raises a client error indicating that the required + `RewrapManyDataKeyOpts.provider` field is missing. + +### 17. On-demand GCP Credentials + +Refer: [Automatic GCP Credentials](../client-side-encryption.md#obtaining-gcp-credentials). + +For these cases, create a [ClientEncryption](../client-side-encryption.md#clientencryption) object $C$ with the +following options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "gcp": {} }, +} +``` + +#### Case 1: Failure + +Do not run this test case in an environment with a GCP service account is attached (e.g. any +[GCE equivalent runtime](https://google.aip.dev/auth/4115)). This may be run in an AWS EC2 instance. + +Attempt to create a datakey with $C$ using the `"gcp"` KMS provider and following `DataKeyOpts`: + +```typescript +class DataKeyOpts { + masterKey: { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle", + } +} +``` + +Expect the attempt to obtain `"gcp"` credentials from the environment to fail. + +#### Case 2: Success + +This test case must run in a Google Compute Engine (GCE) Virtual Machine with a service account attached. See +[drivers-evergreen-tools/.evergreen/csfle/gcpkms](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/gcpkms) +for scripts to create a GCE instance for testing. The Evergreen task SHOULD set a `batchtime` of 14 days to reduce how +often this test case runs. + +Attempt to create a datakey with $C$ using the `"gcp"` KMS provider and following `DataKeyOpts`: + +```typescript +class DataKeyOpts { + masterKey: { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle", + } +} +``` + +This should successfully load and use the GCP credentials of the service account attached to the virtual machine. + +Expect the key to be successfully created. + +### 18. Azure IMDS Credentials + +Refer: [Automatic Azure Credentials](../client-side-encryption.md#obtaining-an-access-token-for-azure-key-vault) + +The test cases for IMDS communication are specially designed to not require an Azure environment, while still exercising +the core of the functionality. The design of these test cases encourages an implementation to separate the concerns of +IMDS communication from the logic of KMS key manipulation. The purpose of these test cases is to ensure drivers will +behave appropriately regardless of the behavior of the IMDS server. + +For these IMDS credentials tests, a simple stand-in IMDS-imitating HTTP server is available in drivers-evergreen-tools, +at `.evergreen/csfle/fake_azure.py`. `fake_azure.py` is a very simple `bottle.py` application. For the easiest use, it +is recommended to execute it through `bottle.py` (which is a sibling file in the same directory): + +```shell +python .evergreen/csfle/bottle.py fake_azure:imds +``` + +This will run the `imds` Bottle application defined in the `fake_azure` Python module. `bottle.py` accepts additional +command line arguments to control the bind host and TCP port (use `--help` for more information). + +For each test case, follow the process for obtaining the token as outlined in the +[automatic Azure credentials section](../client-side-encryption.md#obtaining-an-access-token-for-azure-key-vault) with +the following changes: + +1. Instead of the standard IMDS TCP endpoint of `169.254.169.254:80`, communicate with the running `fake_azure` HTTP + server. +2. For each test case, the behavior of the server may be controlled by attaching an additional HTTP header to the sent + request: `X-MongoDB-HTTP-TestParams`. + +#### Case 1: Success + +Do not set an `X-MongoDB-HTTP-TestParams` header. + +Upon receiving a response from `fake_azure`, the driver must decode the following information: + +1. HTTP status will be `200 Okay`. +2. The HTTP body will be a valid JSON string. +3. The access token will be the string `"magic-cookie"`. +4. The expiry duration of the token will be seventy seconds. +5. The token will have a resource of `"https://vault.azure.net"` + +#### Case 2: Empty JSON + +This case addresses a server returning valid JSON with invalid content. + +Set `X-MongoDB-HTTP-TestParams` to `case=empty-json`. + +Upon receiving a response: + +1. HTTP status will be `200 Okay` +2. The HTTP body will be a valid JSON string. +3. There will be no access token, expiry duration, or resource. + +The test case should ensure that this error condition is handled gracefully. + +#### Case 3: Bad JSON + +This case addresses a server returning malformed JSON. + +Set `X-MongoDB-HTTP-TestParams` to `case=bad-json`. + +Upon receiving a response: + +1. HTTP status will be `200 Okay` +2. The response body will contain a malformed JSON string. + +The test case should ensure that this error condition is handled gracefully. + +#### Case 4: HTTP 404 + +This case addresses a server returning a "Not Found" response. This is documented to occur spuriously within an Azure +environment. + +Set `X-MongoDB-HTTP-TestParams` to `case=404`. + +Upon receiving a response: + +1. HTTP status will be `404 Not Found`. +2. The response body is unspecified. + +The test case should ensure that this error condition is handled gracefully. + +#### Case 5: HTTP 500 + +This case addresses an IMDS server reporting an internal error. This is documented to occur spuriously within an Azure +environment. + +Set `X-MongoDB-HTTP-TestParams` to `case=500`. + +Upon receiving a response: + +1. HTTP status code will be `500`. +2. The response body is unspecified. + +The test case should ensure that this error condition is handled gracefully. + +#### Case 6: Slow Response + +This case addresses an IMDS server responding very slowly. Drivers should not halt the application waiting on a peer to +communicate. + +Set `X-MongoDB-HTTP-TestParams` to `case=slow`. + +The HTTP response from the `fake_azure` server will take at least 1000 seconds to complete. The request should fail with +a timeout. + +### 19. Azure IMDS Credentials Integration Test + +Refer: [Automatic Azure Credentials](../client-side-encryption.md#obtaining-an-access-token-for-azure-key-vault) + +For these cases, create a [ClientEncryption](../client-side-encryption.md#clientencryption) object $C$ with the +following options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "azure": {} }, +} +``` + +#### Case 1: Failure + +Do not run this test case in an Azure environment with an attached identity. This may be run in an AWS EC2 instance. + +Attempt to create a datakey with $C$ using the `"azure"` KMS provider and following `DataKeyOpts`: + +```typescript +class DataKeyOpts { + masterKey: { + "keyVaultEndpoint": "https://keyvault-drivers-2411.vault.azure.net/keys/", + "keyName": "KEY-NAME", + } +} +``` + +Expect the attempt to obtain `"azure"` credentials from the environment to fail. + +#### Case 2: Success + +This test case must run in an Azure environment with an attached identity. See +[drivers-evergreen-tools/.evergreen/csfle/azurekms](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/csfle/azurekms) +for scripts to create a Azure instance for testing. The Evergreen task SHOULD set a `batchtime` of 14 days to reduce how +often this test case runs. + +Attempt to create a datakey with $C$ using the `"azure"` KMS provider and following `DataKeyOpts`: + +```typescript +class DataKeyOpts { + masterKey: { + "keyVaultEndpoint": "https://keyvault-drivers-2411.vault.azure.net/keys/", + "keyName": "KEY-NAME", + } +} +``` + +This should successfully load and use the Azure credentials of the service account attached to the virtual machine. + +Expect the key to be successfully created. + +### 20. Bypass creating mongocryptd client when shared library is loaded + +> [!NOTE] +> IMPORTANT: If [crypt_shared](../client-side-encryption.md#crypt_shared) is not visible to the operating system's +> library search mechanism, this test should be skipped. + +The following tests that a mongocryptd client is not created when shared library is in-use. + +1. Start a new thread (referred to as `listenerThread`) + +2. On `listenerThread`, create a TcpListener on 127.0.0.1 endpoint and port 27021. Start the listener and wait for + establishing connections. If any connection is established, then signal about this to the main thread. + + Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port + that should be free. + +3. Create a MongoClient configured with auto encryption (referred to as `client_encrypted`) + + Configure the required options. Use the `local` KMS provider as follows: + + ```javascript + { "local": { "key": } } + ``` + + Configure with the `keyVaultNamespace` set to `keyvault.datakeys`. + + Configure the following `extraOptions`: + + ```javascript + { + "mongocryptdURI": "mongodb://localhost:27021/?serverSelectionTimeoutMS=1000" + } + ``` + +4. Use `client_encrypted` to insert the document `{"unencrypted": "test"}` into `db.coll`. + +5. Expect no signal from `listenerThread`. + +### 21. Automatic Data Encryption Keys + +The Automatic Data Encryption Keys tests require MongoDB server 7.0+. The tests must not run against a standalone. + +> [!NOTE] +> MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2. +> libmongocrypt 1.8.0 is configured to use the QEv2 protocol. + +For each of the following test cases, assume `DB` is a valid open database handle, and assume a +[ClientEncryption](../client-side-encryption.md#clientencryption) object `CE` created using the following options: + +```javascript +clientEncryptionOptions: { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { + local: { key: base64Decode(LOCAL_MASTERKEY) }, + aws: { + accessKeyId: , + secretAccessKey: + }, + }, +} +``` + +Run each test case with each of these KMS providers: `aws`, `local`. The KMS provider name is referred to as +`kmsProvider`. When testing `aws`, use the following as the `masterKey` option: + +```javascript +{ + region: "us-east-1", + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" +} +``` + +When testing `local`, set `masterKey` to `null`. + +#### Case 1: Simple Creation and Validation + +This test is the most basic to verify that +[CreateEncryptedCollection](../client-side-encryption.md#create-encrypted-collection-helper) created a collection with +queryable encryption enabled. It verifies that the server rejects an attempt to insert plaintext in an encrypted fields. + +1. Create a new create-collection options $Opts$ including the following: + + ```typescript + { + encryptedFields: { + fields: [{ + path: "ssn", + bsonType: "string", + keyId: null + }] + } + } + ``` + +2. Invoke $CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)$ to obtain a new collection + $Coll$. Expect success. + +3. Attempt to insert the following document into `Coll`: + + ```typescript + { + ssn: "123-45-6789" + } + ``` + +4. Expect an error from the insert operation that indicates that the document failed validation. This error indicates + that the server expects to receive an encrypted field for `ssn`, but we tried to insert a plaintext field via a + client that is unaware of the encryption requirements. + +#### Case 2: Missing `encryptedFields` + +The [CreateEncryptedCollection](../client-side-encryption.md#create-encrypted-collection-helper) helper should not +create a regular collection if there are no `encryptedFields` for the collection being created. Instead, it should +generate an error indicated that the `encryptedFields` option is missing. + +1. Create a new empty create-collection options $Opts$. (i.e. it must not contain any `encryptedFields` options.) +2. Invoke $CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)$. +3. Expect the invocation to fail with an error indicating that `encryptedFields` is not defined for the collection, and + expect that no collection was created within the database. It would be *incorrect* for + [CreateEncryptedCollection](../client-side-encryption.md#create-encrypted-collection-helper) to create a regular + collection without queryable encryption enabled. + +#### Case 3: Invalid `keyId` + +The [CreateEncryptedCollection](../client-side-encryption.md#create-encrypted-collection-helper) helper only inspects +`encryptedFields.fields` for `keyId` of `null`. +[CreateEncryptedCollection](../client-side-encryption.md#create-encrypted-collection-helper) should forward all other +data as-is, even if it would be malformed. The server should generate an error when attempting to create a collection +with such invalid settings. + +> [!NOTE] +> This test is not required if the type system of the driver has a compile-time check that fields' `keyId`s are of the +> correct type. + +1. Create a new create-collection options $Opts$ including the following: + + ```typescript + { + encryptedFields: { + fields: [{ + path: "ssn", + bsonType: "string", + keyId: false, + }] + } + } + ``` + +2. Invoke $CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)$. + +3. Expect an error from the server indicating a validation error at `create.encryptedFields.fields.keyId`, which must be + a UUID and not a boolean value. + +#### Case 4: Insert encrypted value + +This test is continuation of the case 1 and provides a way to complete inserting with encrypted value. + +1. Create a new create-collection options $Opts$ including the following: + + ```typescript + { + encryptedFields: { + fields: [{ + path: "ssn", + bsonType: "string", + keyId: null + }] + } + } + ``` + +2. Invoke $CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)$ to obtain a new collection + $Coll$ and data key $key1$. Expect success. + +3. Use $CE$ to explicitly encrypt the string "123-45-6789" using algorithm $Unindexed$ and data key $key1$. Refer result + as $encryptedPayload$. + +4. Attempt to insert the following document into `Coll`: + + ```typescript + { + ssn: + } + ``` + + Expect success. + +### 22. Range Explicit Encryption + +The Range Explicit Encryption tests utilize Queryable Encryption (QE) range protocol V2 and require MongoDB server +8.0.0-rc14+ for [SERVER-91889](https://jira.mongodb.org/browse/SERVER-91889) and libmongocrypt 1.11.0+ for +[MONGOCRYPT-705](https://jira.mongodb.org/browse/MONGOCRYPT-705). The tests must not run against a standalone. + +Each of the following test cases must pass for each of the supported types (`DecimalNoPrecision`, `DecimalPrecision`, +`DoublePrecision`, `DoubleNoPrecision`, `Date`, `Int`, and `Long`), unless it is stated the type should be skipped. + +Tests for `DecimalNoPrecision` must only run against a replica set. `DecimalNoPrecision` queries are expected to take a +long time and may exceed the default mongos timeout. + +Before running each of the following test cases, perform the following Test Setup. + +#### Test Setup + +Load the file for the specific data type being tested `range-encryptedFields-.json`. For example, for `Int` load +[range-encryptedFields-Int.json](https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/etc/data/range-encryptedFields-Int.json) +as `encryptedFields`. + +Load the file +[key1-document.json](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/etc/data/keys/key1-document.json) +as `key1Document`. + +Read the `"_id"` field of `key1Document` as `key1ID`. + +Drop and create the collection `db.explicit_encryption` using `encryptedFields` as an option. See +[FLE 2 CreateCollection() and Collection.Drop()](../client-side-encryption.md#create-collection-helper). + +Drop and create the collection `keyvault.datakeys`. + +Insert `key1Document` in `keyvault.datakeys` with majority write concern. + +Create a MongoClient named `keyVaultClient`. + +Create a ClientEncryption object named `clientEncryption` with these options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, +} +``` + +Create a MongoClient named `encryptedClient` with these `AutoEncryptionOpts`: + +```typescript +class AutoEncryptionOpts { + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": } }, + bypassQueryAnalysis: true, +} +``` + +The remaining tasks require setting `RangeOpts`. [Test Setup: RangeOpts](#test-setup-rangeopts) lists the values to use +for `RangeOpts` for each of the supported data types. + +Use `clientEncryption` to encrypt these values: 0, 6, 30, and 200. Ensure the type matches that of the encrypted field. +For example, if the encrypted field is `encryptedDoubleNoPrecision` encrypt the value 6.0. + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Use `encryptedClient` to insert the following documents into `db.explicit_encryption`: + +```javascript +{ "_id": 0, "encrypted": } +{ "_id": 1, "encrypted": } +{ "_id": 2, "encrypted": } +{ "_id": 3, "encrypted": } +``` + +#### Test Setup: RangeOpts + +This section lists the values to use for `RangeOpts` for each of the supported data types, since each data type requires +a different `RangeOpts`. + +Each test listed in the cases below must pass for all supported data types unless it is stated the type should be +skipped. + +1. DecimalNoPrecision + + ```typescript + class RangeOpts { + trimFactor: 1, + sparsity: 1, + } + ``` + +2. DecimalPrecision + + ```typescript + class RangeOpts { + min: { "$numberDecimal": "0" }, + max: { "$numberDecimal": "200" }, + trimFactor: 1, + sparsity: 1, + precision: 2, + } + ``` + +3. DoubleNoPrecision + + ```typescript + class RangeOpts { + trimFactor: 1 + sparsity: 1, + } + ``` + +4. DoublePrecision + + ```typescript + class RangeOpts { + min: { "$numberDouble": "0" }, + max: { "$numberDouble": "200" }, + trimFactor: 1, + sparsity: 1, + precision: 2, + } + ``` + +5. Date + + ```typescript + class RangeOpts { + min: {"$date": { "$numberLong": "0" } } , + max: {"$date": { "$numberLong": "200" } }, + trimFactor: 1, + sparsity: 1, + } + ``` + +6. Int + + ```typescript + class RangeOpts { + min: {"$numberInt": "0" } , + max: {"$numberInt": "200" }, + trimFactor: 1, + sparsity: 1, + } + ``` + +7. Long + + ```typescript + class RangeOpts { + min: {"$numberLong": "0" } , + max: {"$numberLong": "200" }, + trimFactor: 1, + sparsity: 1, + } + ``` + +#### Case 1: can decrypt a payload + +Use `clientEncryption.encrypt()` to encrypt the value 6. Ensure the type matches that of the encrypted field. For +example, if the encrypted field is `encryptedLong` encrypt a BSON int64 type, not a BSON int32 type. + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Store the result in `insertPayload`. + +Use `clientEncryption` to decrypt `insertPayload`. Assert the returned value equals 6 and has the expected type. + +> [!NOTE] +> The type returned by `clientEncryption.decrypt()` may differ from the input type to `clientEncryption.encrypt()` +> depending on how the driver unmarshals BSON numerics to language native types. Example: a driver may unmarshal a BSON +> int64 to a numeric type that does not distinguish between int64 and int32. + +#### Case 2: can find encrypted range and return the maximum + +Use `clientEncryption.encryptExpression()` to encrypt this query: + +```javascript +// Convert 6 and 200 to the encrypted field type +{ "$and": [ { "encrypted": { "$gte": 6 } }, { "encrypted": { "$lte": 200 } } ] } +``` + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + queryType: "range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter `findPayload` +and sort the results by `_id`. + +Assert the following three documents are returned: + +```javascript +// Convert 6, 30, and 200 to the encrypted field type +{ "_id": 1, "encrypted": 6 } +{ "_id": 2, "encrypted": 30 } +{ "_id": 3, "encrypted": 200 } +``` + +#### Case 3: can find encrypted range and return the minimum + +Use `clientEncryption.encryptExpression()` to encrypt this query: + +```javascript +// Convert 0 and 6 to the encrypted field type +{ "$and": [ { "encrypted": { "$gte": 0 } }, { "encrypted": { "$lte": 6 } } ] } +``` + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + queryType: "range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter `findPayload` +and sort the results by `_id`. + +Assert the following two documents are returned: + +```javascript +// Convert 0 and 6 to the encrypted field type +{ "_id": 0, "encrypted": 0 } +{ "_id": 1, "encrypted": 6 } +``` + +#### Case 4: can find encrypted range with an open range query + +Use `clientEncryption.encryptExpression()` to encrypt this query: + +```javascript +// Convert 30 to the encrypted field type +{ "$and": [ { "encrypted": { "$gt": 30 } } ] } +``` + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + queryType: "range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter `findPayload` +and sort the results by `_id`. + +Assert the following document is returned: + +```javascript +// Convert 200 to the encrypted field type +{ "_id": 3, "encrypted": 200 } +``` + +#### Case 5: can run an aggregation expression inside $expr + +Use `clientEncryption.encryptExpression()` to encrypt this query: + +```javascript +// Convert 30 to the encrypted field type +{ "$and": [ { "$lt": [ "$encrypted", 30 ] } ] } } +``` + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + queryType: "range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Store the result in `findPayload`. + +Use `encryptedClient` to run a "find" operation on the `db.explicit_encryption` collection with the filter +`{ "$expr": }` and sort the results by `_id`. + +Assert the following two documents are returned: + +```javascript +// Convert 0 and 6 to the encrypted field type +{ "_id": 0, "encrypted": 0 } +{ "_id": 1, "encrypted": 6 } +``` + +#### Case 6: encrypting a document greater than the maximum errors + +This test case should be skipped if the encrypted field is `encryptedDoubleNoPrecision` or +`encryptedDecimalNoPrecision`. + +Use `clientEncryption.encrypt()` to encrypt the value 201. Ensure the type matches that of the encrypted field. + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Assert that an error was raised because 201 is greater than the maximum value in `RangeOpts`. + +#### Case 7: encrypting a value of a different type errors + +This test case should be skipped if the encrypted field is `encryptedDoubleNoPrecision` or +`encryptedDecimalNoPrecision`. + +Use `clientEncryption.encrypt()` to encrypt the value 6 with a type that does not match that of the encrypted field. + +If the encrypted field is `encryptedInt` use a BSON double type. Otherwise, use a BSON int32 type. + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Ensure that `RangeOpts` corresponds to the type of the encrypted field (i.e. expected type) and not that of the value +being passed to `clientEncryption.encrypt()`. + +Assert that an error was raised. + +#### Case 8: setting precision errors if the type is not double or Decimal128 + +This test case should be skipped if the encrypted field is `encryptedDoublePrecision`, `encryptedDoubleNoPrecision`, +`encryptedDecimalPrecision`, or `encryptedDecimalNoPrecision`. + +Use `clientEncryption.encrypt()` to encrypt the value 6. Ensure the type matches that of the encrypted field. + +Add `{ precision: 2 }` to the encrypted field's `RangeOpts` (see: [Test Setup: RangeOpts](#test-setup-rangeopts)). + +Encrypt using the following `EncryptOpts`: + +```typescript +class EncryptOpts { + keyId : , + algorithm: "Range", + contentionFactor: 0, + rangeOpts: , +} +``` + +Assert that an error was raised. + +### 23. Range Explicit Encryption applies defaults + +This test requires libmongocrypt with changes in +[14ccd9ce](https://github.com/mongodb/libmongocrypt/commit/14ccd9ce8a030158aec07f63e8139d34b95d88e6) +([MONGOCRYPT-698](https://jira.mongodb.org/browse/MONGOCRYPT-698)). + +#### Test Setup + +Create a MongoClient named `keyVaultClient`. + +Create a ClientEncryption object named `clientEncryption` with these options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: keyVaultClient, + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "local": { "key": "" } }, +} +``` + +Create a key with `clientEncryption.createDataKey`. Store the returned key ID in a variable named `keyId`. + +Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options: + +```typescript +class EncryptOpts { + keyId : keyId, + algorithm: "Range", + contentionFactor: 0, + rangeOpts: RangeOpts { + min: 0, + max: 1000 + } +} +``` + +Store the result in a variable named `payload_defaults`. + +#### Case 1: Uses libmongocrypt defaults + +Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options: + +```typescript +class EncryptOpts { + keyId : keyId, + algorithm: "Range", + contentionFactor: 0, + rangeOpts: RangeOpts { + min: 0, + max: 1000, + sparsity: 2, + trimFactor: 6 + } +} +``` + +Assert the returned payload size equals the size of `payload_defaults`. + +> [!NOTE] +> Do not compare the payload contents. The payloads include random data. The `trimFactor` and `sparsity` directly affect +> the payload size. + +#### Case 2: Accepts `trimFactor` 0 + +Call `clientEncryption.encrypt` to encrypt the int32 value `123` with these options: + +```typescript +class EncryptOpts { + keyId : keyId, + algorithm: "Range", + contentionFactor: 0, + rangeOpts: RangeOpts { + min: 0, + max: 1000, + trimFactor: 0 + } +} +``` + +Assert the returned payload size is greater than the size of `payload_defaults`. + +> [!NOTE] +> Do not compare the payload contents. The payloads include random data. The `trimFactor` and `sparsity` directly affect +> the payload size. + +### 24. KMS Retry Tests + +The following tests that certain AWS, Azure, and GCP KMS operations are retried on transient errors. + +This test uses a mock server with configurable failpoints to simulate network failures. To start the server: + +```shell +python -u kms_failpoint_server.py --port 9003 +``` + +See the [TLS tests](#10-kms-tls-tests) for running the mock server on Evergreen. See +[the mock server implementation](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/4ba50d373652b6fb39239745664637e33e2b01e6/.evergreen/csfle/kms_failpoint_server.py) +and the +[C driver tests](https://github.com/mongodb/mongo-c-driver/blob/d934cd5de55af65220816e4fd01ce3f9c0ef1cd4/src/libmongoc/tests/test-mongoc-client-side-encryption.c#L6295) +for how to configure failpoints. + +#### Setup + +1. Start a `mongod` process with **server version 4.2.0 or later**. +2. Start the failpoint KMS server with: `python -u kms_failpoint_server.py --port 9003`. +3. Create a `MongoClient` for key vault operations. +4. Create a `ClientEncryption` object (referred to as `client_encryption`) with `keyVaultNamespace` set to + `keyvault.datakeys`. + +The failpoint server is configured using HTTP requests. Example request to simulate a network failure: + +`curl -X POST https://localhost:9003/set_failpoint/network -d '{"count": 1}' --cacert drivers-evergreen-tools/.evergreen/x509gen/ca.pem` + +To simulate an HTTP failure, replace `network` with `http`. + +When the following test cases request setting `masterKey`, use the following values based on the KMS provider: + +For "aws": + +```javascript +{ + "region": "foo", + "key": "bar", + "endpoint": "127.0.0.1:9003", +} +``` + +For "azure": + +```javascript +{ + "keyVaultEndpoint": "127.0.0.1:9003", + "keyName": "foo", +} +``` + +For "gcp": + +```javascript +{ + "projectId": "foo", + "location": "bar", + "keyRing": "baz", + "keyName": "qux", + "endpoint": "127.0.0.1:9003" +} +``` + +#### Case 1: createDataKey and encrypt with TCP retry + +1. Configure the mock server to simulate one network failure. +2. Call `client_encryption.createDataKey()` with "aws" as the provider. Expect this to succeed. Store the returned key + ID in a variable named `keyId`. +3. Configure the mock server to simulate another network failure. +4. Call `clientEncryption.encrypt` with the following `EncryptOpts` to encrypt the int32 value `123` with the newly + created key: + ```typescript + class EncryptOpts { + keyId : , + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", + } + ``` + Expect this to succeed. + +Repeat this test with the `azure` and `gcp` masterKeys. + +#### Case 2: createDataKey and encrypt with HTTP retry + +1. Configure the mock server to simulate one HTTP failure. +2. Call `client_encryption.createDataKey()` with "aws" as the provider. Expect this to succeed. Store the returned key + ID in a variable named `keyId`. +3. Configure the mock server to simulate another HTTP failure. +4. Call `clientEncryption.encrypt` with the following `EncryptOpts` to encrypt the int32 value `123` with the newly + created key: + ```typescript + class EncryptOpts { + keyId : , + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", + } + ``` + Expect this to succeed. + +Repeat this test with the `azure` and `gcp` masterKeys. + +#### Case 3: createDataKey fails after too many retries + +1. Configure the mock server to simulate four network failures. +2. Call `client_encryption.createDataKey()` with "aws" as the provider. Expect this to fail. + +Repeat this test with the `azure` and `gcp` masterKeys. + +### 25. Test $lookup + +All tests require libmongocrypt 1.13.0, server 7.0+, and must be skipped on standalone. Tests define more constraints. + +The syntax `` is used to refer to the content of the corresponding file in `../etc/data/lookup`. + +#### Setup + +Create an encrypted MongoClient named `encryptedClient` configured with: + +```python +AutoEncryptionOpts( + keyVaultNamespace="db.keyvault", + kmsProviders={"local": { "key": "" }} +) +``` + +Use `encryptedClient` to drop `db.keyvault`. Insert `` into `db.keyvault` with majority write concern. + +Use `encryptedClient` to drop and create the following collections: + +- `db.csfle` with options: `{ "validator": { "$jsonSchema": ""}}`. +- `db.csfle2` with options: `{ "validator": { "$jsonSchema": ""}}`. +- `db.qe` with options: `{ "encryptedFields": ""}`. +- `db.qe2` with options: `{ "encryptedFields": ""}`. +- `db.no_schema` with no options. +- `db.no_schema2` with no options. + +Create an unencrypted MongoClient named `unencryptedClient`. + +Insert documents with `encryptedClient`: + +- `{"csfle": "csfle"}` into `db.csfle` + - Use `unencryptedClient` to retrieve it. Assert the `csfle` field is BSON binary. +- `{"csfle2": "csfle2"}` into `db.csfle2` + - Use `unencryptedClient` to retrieve it. Assert the `csfle2` field is BSON binary. +- `{"qe": "qe"}` into `db.qe` + - Use `unencryptedClient` to retrieve it. Assert the `qe` field is BSON binary. +- `{"qe2": "qe2"}` into `db.qe2` + - Use `unencryptedClient` to retrieve it. Assert the `qe2` field is BSON binary. +- `{"no_schema": "no_schema"}` into `db.no_schema` +- `{"no_schema2": "no_schema2"}` into `db.no_schema2` + +#### Case 1: `db.csfle` joins `db.no_schema` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.csfle` with the following pipeline: + +```json +[ + {"$match" : {"csfle" : "csfle"}}, + { + "$lookup" : { + "from" : "no_schema", + "as" : "matched", + "pipeline" : [ {"$match" : {"no_schema" : "no_schema"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect one document to be returned matching: `{"csfle" : "csfle", "matched" : [ {"no_schema" : "no_schema"} ]}`. + +#### Case 2: `db.qe` joins `db.no_schema` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.qe` with the following pipeline: + +```json +[ + {"$match" : {"qe" : "qe"}}, + { + "$lookup" : { + "from" : "no_schema", + "as" : "matched", + "pipeline" : + [ {"$match" : {"no_schema" : "no_schema"}}, {"$project" : {"_id" : 0, "__safeContent__" : 0}} ] + } + }, + {"$project" : {"_id" : 0, "__safeContent__" : 0}} +] +``` + +Expect one document to be returned matching: `{"qe" : "qe", "matched" : [ {"no_schema" : "no_schema"} ]}`. + +#### Case 3: `db.no_schema` joins `db.csfle` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.no_schema` with the following pipeline: + +```json +[ + {"$match" : {"no_schema" : "no_schema"}}, + { + "$lookup" : { + "from" : "csfle", + "as" : "matched", + "pipeline" : [ {"$match" : {"csfle" : "csfle"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect one document to be returned matching: `{"no_schema" : "no_schema", "matched" : [ {"csfle" : "csfle"} ]}`. + +#### Case 4: `db.no_schema` joins `db.qe` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.no_schema` with the following pipeline: + +```json +[ + {"$match" : {"no_schema" : "no_schema"}}, + { + "$lookup" : { + "from" : "qe", + "as" : "matched", + "pipeline" : [ {"$match" : {"qe" : "qe"}}, {"$project" : {"_id" : 0, "__safeContent__" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect one document to be returned matching: `{"no_schema" : "no_schema", "matched" : [ {"qe" : "qe"} ]}`. + +#### Case 5: `db.csfle` joins `db.csfle2` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.csfle` with the following pipeline: + +```json +[ + {"$match" : {"csfle" : "csfle"}}, + { + "$lookup" : { + "from" : "csfle2", + "as" : "matched", + "pipeline" : [ {"$match" : {"csfle2" : "csfle2"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect one document to be returned matching: `{"csfle" : "csfle", "matched" : [ {"csfle2" : "csfle2"} ]}`. + +#### Case 6: `db.qe` joins `db.qe2` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.qe` with the following pipeline: + +```json +[ + {"$match" : {"qe" : "qe"}}, + { + "$lookup" : { + "from" : "qe2", + "as" : "matched", + "pipeline" : [ {"$match" : {"qe2" : "qe2"}}, {"$project" : {"_id" : 0, "__safeContent__" : 0}} ] + } + }, + {"$project" : {"_id" : 0, "__safeContent__" : 0}} +] +``` + +Expect one document to be returned matching: `{"qe" : "qe", "matched" : [ {"qe2" : "qe2"} ]}`. + +#### Case 7: `db.no_schema` joins `db.no_schema2` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.no_schema` with the following pipeline: + +```json +[ + {"$match" : {"no_schema" : "no_schema"}}, + { + "$lookup" : { + "from" : "no_schema2", + "as" : "matched", + "pipeline" : [ {"$match" : {"no_schema2" : "no_schema2"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect one document to be returned matching: +`{"no_schema" : "no_schema", "matched" : [ {"no_schema2" : "no_schema2"} ]}`. + +#### Case 8: `db.csfle` joins `db.qe` + +Test requires server 8.1+ and mongocryptd/crypt_shared 8.1+. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.csfle` with the following pipeline: + +```json +[ + {"$match" : {"csfle" : "qe"}}, + { + "$lookup" : { + "from" : "qe", + "as" : "matched", + "pipeline" : [ {"$match" : {"qe" : "qe"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect an exception to be thrown with a message containing the substring `not supported`. + +#### Case 9: test error with \<8.1 + +This case requires mongocryptd/crypt_shared \<8.1. + +Recreate `encryptedClient` with the same `AutoEncryptionOpts` as the setup. (Recreating prevents schema caching from +impacting the test). + +Run an aggregate operation on `db.csfle` with the following pipeline: + +```json +[ + {"$match" : {"csfle" : "csfle"}}, + { + "$lookup" : { + "from" : "no_schema", + "as" : "matched", + "pipeline" : [ {"$match" : {"no_schema" : "no_schema"}}, {"$project" : {"_id" : 0}} ] + } + }, + {"$project" : {"_id" : 0}} +] +``` + +Expect an exception to be thrown with a message containing the substring `Upgrade`. + +### 26. Custom AWS Credentials + +These tests require valid AWS credentials for the remote KMS provider via the secrets manager (FLE_AWS_KEY and +FLE_AWS_SECRET). These tests MUST NOT run inside an AWS environment that has the same credentials set in order to +properly ensure the tests would fail using on-demand credentials. + +#### Case 1: ClientEncryption with `credentialProviders` and incorrect `kmsProviders` + +Create a MongoClient named `setupClient`. + +Create a [ClientEncryption](../client-side-encryption.md#clientencryption) object with the following options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "aws": { "accessKeyId": , "secretAccessKey": } }, + credentialProviders: { "aws": } +} +``` + +Assert that an error is thrown. + +#### Case 2: ClientEncryption with `credentialProviders` works + +Create a MongoClient named `setupClient`. + +Create a [ClientEncryption](../client-side-encryption.md#clientencryption) object with the following options: + +```typescript +class ClientEncryptionOpts { + keyVaultClient: , + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "aws": {} }, + credentialProviders: { "aws": } +} +``` + +Use the client encryption to create a datakey using the "aws" KMS provider. This should successfully load and use the +AWS credentials that were provided by the secrets manager for the remote provider. Assert the datakey was created and +that the custom credential provider was called at least once. + +An example of this in Node.js: + +```typescript +import { ClientEncryption, MongoClient } from 'mongodb'; + +let calledCount = 0; +const masterKey = { + region: '', + key: '' +}; +const keyVaultClient = new MongoClient(process.env.MONGODB_URI); +const options = { + keyVaultNamespace: 'keyvault.datakeys', + kmsProviders: { aws: {} }, + credentialProviders: { + aws: async () => { + calledCount++; + return { + accessKeyId: process.env.FLE_AWS_KEY, + secretAccessKey: process.env.FLE_AWS_SECRET + }; + } + } +}; +const clientEncryption = new ClientEncryption(keyVaultClient, options); +const dk = await clientEncryption.createDataKey('aws', { masterKey }); +expect(dk).to.be.a(Binary); +expect(calledCount).to.be.greaterThan(0); +``` + +#### Case 3: `AutoEncryptionOpts` with `credentialProviders` and incorrect `kmsProviders` + +Create a `MongoClient` object with the following options: + +```typescript +class AutoEncryptionOpts { + autoEncryption: { + keyVaultNamespace: "keyvault.datakeys", + kmsProviders: { "aws": { "accessKeyId": , "secretAccessKey": } }, + credentialProviders: { "aws": } + } +} +``` + +Assert that an error is thrown. diff --git a/src/test/spec/json/client-side-encryption/README.rst b/src/test/spec/json/client-side-encryption/README.rst deleted file mode 100644 index ea98dc57c..000000000 --- a/src/test/spec/json/client-side-encryption/README.rst +++ /dev/null @@ -1,2994 +0,0 @@ -============================ -Client Side Encryption Tests -============================ - -.. contents:: - ----- - -Introduction -============ - -This document describes the format of the driver spec tests included in the -JSON and YAML files included in the ``legacy`` sub-directory. Tests in the -``unified`` directory are written using the `Unified Test Format -<../../unified-test-format/unified-test-format.rst>`_. - -The ``timeoutMS.yml``/``timeoutMS.json`` files in this directory contain tests -for the ``timeoutMS`` option and its application to the client-side encryption -feature. Drivers MUST only run these tests after implementing the -`Client Side Operations Timeout -<../client-side-operations-timeout/client-side-operations-timeout.rst>`__ -specification. - -Additional prose tests, that are not represented in the spec tests, are described -and MUST be implemented by all drivers. - -Spec Test Format -================ - -The spec tests format is an extension of `transactions spec tests `_ with some additions: - -- A ``json_schema`` to set on the collection used for operations. - -- An ``encrypted_fields`` to set on the collection used for operations. - -- A ``key_vault_data`` of data that should be inserted in the key vault collection before each test. - -- Introduction ``autoEncryptOpts`` to `clientOptions` - -- Addition of `$db` to command in `command_started_event` - -- Addition of `$$type` to command_started_event and outcome. - -The semantics of `$$type` is that any actual value matching one of the types indicated by either a BSON type string -or an array of BSON type strings is considered a match. - -For example, the following matches a command_started_event for an insert of a document where `random` must be of type ``binData``:: - - - command_started_event: - command: - insert: *collection_name - documents: - - { random: { $$type: "binData" } } - ordered: true - command_name: insert - -The following matches a command_started_event for an insert of a document where ``random`` must be of type -``binData`` or ``string``:: - - - command_started_event: - command: - insert: *collection_name - documents: - - { random: { $$type: ["binData", "string"] } } - ordered: true - command_name: insert - -The values of `$$type` correspond to `these documented string representations of BSON types `_. - - -Each YAML file has the following keys: - -.. |txn| replace:: Unchanged from Transactions spec tests. - -- ``runOn`` |txn| - -- ``database_name`` |txn| - -- ``collection_name`` |txn| - -- ``data`` |txn| - -- ``json_schema`` A JSON Schema that should be set on the collection (using ``createCollection``) before each test run. - -- ``encrypted_fields`` An encryptedFields option that should be set on the collection (using ``createCollection``) before each test run. - -- ``key_vault_data`` The data that should exist in the key vault collection under test before each test run. - -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some or all of the following fields: - - - ``description``: |txn| - - - ``skipReason``: |txn| - - - ``useMultipleMongoses``: |txn| - - - ``failPoint``: |txn| - - - ``clientOptions``: Optional, parameters to pass to MongoClient(). - - - ``autoEncryptOpts``: Optional - - - ``kmsProviders`` A dictionary of KMS providers to set on the key vault ("aws" or "local") - - - ``aws`` The AWS KMS provider. An empty object. Drivers MUST fill in AWS credentials (`accessKeyId`, `secretAccessKey`) from the environment. - - - ``azure`` The Azure KMS provider credentials. An empty object. Drivers MUST fill in Azure credentials (`tenantId`, `clientId`, and `clientSecret`) from the environment. - - - ``gcp`` The GCP KMS provider credentials. An empty object. Drivers MUST fill in GCP credentials (`email`, `privateKey`) from the environment. - - - ``local`` The local KMS provider. - - - ``key`` A 96 byte local key. - - - ``kmip`` The KMIP KMS provider credentials. An empty object. Drivers MUST fill in KMIP credentials (`endpoint`, and TLS options). - - - ``schemaMap``: Optional, a map from namespaces to local JSON schemas. - - - ``keyVaultNamespace``: Optional, a namespace to the key vault collection. Defaults to "keyvault.datakeys". - - - ``bypassAutoEncryption``: Optional, a boolean to indicate whether or not auto encryption should be bypassed. Defaults to ``false``. - - - ``encryptedFieldsMap`` An optional document. The document maps collection namespace to ``EncryptedFields`` documents. - - - ``operations``: Array of documents, each describing an operation to be - executed. Each document has the following fields: - - - ``name``: |txn| - - - ``object``: |txn|. Defaults to "collection" if omitted. - - - ``collectionOptions``: |txn| - - - ``command_name``: |txn| - - - ``arguments``: |txn| - - - ``result``: Same as the Transactions spec test format with one addition: if the operation is expected to return - an error, the ``result`` document may contain an ``isTimeoutError`` boolean field. If ``true``, the test runner - MUST assert that the error represents a timeout due to the use of the ``timeoutMS`` option. If ``false``, the - test runner MUST assert that the error does not represent a timeout. - - - ``expectations``: |txn| - - - ``outcome``: |txn| - - - -Use as integration tests -======================== - -Do the following before running spec tests: - -- If available for the platform under test, obtain a crypt_shared_ binary and place it - in a location accessible to the tests. Refer to: `Using crypt_shared`_ -- Start the mongocryptd process. -- Start a mongod process with **server version 4.1.9 or later**. -- Place credentials to an AWS IAM user (access key ID + secret access key) somewhere in the environment outside of tracked code. (If testing on evergreen, project variables are a good place). -- Start a KMIP test server on port 5698 by running `drivers-evergreen-tools/.evergreen/csfle/kms_kmip_server.py `_. - -.. _crypt_shared: ../client-side-encryption.rst#crypt_shared - -Load each YAML (or JSON) file using a Canonical Extended JSON parser. - -If the test file name matches the regular expression ``fle2\-Range\-.*\-Correctness``, drivers MAY skip the test on macOS. The ``fle2-Range`` tests are very slow on macOS and do not provide significant additional test coverage. - -Then for each element in ``tests``: - -#. If the ``skipReason`` field is present, skip this test completely. -#. If the ``key_vault_data`` field is present: - - #. Drop the ``keyvault.datakeys`` collection using writeConcern "majority". - #. Insert the data specified into the ``keyvault.datakeys`` with write concern "majority". - -#. Create a MongoClient. - -#. Create a collection object from the MongoClient, using the ``database_name`` - and ``collection_name`` fields from the YAML file. Drop the collection - with writeConcern "majority". If a ``json_schema`` is defined in the test, - use the ``createCollection`` command to explicitly create the collection: - - .. code:: typescript - - {"create": , "validator": {"$jsonSchema": }} - - If ``encrypted_fields`` is defined in the test, the required collections and index described in `Create and Drop Collection Helpers `_ must be created: - - - Use the ``dropCollection`` helper with ``encrypted_fields`` as an option and writeConcern "majority". - - Use the ``createCollection`` helper with ``encrypted_fields`` as an option. - -#. If the YAML file contains a ``data`` array, insert the documents in ``data`` - into the test collection, using writeConcern "majority". - -#. Create a **new** MongoClient using ``clientOptions``. - - #. If ``autoEncryptOpts`` includes ``aws``, ``awsTemporary``, ``awsTemporaryNoSessionToken``, - ``azure``, ``gcp``, and/or ``kmip`` as a KMS provider, pass in credentials from the environment. - - - ``awsTemporary``, and ``awsTemporaryNoSessionToken`` require temporary - AWS credentials. These can be retrieved using the csfle `set-temp-creds.sh - `_ - script. - - - ``aws``, ``awsTemporary``, and ``awsTemporaryNoSessionToken`` are - mutually exclusive. - - ``aws`` should be substituted with: - - .. code:: javascript - - "aws": { - "accessKeyId": , - "secretAccessKey": - } - - ``awsTemporary`` should be substituted with: - - .. code:: javascript - - "aws": { - "accessKeyId": , - "secretAccessKey": - "sessionToken": - } - - ``awsTemporaryNoSessionToken`` should be substituted with: - - .. code:: javascript - - "aws": { - "accessKeyId": , - "secretAccessKey": - } - - ``gcp`` should be substituted with: - - .. code:: javascript - - "gcp": { - "email": , - "privateKey": , - } - - ``azure`` should be substituted with: - - .. code:: javascript - - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - } - - ``local`` should be substituted with: - - .. code:: javascript - - "local": { "key": } - - ``kmip`` should be substituted with: - - .. code:: javascript - - "kmip": { "endpoint": "localhost:5698" } - - Configure KMIP TLS connections to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/ca.pem `_. This MAY be configured system-wide. - - ``tlsCertificateKeyFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/client.pem `_. - - The method of passing TLS options for KMIP TLS connections is driver dependent. - - #. If ``autoEncryptOpts`` does not include ``keyVaultNamespace``, default it - to ``keyvault.datakeys``. - -#. For each element in ``operations``: - - - Enter a "try" block or your programming language's closest equivalent. - - Create a Database object from the MongoClient, using the ``database_name`` - field at the top level of the test file. - - Create a Collection object from the Database, using the - ``collection_name`` field at the top level of the test file. - If ``collectionOptions`` is present create the Collection object with the - provided options. Otherwise create the object with the default options. - - Execute the named method on the provided ``object``, passing the - arguments listed. - - If the driver throws an exception / returns an error while executing this - series of operations, store the error message and server error code. - - If the result document has an "errorContains" field, verify that the - method threw an exception or returned an error, and that the value of the - "errorContains" field matches the error string. "errorContains" is a - substring (case-insensitive) of the actual error message. - - If the result document has an "errorCodeName" field, verify that the - method threw a command failed exception or returned an error, and that - the value of the "errorCodeName" field matches the "codeName" in the - server error response. - - If the result document has an "errorLabelsContain" field, verify that the - method threw an exception or returned an error. Verify that all of the - error labels in "errorLabelsContain" are present in the error or exception - using the ``hasErrorLabel`` method. - - If the result document has an "errorLabelsOmit" field, verify that the - method threw an exception or returned an error. Verify that none of the - error labels in "errorLabelsOmit" are present in the error or exception - using the ``hasErrorLabel`` method. - - If the operation returns a raw command response, eg from ``runCommand``, - then compare only the fields present in the expected result document. - Otherwise, compare the method's return value to ``result`` using the same - logic as the CRUD Spec Tests runner. - -#. If the test includes a list of command-started events in ``expectations``, - compare them to the actual command-started events using the - same logic as the Command Monitoring Spec Tests runner. - -#. For each element in ``outcome``: - - - If ``name`` is "collection", create a new MongoClient *without encryption* - and verify that the test collection contains exactly the documents in the - ``data`` array. Ensure this find reads the latest data by using - **primary read preference** with **local read concern** even when the - MongoClient is configured with another read preference or read concern. - -The spec test MUST be run with *and* without auth. - - -Using ``crypt_shared`` -====================== - -On platforms where crypt_shared_ is available, drivers should prefer to test -with the ``crypt_shared`` library instead of spawning mongocryptd. - -crypt_shared_ is released alongside the server. -crypt_shared_ is only available in versions 6.0 and above. - -mongocryptd is released alongside the server. -mongocryptd is available in versions 4.2 and above. - -Drivers MUST run all tests with mongocryptd on at least one platform for all -tested server versions. - -Drivers MUST run all tests with crypt_shared_ on at least one platform for all -tested server versions. For server versions < 6.0, drivers MUST test with the -latest major release of crypt_shared_. Using the latest major release of -crypt_shared_ is supported with older server versions. - -Note that some tests assert on mongocryptd-related behaviors (e.g. the -``mongocryptdBypassSpawn`` test). - -Drivers under test should load the crypt_shared_ library using either the -``cryptSharedLibPath`` public API option (as part of the AutoEncryption -``extraOptions``), or by setting a special search path instead. - -Some tests will require *not* using crypt_shared_. For such tests, one should -ensure that ``crypt_shared`` will not be loaded. Refer to the -client-side-encryption documentation for information on "disabling" -``crypt_shared`` and setting library search paths. - -.. note:: - - The crypt_shared_ dynamic library can be obtained using the mongodl_ Python - script from drivers-evergreen-tools_: - - .. code-block:: shell - - $ python3 mongodl.py --component=crypt_shared --version= --out=./crypt_shared/ - - Other versions of ``crypt_shared`` are also available. Please use the - ``--list`` option to see versions. - -.. _mongodl: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/mongodl.py -.. _drivers-evergreen-tools: https://github.com/mongodb-labs/drivers-evergreen-tools/ - - - -Prose Tests -=========== - -Tests for the ClientEncryption type are not included as part of the YAML tests. - -In the prose tests LOCAL_MASTERKEY refers to the following base64: - -.. code:: javascript - - Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk - -Perform all applicable operations on key vault collections (e.g. inserting an example data key, or running a find command) with readConcern/writeConcern "majority". - -1. Custom Key Material Test -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -#. Create a ``MongoClient`` object (referred to as ``client``). - -#. Using ``client``, drop the collection ``keyvault.datakeys``. - -#. Create a ``ClientEncryption`` object (referred to as ``client_encryption``) with ``client`` set as the ``keyVaultClient``. - -#. Using ``client_encryption``, create a data key with a ``local`` KMS provider and the following custom key material (given as base64): - -.. code:: javascript - - xPTAjBRG5JiPm+d3fj6XLi2q5DMXUS/f1f+SMAlhhwkhDRL0kr8r9GDLIGTAGlvC+HVjSIgdL+RKwZCvpXSyxTICWSXTUYsWYPyu3IoHbuBZdmw2faM3WhcRIgbMReU5 - -#. Find the resulting key document in ``keyvault.datakeys``, save a copy of the key document, then remove the key document from the collection. - -#. Replace the ``_id`` field in the copied key document with a UUID with base64 value ``AAAAAAAAAAAAAAAAAAAAAA==`` (16 bytes all equal to ``0x00``) and insert the modified key document into ``keyvault.datakeys`` with majority write concern. - -#. Using ``client_encryption``, encrypt the string ``"test"`` with the modified data key using the ``AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`` algorithm and assert the resulting value is equal to the following (given as base64): - -.. code:: javascript - - AQAAAAAAAAAAAAAAAAAAAAACz0ZOLuuhEYi807ZXTdhbqhLaS2/t9wLifJnnNYwiw79d75QYIZ6M/aYC1h9nCzCjZ7pGUpAuNnkUhnIXM3PjrA== - -2. Data Key and Double Encryption -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -First, perform the setup. - -#. Create a MongoClient without encryption enabled (referred to as ``client``). Enable command monitoring to listen for command_started events. - -#. Using ``client``, drop the collections ``keyvault.datakeys`` and ``db.coll``. - -#. Create the following: - - - A MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - A ``ClientEncryption`` object (referred to as ``client_encryption``) - - Configure both objects with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - }, - "gcp": { - "email": , - "privateKey": , - } - "local": { "key": }, - "kmip": { "endpoint": "localhost:5698" } - } - - Configure KMIP TLS connections to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/ca.pem `_. This MAY be configured system-wide. - - ``tlsCertificateKeyFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/client.pem `_. - - The method of passing TLS options for KMIP TLS connections is driver dependent. - - Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure the ``MongoClient`` with the following ``schema_map``: - - .. code:: javascript - - { - "db.coll": { - "bsonType": "object", - "properties": { - "encrypted_placeholder": { - "encrypt": { - "keyId": "/placeholder", - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" - } - } - } - } - } - - Configure ``client_encryption`` with the ``keyVaultClient`` of the previously created ``client``. - -For each KMS provider (``aws``, ``azure``, ``gcp``, ``local``, and ``kmip``), referred to as ``provider_name``, run the following test. - -#. Call ``client_encryption.createDataKey()``. - - - Set keyAltNames to ``["_altname"]``. - - Set the masterKey document based on ``provider_name``. - - For "aws": - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - } - - For "azure": - - .. code:: javascript - - { - "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", - "keyName": "key-name-csfle" - } - - For "gcp": - - .. code:: javascript - - { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle" - } - - For "kmip": - - .. code:: javascript - - {} - - For "local", do not set a masterKey document. - - Expect a BSON binary with subtype 4 to be returned, referred to as ``datakey_id``. - - Use ``client`` to run a ``find`` on ``keyvault.datakeys`` by querying with the ``_id`` set to the ``datakey_id``. - - Expect that exactly one document is returned with the "masterKey.provider" equal to ``provider_name``. - - Check that ``client`` captured a command_started event for the ``insert`` command containing a majority writeConcern. - -#. Call ``client_encryption.encrypt()`` with the value "hello ", the algorithm ``AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic``, and the ``key_id`` of ``datakey_id``. - - - Expect the return value to be a BSON binary subtype 6, referred to as ``encrypted``. - - Use ``client_encrypted`` to insert ``{ _id: "", "value": }`` into ``db.coll``. - - Use ``client_encrypted`` to run a find querying with ``_id`` of "" and expect ``value`` to be "hello ". - -#. Call ``client_encryption.encrypt()`` with the value "hello ", the algorithm ``AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic``, and the ``key_alt_name`` of ``_altname``. - - - Expect the return value to be a BSON binary subtype 6. Expect the value to exactly match the value of ``encrypted``. - -#. Test explicit encrypting an auto encrypted field. - - - Use ``client_encrypted`` to attempt to insert ``{ "encrypted_placeholder": }`` - - Expect an exception to be thrown, since this is an attempt to auto encrypt an already encrypted value. - - - -3. External Key Vault Test -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Run the following tests twice, parameterized by a boolean ``withExternalKeyVault``. - -#. Create a MongoClient without encryption enabled (referred to as ``client``). - -#. Using ``client``, drop the collections ``keyvault.datakeys`` and ``db.coll``. - Insert the document `external/external-key.json <../external/external-key.json>`_ into ``keyvault.datakeys``. - -#. Create the following: - - - A MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - A ``ClientEncryption`` object (referred to as ``client_encryption``) - - Configure both objects with the ``local`` KMS providers as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure ``client_encrypted`` to use the schema `external/external-schema.json <../external/external-schema.json>`_ for ``db.coll`` by setting a schema map like: ``{ "db.coll": }`` - - If ``withExternalKeyVault == true``, configure both objects with an external key vault client. The external client MUST connect to the same - MongoDB cluster that is being tested against, except it MUST use the username ``fake-user`` and password ``fake-pwd``. - -#. Use ``client_encrypted`` to insert the document ``{"encrypted": "test"}`` into ``db.coll``. - If ``withExternalKeyVault == true``, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed. - -#. Use ``client_encryption`` to explicitly encrypt the string ``"test"`` with key ID ``LOCALAAAAAAAAAAAAAAAAA==`` and deterministic algorithm. - If ``withExternalKeyVault == true``, expect an authentication exception to be thrown. Otherwise, expect the insert to succeed. - - -4. BSON Size Limits and Batch Splitting -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -First, perform the setup. - -#. Create a MongoClient without encryption enabled (referred to as ``client``). - -#. Using ``client``, drop and create the collection ``db.coll`` configured with the included JSON schema `limits/limits-schema.json <../limits/limits-schema.json>`_. - -#. Using ``client``, drop the collection ``keyvault.datakeys``. Insert the document `limits/limits-key.json <../limits/limits-key.json>`_ - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure with the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - -Using ``client_encrypted`` perform the following operations: - -#. Insert ``{ "_id": "over_2mib_under_16mib", "unencrypted": }``. - - Expect this to succeed since this is still under the ``maxBsonObjectSize`` limit. - -#. Insert the document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }`` - Note: limits-doc.json is a 1005 byte BSON document that encrypts to a ~10,000 byte document. - - Expect this to succeed since after encryption this still is below the normal maximum BSON document size. - Note, before auto encryption this document is under the 2 MiB limit. After encryption it exceeds the 2 MiB limit, but does NOT exceed the 16 MiB limit. - -#. Bulk insert the following: - - - ``{ "_id": "over_2mib_1", "unencrypted": }`` - - - ``{ "_id": "over_2mib_2", "unencrypted": }`` - - Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using `command monitoring `_. - -#. Bulk insert the following: - - - The document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib_1", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }`` - - - The document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_2mib_2", "unencrypted": < the string "a" repeated (2097152 - 2000) times > }`` - - Expect the bulk write to succeed and split after first doc (i.e. two inserts occur). This may be verified using `command logging and monitoring `_. - -#. Insert ``{ "_id": "under_16mib", "unencrypted": ``. - - Expect this to succeed since this is still (just) under the ``maxBsonObjectSize`` limit. - -#. Insert the document `limits/limits-doc.json <../limits/limits-doc.json>`_ concatenated with ``{ "_id": "encryption_exceeds_16mib", "unencrypted": < the string "a" repeated (16777216 - 2000) times > }`` - - Expect this to fail since encryption results in a document exceeding the ``maxBsonObjectSize`` limit. - -Optionally, if it is possible to mock the maxWriteBatchSize (i.e. the maximum number of documents in a batch) test that setting maxWriteBatchSize=1 and inserting the two documents ``{ "_id": "a" }, { "_id": "b" }`` with ``client_encrypted`` splits the operation into two inserts. - - -5. Views Are Prohibited -~~~~~~~~~~~~~~~~~~~~~~~ - -#. Create a MongoClient without encryption enabled (referred to as ``client``). - -#. Using ``client``, drop and create a view named ``db.view`` with an empty pipeline. E.g. using the command ``{ "create": "view", "viewOn": "coll" }``. - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure with the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - -#. Using ``client_encrypted``, attempt to insert a document into ``db.view``. Expect an exception to be thrown containing the message: "cannot auto encrypt a view". - - -6. Corpus Test -~~~~~~~~~~~~~~ - -The corpus test exhaustively enumerates all ways to encrypt all BSON value types. Note, the test data includes BSON binary subtype 4 (or standard UUID), which MUST be decoded and encoded as subtype 4. Run the test as follows. - -1. Create a MongoClient without encryption enabled (referred to as ``client``). - -2. Using ``client``, drop and create the collection ``db.coll`` configured with the included JSON schema `corpus/corpus-schema.json <../corpus/corpus-schema.json>`_. - -3. Using ``client``, drop the collection ``keyvault.datakeys``. Insert the documents `corpus/corpus-key-local.json <../corpus/corpus-key-local.json>`_, `corpus/corpus-key-aws.json <../corpus/corpus-key-aws.json>`_, `corpus/corpus-key-azure.json <../corpus/corpus-key-azure.json>`_, `corpus/corpus-key-gcp.json <../corpus/corpus-key-gcp.json>`_, and `corpus/corpus-key-kmip.json <../corpus/corpus-key-kmip.json>`_. - -4. Create the following: - - - A MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - A ``ClientEncryption`` object (referred to as ``client_encryption``) - - Configure both objects with ``aws``, ``azure``, ``gcp``, ``local``, and ``kmip`` KMS providers as follows: - - .. code:: javascript - - { - "aws": { }, - "azure": { }, - "gcp": { }, - "local": { "key": }, - "kmip": { "endpoint": "localhost:5698" } } - } - - Configure KMIP TLS connections to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/ca.pem `_. This MAY be configured system-wide. - - ``tlsCertificateKeyFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/client.pem `_. - - The method of passing TLS options for KMIP TLS connections is driver dependent. - - Where LOCAL_MASTERKEY is the following base64: - - .. code:: javascript - - Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk - - Configure both objects with ``keyVaultNamespace`` set to ``keyvault.datakeys``. - -5. Load `corpus/corpus.json <../corpus/corpus.json>`_ to a variable named ``corpus``. The corpus contains subdocuments with the following fields: - - - ``kms`` is ``aws``, ``azure``, ``gcp``, ``local``, or ``kmip`` - - ``type`` is a BSON type string `names coming from here `_) - - ``algo`` is either ``rand`` or ``det`` for random or deterministic encryption - - ``method`` is either ``auto``, for automatic encryption or ``explicit`` for explicit encryption - - ``identifier`` is either ``id`` or ``altname`` for the key identifier - - ``allowed`` is a boolean indicating whether the encryption for the given parameters is permitted. - - ``value`` is the value to be tested. - - Create a new BSON document, named ``corpus_copied``. - Iterate over each field of ``corpus``. - - - If the field name is ``_id``, ``altname_aws``, ``altname_local``, ``altname_azure``, ``altname_gcp``, or ``altname_kmip`` copy the field to ``corpus_copied``. - - If ``method`` is ``auto``, copy the field to ``corpus_copied``. - - If ``method`` is ``explicit``, use ``client_encryption`` to explicitly encrypt the value. - - - Encrypt with the algorithm described by ``algo``. - - If ``identifier`` is ``id`` - - - If ``kms`` is ``local`` set the key_id to the UUID with base64 value ``LOCALAAAAAAAAAAAAAAAAA==``. - - If ``kms`` is ``aws`` set the key_id to the UUID with base64 value ``AWSAAAAAAAAAAAAAAAAAAA==``. - - If ``kms`` is ``azure`` set the key_id to the UUID with base64 value ``AZUREAAAAAAAAAAAAAAAAA==``. - - If ``kms`` is ``gcp`` set the key_id to the UUID with base64 value ``GCPAAAAAAAAAAAAAAAAAAA==``. - - If ``kms`` is ``kmip`` set the key_id to the UUID with base64 value ``KMIPAAAAAAAAAAAAAAAAAA==``. - - - If ``identifier`` is ``altname`` - - - If ``kms`` is ``local`` set the key_alt_name to "local". - - If ``kms`` is ``aws`` set the key_alt_name to "aws". - - If ``kms`` is ``azure`` set the key_alt_name to "azure". - - If ``kms`` is ``gcp`` set the key_alt_name to "gcp". - - If ``kms`` is ``kmip`` set the key_alt_name to "kmip". - - If ``allowed`` is true, copy the field and encrypted value to ``corpus_copied``. - If ``allowed`` is false. verify that an exception is thrown. Copy the unencrypted value to to ``corpus_copied``. - - -6. Using ``client_encrypted``, insert ``corpus_copied`` into ``db.coll``. - -7. Using ``client_encrypted``, find the inserted document from ``db.coll`` to a variable named ``corpus_decrypted``. Since it should have been automatically decrypted, assert the document exactly matches ``corpus``. - -8. Load `corpus/corpus_encrypted.json <../corpus/corpus-encrypted.json>`_ to a variable named ``corpus_encrypted_expected``. - Using ``client`` find the inserted document from ``db.coll`` to a variable named ``corpus_encrypted_actual``. - - Iterate over each field of ``corpus_encrypted_expected`` and check the following: - - - If the ``algo`` is ``det``, that the value equals the value of the corresponding field in ``corpus_encrypted_actual``. - - If the ``algo`` is ``rand`` and ``allowed`` is true, that the value does not equal the value of the corresponding field in ``corpus_encrypted_actual``. - - If ``allowed`` is true, decrypt the value with ``client_encryption``. Decrypt the value of the corresponding field of ``corpus_encrypted`` and validate that they are both equal. - - If ``allowed`` is false, validate the value exactly equals the value of the corresponding field of ``corpus`` (neither was encrypted). - -9. Repeat steps 1-8 with a local JSON schema. I.e. amend step 4 to configure the schema on ``client_encrypted`` with the ``schema_map`` option. - -7. Custom Endpoint Test -~~~~~~~~~~~~~~~~~~~~~~~ - -Setup -````` - -For each test cases, start by creating two ``ClientEncryption`` objects. Recreate the ``ClientEncryption`` objects for each test case. - -Create a ``ClientEncryption`` object (referred to as ``client_encryption``) - -Configure with ``keyVaultNamespace`` set to ``keyvault.datakeys``, and a default MongoClient as the ``keyVaultClient``. - -Configure with KMS providers as follows: - -.. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "login.microsoftonline.com:443" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "oauth2.googleapis.com:443" - }, - "kmip" { - "endpoint": "localhost:5698" - } - } - -Create a ``ClientEncryption`` object (referred to as ``client_encryption_invalid``) - -Configure with ``keyVaultNamespace`` set to ``keyvault.datakeys``, and a default MongoClient as the ``keyVaultClient``. - -Configure with KMS providers as follows: - -.. code:: javascript - - { - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "doesnotexist.invalid:443" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "doesnotexist.invalid:443" - }, - "kmip": { - "endpoint": "doesnotexist.local:5698" - } - } - -Configure KMIP TLS connections to use the following options: - -- ``tlsCAFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/ca.pem `_. This MAY be configured system-wide. -- ``tlsCertificateKeyFile`` (or equivalent) set to `drivers-evergreen-tools/.evergreen/x509gen/client.pem `_. - -The method of passing TLS options for KMIP TLS connections is driver dependent. - -Test cases -`````````` - -1. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - -2. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - endpoint: "kms.us-east-1.amazonaws.com" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - -3. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - endpoint: "kms.us-east-1.amazonaws.com:443" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - -4. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - endpoint: "kms.us-east-1.amazonaws.com:12345" - } - - Expect this to fail with a socket connection error. - -5. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - endpoint: "kms.us-east-2.amazonaws.com" - } - - Expect this to fail with an exception. - -6. Call `client_encryption.createDataKey()` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - endpoint: "doesnotexist.invalid" - } - - Expect this to fail with a network exception indicating failure to resolve "doesnotexist.invalid". - -7. Call `client_encryption.createDataKey()` with "azure" as the provider and the following masterKey: - - .. code:: javascript - - { - "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", - "keyName": "key-name-csfle" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - - Call ``client_encryption_invalid.createDataKey()`` with the same masterKey. Expect this to fail with a network exception indicating failure to resolve "doesnotexist.invalid". - -8. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey: - - .. code:: javascript - - { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle", - "endpoint": "cloudkms.googleapis.com:443" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - - Call ``client_encryption_invalid.createDataKey()`` with the same masterKey. Expect this to fail with a network exception indicating failure to resolve "doesnotexist.invalid". - -9. Call `client_encryption.createDataKey()` with "gcp" as the provider and the following masterKey: - - .. code:: javascript - - { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle", - "endpoint": "doesnotexist.invalid:443" - } - - Expect this to fail with an exception with a message containing the string: "Invalid KMS response". - -10. Call `client_encryption.createDataKey()` with "kmip" as the provider and the following masterKey: - - .. code:: javascript - - { - "keyId": "1" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - - Call ``client_encryption_invalid.createDataKey()`` with the same masterKey. Expect this to fail with a network exception indicating failure to resolve "doesnotexist.local". - -11. Call ``client_encryption.createDataKey()`` with "kmip" as the provider and the following masterKey: - - .. code:: javascript - - { - "keyId": "1", - "endpoint": "localhost:5698" - } - - Expect this to succeed. Use the returned UUID of the key to explicitly encrypt and decrypt the string "test" to validate it works. - -12. Call ``client_encryption.createDataKey()`` with "kmip" as the provider and the following masterKey: - - .. code:: javascript - - { - "keyId": "1", - "endpoint": "doesnotexist.local:5698" - } - - Expect this to fail with a network exception indicating failure to resolve "doesnotexist.local". - -8. Bypass Spawning mongocryptd -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Via loading shared library -`````````````````````````` - -The following tests that loading crypt_shared_ bypasses spawning mongocryptd. - -.. note:: - - IMPORTANT: This test requires the crypt_shared_ library be loaded. If the crypt_shared_ library is - not available, skip the test. - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure the required options. Use the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure ``client_encrypted`` to use the schema `external/external-schema.json <../external/external-schema.json>`_ for ``db.coll`` by setting a schema map like: ``{ "db.coll": }`` - - Configure the following ``extraOptions``: - - .. code:: javascript - - { - "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", - "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"], - "cryptSharedLibPath": "", - "cryptSharedLibRequired": true - } - - Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free. - -#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``. Expect this to succeed. - -#. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or whatever was passed via ``--port``) with serverSelectionTimeoutMS=1000. Run a handshake command and ensure it fails with a server selection timeout. - -.. note:: - - IMPORTANT: If crypt_shared_ is visible to the operating system's library - search mechanism, the expected server error generated by these - ``mongocryptdBypassSpawn`` tests will not appear because libmongocrypt will - load the ``crypt_shared`` library instead of consulting mongocryptd. For - the following tests, it is required that libmongocrypt *not* load ``crypt_shared``. - Refer to the client-side-encryption document for more information on - "disabling" ``crypt_shared``. - - -Via mongocryptdBypassSpawn -`````````````````````````` - -The following tests that setting ``mongocryptdBypassSpawn=true`` really does bypass spawning mongocryptd. - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure the required options. Use the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure ``client_encrypted`` to use the schema `external/external-schema.json <../external/external-schema.json>`_ for ``db.coll`` by setting a schema map like: ``{ "db.coll": }`` - - Configure the following ``extraOptions``: - - .. code:: javascript - - { - "mongocryptdBypassSpawn": true - "mongocryptdURI": "mongodb://localhost:27021/db?serverSelectionTimeoutMS=1000", - "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"] - } - - Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free. - -#. Use ``client_encrypted`` to insert the document ``{"encrypted": "test"}`` into ``db.coll``. Expect a server selection error propagated from the internal MongoClient failing to connect to mongocryptd on port 27021. - -Via bypassAutoEncryption -```````````````````````` - -The following tests that setting ``bypassAutoEncryption=true`` really does bypass spawning mongocryptd. - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure the required options. Use the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure with ``bypassAutoEncryption=true``. - - Configure the following ``extraOptions``: - - .. code:: javascript - - { - "mongocryptdSpawnArgs": [ "--pidfilepath=bypass-spawning-mongocryptd.pid", "--port=27021"] - } - - Drivers MAY pass a different value to ``--port`` if they expect their testing infrastructure to be using port 27021. Pass a port that should be free. - -#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``. Expect this to succeed. - -#. Validate that mongocryptd was not spawned. Create a MongoClient to localhost:27021 (or whatever was passed via ``--port``) with serverSelectionTimeoutMS=1000. Run a handshake command and ensure it fails with a server selection timeout. - -Via bypassQueryAnalysis -``````````````````````` - -Repeat the steps from the "Via bypassAutoEncryption" test, replacing "bypassAutoEncryption=true" with "bypassQueryAnalysis=true". - -9. Deadlock Tests -~~~~~~~~~~~~~~~~~ - -.. _Connection Monitoring and Pooling: /source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.rst - -The following tests only apply to drivers that have implemented a connection pool (see the `Connection Monitoring and Pooling`_ specification). - -There are multiple parameterized test cases. Before each test case, perform the setup. - -Setup -````` - -Create a ``MongoClient`` for setup operations named ``client_test``. - -Create a ``MongoClient`` for key vault operations with ``maxPoolSize=1`` named ``client_keyvault``. Capture command started events. - -Using ``client_test``, drop the collections ``keyvault.datakeys`` and ``db.coll``. - -Insert the document `external/external-key.json <../external/external-key.json>`_ into ``keyvault.datakeys`` with majority write concern. - -Create a collection ``db.coll`` configured with a JSON schema `external/external-schema.json <../external/external-schema.json>`_ as the validator, like so: - -.. code:: typescript - - {"create": "coll", "validator": {"$jsonSchema": }} - -Create a ``ClientEncryption`` object, named ``client_encryption`` configured with: -- ``keyVaultClient``=``client_test`` -- ``keyVaultNamespace``="keyvault.datakeys" -- ``kmsProviders``=``{ "local": { "key": } }`` - -Use ``client_encryption`` to encrypt the value "string0" with ``algorithm``="AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" and ``keyAltName``="local". Store the result in a variable named ``ciphertext``. - -Proceed to run the test case. - -Each test case configures a ``MongoClient`` with automatic encryption (named ``client_encrypted``). - -Each test must assert the number of unique ``MongoClient``s created. This can be accomplished by capturing ``TopologyOpeningEvent``, or by checking command started events for a client identifier (not possible in all drivers). - -Running a test case -``````````````````` -- Create a ``MongoClient`` named ``client_encrypted`` configured as follows: - - Set ``AutoEncryptionOpts``: - - ``keyVaultNamespace="keyvault.datakeys"`` - - ``kmsProviders``=``{ "local": { "key": } }`` - - Append ``TestCase.AutoEncryptionOpts`` (defined below) - - Capture command started events. - - Set ``maxPoolSize=TestCase.MaxPoolSize`` -- If the testcase sets ``AutoEncryptionOpts.bypassAutoEncryption=true``: - - Use ``client_test`` to insert ``{ "_id": 0, "encrypted": }`` into ``db.coll``. -- Otherwise: - - Use ``client_encrypted`` to insert ``{ "_id": 0, "encrypted": "string0" }``. -- Use ``client_encrypted`` to run a ``findOne`` operation on ``db.coll``, with the filter ``{ "_id": 0 }``. -- Expect the result to be ``{ "_id": 0, "encrypted": "string0" }``. -- Check captured events against ``TestCase.Expectations``. -- Check the number of unique ``MongoClient``s created is equal to ``TestCase.ExpectedNumberOfClients``. - -Case 1 -`````` -- MaxPoolSize: 1 -- AutoEncryptionOpts: - - bypassAutoEncryption=false - - keyVaultClient=unset -- Expectations: - - Expect ``client_encrypted`` to have captured four ``CommandStartedEvent``: - - a listCollections to "db". - - a find on "keyvault". - - an insert on "db". - - a find on "db" -- ExpectedNumberOfClients: 2 - -Case 2 -`````` -- MaxPoolSize: 1 -- AutoEncryptionOpts: - - bypassAutoEncryption=false - - keyVaultClient=client_keyvault -- Expectations: - - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``: - - a listCollections to "db". - - an insert on "db". - - a find on "db" - - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``: - - a find on "keyvault". -- ExpectedNumberOfClients: 2 - -Case 3 -`````` -- MaxPoolSize: 1 -- AutoEncryptionOpts: - - bypassAutoEncryption=true - - keyVaultClient=unset -- Expectations: - - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``: - - a find on "db" - - a find on "keyvault". -- ExpectedNumberOfClients: 2 - -Case 4 -`````` -- MaxPoolSize: 1 -- AutoEncryptionOpts: - - bypassAutoEncryption=true - - keyVaultClient=client_keyvault -- Expectations: - - Expect ``client_encrypted`` to have captured two ``CommandStartedEvent``: - - a find on "db" - - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``: - - a find on "keyvault". -- ExpectedNumberOfClients: 1 - -Case 5 -`````` -Drivers that do not support an unlimited maximum pool size MUST skip this test. - -- MaxPoolSize: 0 -- AutoEncryptionOpts: - - bypassAutoEncryption=false - - keyVaultClient=unset -- Expectations: - - Expect ``client_encrypted`` to have captured five ``CommandStartedEvent``: - - a listCollections to "db". - - a listCollections to "keyvault". - - a find on "keyvault". - - an insert on "db". - - a find on "db" -- ExpectedNumberOfClients: 1 - -Case 6 -`````` -Drivers that do not support an unlimited maximum pool size MUST skip this test. - -- MaxPoolSize: 0 -- AutoEncryptionOpts: - - bypassAutoEncryption=false - - keyVaultClient=client_keyvault -- Expectations: - - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``: - - a listCollections to "db". - - an insert on "db". - - a find on "db" - - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``: - - a find on "keyvault". -- ExpectedNumberOfClients: 1 - -Case 7 -`````` -Drivers that do not support an unlimited maximum pool size MUST skip this test. - -- MaxPoolSize: 0 -- AutoEncryptionOpts: - - bypassAutoEncryption=true - - keyVaultClient=unset -- Expectations: - - Expect ``client_encrypted`` to have captured three ``CommandStartedEvent``: - - a find on "db" - - a find on "keyvault". -- ExpectedNumberOfClients: 1 - -Case 8 -`````` -Drivers that do not support an unlimited maximum pool size MUST skip this test. - -- MaxPoolSize: 0 -- AutoEncryptionOpts: - - bypassAutoEncryption=true - - keyVaultClient=client_keyvault -- Expectations: - - Expect ``client_encrypted`` to have captured two ``CommandStartedEvent``: - - a find on "db" - - Expect ``client_keyvault`` to have captured one ``CommandStartedEvent``: - - a find on "keyvault". -- ExpectedNumberOfClients: 1 - -10. KMS TLS Tests -~~~~~~~~~~~~~~~~~ - -.. _ca.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/ca.pem -.. _expired.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/expired.pem -.. _wrong-host.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/wrong-host.pem -.. _server.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/server.pem -.. _client.pem: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/x509gen/client.pem - -The following tests that connections to KMS servers with TLS verify peer certificates. - -The two tests below make use of mock KMS servers which can be run on Evergreen using `the mock KMS server script `_. -Drivers can set up their local Python enviroment for the mock KMS server by running `the virtualenv activation script `_. - -To start two mock KMS servers, one on port 9000 with `ca.pem`_ as a CA file and `expired.pem`_ as a cert file, and one on port 9001 with `ca.pem`_ as a CA file and `wrong-host.pem`_ as a cert file, -run the following commands from the ``.evergreen/csfle`` directory: - -.. code:: - - . ./activate_venv.sh - python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 9000 & - python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 9001 & - -Setup -````` - -For both tests, do the following: - -#. Start a ``mongod`` process with **server version 4.1.9 or later**. - -#. Create a ``MongoClient`` for key vault operations. - -#. Create a ``ClientEncryption`` object (referred to as ``client_encryption``) with ``keyVaultNamespace`` set to ``keyvault.datakeys``. - -Invalid KMS Certificate -``````````````````````` - -#. Start a mock KMS server on port 9000 with `ca.pem`_ as a CA file and `expired.pem`_ as a cert file. - -#. Call ``client_encryption.createDataKey()`` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - "region": "us-east-1", - "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - "endpoint": "127.0.0.1:9000", - } - - Expect this to fail with an exception with a message referencing an expired certificate. This message will be language dependent. - In Python, this message is "certificate verify failed: certificate has expired". In Go, this message is - "certificate has expired or is not yet valid". If the language of implementation has a single, generic error message for - all certificate validation errors, drivers may inspect other fields of the error to verify its meaning. - -Invalid Hostname in KMS Certificate -``````````````````````````````````` - -#. Start a mock KMS server on port 9001 with `ca.pem`_ as a CA file and `wrong-host.pem`_ as a cert file. - -#. Call ``client_encryption.createDataKey()`` with "aws" as the provider and the following masterKey: - - .. code:: javascript - - { - "region": "us-east-1", - "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - "endpoint": "127.0.0.1:9001", - } - - Expect this to fail with an exception with a message referencing an incorrect or unexpected host. This message will be language dependent. - In Python, this message is "certificate verify failed: IP address mismatch, certificate is not valid for '127.0.0.1'". In Go, this message - is "cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs". If the language of implementation has a single, generic - error message for all certificate validation errors, drivers may inspect other fields of the error to verify its meaning. - -11. KMS TLS Options Tests -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Setup -````` - -Start a ``mongod`` process with **server version 4.1.9 or later**. - -Four mock KMS server processes must be running: - -1. The mock `KMS HTTP server `_. - - Run on port 9000 with `ca.pem`_ as a CA file and `expired.pem`_ as a cert file. - - Example: - - .. code:: - - python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/expired.pem --port 9000 - -2. The mock `KMS HTTP server `_. - - Run on port 9001 with `ca.pem`_ as a CA file and `wrong-host.pem`_ as a cert file. - - Example: - - .. code:: - - python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/wrong-host.pem --port 9001 - -3. The mock `KMS HTTP server `_. - - Run on port 9002 with `ca.pem`_ as a CA file and `server.pem`_ as a cert file. - - Run with the ``--require_client_cert`` option. - - Example: - - .. code:: - - python -u kms_http_server.py --ca_file ../x509gen/ca.pem --cert_file ../x509gen/server.pem --port 9002 --require_client_cert - - -4. The mock `KMS KMIP server `_. - -Create the following four ``ClientEncryption`` objects. - -Configure each with ``keyVaultNamespace`` set to ``keyvault.datakeys``, and a default MongoClient as the ``keyVaultClient``. - -1. Create a ``ClientEncryption`` object named ``client_encryption_no_client_cert`` with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "127.0.0.1:9002" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "127.0.0.1:9002" - }, - "kmip" { - "endpoint": "127.0.0.1:5698" - } - } - - Add TLS options for the ``aws``, ``azure``, ``gcp``, and - ``kmip`` providers to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `ca.pem`_. This MAY be configured system-wide. - -2. Create a ``ClientEncryption`` object named ``client_encryption_with_tls`` with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "127.0.0.1:9002" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "127.0.0.1:9002" - }, - "kmip" { - "endpoint": "127.0.0.1:5698" - } - } - - Add TLS options for the ``aws``, ``azure``, ``gcp``, and - ``kmip`` providers to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `ca.pem`_. This MAY be configured system-wide. - - ``tlsCertificateKeyFile`` (or equivalent) set to `client.pem`_ - -3. Create a ``ClientEncryption`` object named ``client_encryption_expired`` with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "127.0.0.1:9000" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "127.0.0.1:9000" - }, - "kmip" { - "endpoint": "127.0.0.1:9000" - } - } - - Add TLS options for the ``aws``, ``azure``, ``gcp``, and - ``kmip`` providers to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `ca.pem`_. This MAY be configured system-wide. - -4. Create a ``ClientEncryption`` object named ``client_encryption_invalid_hostname`` with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": , - "secretAccessKey": - }, - "azure": { - "tenantId": , - "clientId": , - "clientSecret": , - "identityPlatformEndpoint": "127.0.0.1:9001" - }, - "gcp": { - "email": , - "privateKey": , - "endpoint": "127.0.0.1:9001" - }, - "kmip" { - "endpoint": "127.0.0.1:9001" - } - } - - Add TLS options for the ``aws``, ``azure``, ``gcp``, and - ``kmip`` providers to use the following options: - - - ``tlsCAFile`` (or equivalent) set to `ca.pem`_. This MAY be configured system-wide. - -Case 1: AWS -``````````` - -Call `client_encryption_no_client_cert.createDataKey()` with "aws" as the provider and the -following masterKey: - -.. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - endpoint: "127.0.0.1:9002" - } - -Expect an error indicating TLS handshake failed. - -Call `client_encryption_with_tls.createDataKey()` with "aws" as the provider and the -following masterKey: - -.. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - endpoint: "127.0.0.1:9002" - } - -Expect an error from libmongocrypt with a message containing the string: "parse -error". This implies TLS handshake succeeded. - -Call `client_encryption_expired.createDataKey()` with "aws" as the provider and the -following masterKey: - -.. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - endpoint: "127.0.0.1:9000" - } - -Expect an error indicating TLS handshake failed due to an expired certificate. - -Call `client_encryption_invalid_hostname.createDataKey()` with "aws" as the provider and the -following masterKey: - -.. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - endpoint: "127.0.0.1:9001" - } - -Expect an error indicating TLS handshake failed due to an invalid hostname. - -Case 2: Azure -````````````` - -Call `client_encryption_no_client_cert.createDataKey()` with "azure" as the provider and the -following masterKey: - -.. code:: javascript - - { 'keyVaultEndpoint': 'doesnotexist.local', 'keyName': 'foo' } - -Expect an error indicating TLS handshake failed. - -Call `client_encryption_with_tls.createDataKey()` with "azure" as the provider -and the same masterKey. - -Expect an error from libmongocrypt with a message containing the string: "HTTP -status=404". This implies TLS handshake succeeded. - -Call `client_encryption_expired.createDataKey()` with "azure" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an expired certificate. - -Call `client_encryption_invalid_hostname.createDataKey()` with "azure" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an invalid hostname. - -Case 3: GCP -``````````` - -Call `client_encryption_no_client_cert.createDataKey()` with "gcp" as the provider and the -following masterKey: - -.. code:: javascript - - { 'projectId': 'foo', 'location': 'bar', 'keyRing': 'baz', 'keyName': 'foo' } - -Expect an error indicating TLS handshake failed. - -Call `client_encryption_with_tls.createDataKey()` with "gcp" as the provider and -the same masterKey. - -Expect an error from libmongocrypt with a message containing the string: "HTTP -status=404". This implies TLS handshake succeeded. - -Call `client_encryption_expired.createDataKey()` with "gcp" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an expired certificate. - -Call `client_encryption_invalid_hostname.createDataKey()` with "gcp" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an invalid hostname. - -Case 4: KMIP -```````````` - -Call `client_encryption_no_client_cert.createDataKey()` with "kmip" as the provider and the -following masterKey: - -.. code:: javascript - - { } - -Expect an error indicating TLS handshake failed. - -Call `client_encryption_with_tls.createDataKey()` with "kmip" as the provider -and the same masterKey. - -Expect success. - -Call `client_encryption_expired.createDataKey()` with "kmip" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an expired certificate. - -Call `client_encryption_invalid_hostname.createDataKey()` with "kmip" as the provider and -the same masterKey. - -Expect an error indicating TLS handshake failed due to an invalid hostname. - -Case 5: `tlsDisableOCSPEndpointCheck` is permitted -`````````````````````````````````````````````````` - -This test does not apply if the driver does not support the the option ``tlsDisableOCSPEndpointCheck``. - -Create a ``ClientEncryption`` object with the following KMS providers: - - .. code:: javascript - - { - "aws": { - "accessKeyId": "foo", - "secretAccessKey": "bar" - } - } - - Add TLS options for the ``aws`` with the following options: - - - ``tlsDisableOCSPEndpointCheck`` (or equivalent) set to ``true``. - -Expect no error on construction. - - -12. Explicit Encryption -~~~~~~~~~~~~~~~~~~~~~~~ - -The Explicit Encryption tests require MongoDB server 7.0+. The tests must not run against a standalone. - -.. note:: - MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2. - libmongocrypt 1.8.0 is configured to use the QEv2 protocol. - -.. note:: - Skip this test on Serverless until MongoDB Serverless enables the QEv2 protocol. Refer: `DRIVERS-2589 `_ - -Before running each of the following test cases, perform the following Test Setup. - -Test Setup -`````````` - -Load the file `encryptedFields.json `_ as ``encryptedFields``. - -Load the file `key1-document.json `_ as ``key1Document``. - -Read the ``"_id"`` field of ``key1Document`` as ``key1ID``. - -Drop and create the collection ``db.explicit_encryption`` using ``encryptedFields`` as an option. See `FLE 2 CreateCollection() and Collection.Drop() `_. - -Drop and create the collection ``keyvault.datakeys``. - -Insert ``key1Document`` in ``keyvault.datakeys`` with majority write concern. - -Create a MongoClient named ``keyVaultClient``. - -Create a ClientEncryption object named ``clientEncryption`` with these options: - -.. code:: typescript - - ClientEncryptionOpts { - keyVaultClient: ; - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: { "local": { "key": } } - } - -Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``: - -.. code:: typescript - - AutoEncryptionOpts { - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: { "local": { "key": } } - bypassQueryAnalysis: true - } - - -Case 1: can insert encrypted indexed and find -````````````````````````````````````````````` - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - contentionFactor: 0 - } - -Store the result in ``insertPayload``. - -Use ``encryptedClient`` to insert the document ``{ "encryptedIndexed": }`` into ``db.explicit_encryption``. - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - queryType: "equality", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``{ "encryptedIndexed": }``. - -Assert one document is returned containing the field ``{ "encryptedIndexed": "encrypted indexed value" }``. - -Case 2: can insert encrypted indexed and find with non-zero contention -``````````````````````````````````````````````````````````````````````` - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - contentionFactor: 10 - } - -Store the result in ``insertPayload``. - -Use ``encryptedClient`` to insert the document ``{ "encryptedIndexed": }`` into ``db.explicit_encryption``. - -Repeat the above steps 10 times to insert 10 total documents. The ``insertPayload`` must be regenerated each iteration. - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - queryType: "equality", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``{ "encryptedIndexed": }``. - -Assert less than 10 documents are returned. 0 documents may be returned. Assert each returned document contains the field ``{ "encryptedIndexed": "encrypted indexed value" }``. - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - queryType: "equality", - contentionFactor: 10 - } - -Store the result in ``findPayload2``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``{ "encryptedIndexed": }``. - -Assert 10 documents are returned. Assert each returned document contains the field ``{ "encryptedIndexed": "encrypted indexed value" }``. - -Case 3: can insert encrypted unindexed -`````````````````````````````````````` - -Use ``clientEncryption`` to encrypt the value "encrypted unindexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Unindexed" - } - -Store the result in ``insertPayload``. - -Use ``encryptedClient`` to insert the document ``{ "_id": 1, "encryptedUnindexed": }`` into ``db.explicit_encryption``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``{ "_id": 1 }``. - -Assert one document is returned containing the field ``{ "encryptedUnindexed": "encrypted unindexed value" }``. - -Case 4: can roundtrip encrypted indexed -``````````````````````````````````````` - -Use ``clientEncryption`` to encrypt the value "encrypted indexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Indexed", - contentionFactor: 0 - } - -Store the result in ``payload``. - -Use ``clientEncryption`` to decrypt ``payload``. Assert the returned value equals "encrypted indexed value". - -Case 5: can roundtrip encrypted unindexed -````````````````````````````````````````` - -Use ``clientEncryption`` to encrypt the value "encrypted unindexed value" with these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "Unindexed", - } - -Store the result in ``payload``. - -Use ``clientEncryption`` to decrypt ``payload``. Assert the returned value equals "encrypted unindexed value". - -13. Unique Index on keyAltNames -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The following setup must occur before running each of the following test cases. - -Setup -````` - -1. Create a ``MongoClient`` object (referred to as ``client``). - -2. Using ``client``, drop the collection ``keyvault.datakeys``. - -3. Using ``client``, create a unique index on ``keyAltNames`` with a partial index filter for only documents where ``keyAltNames`` exists using writeConcern "majority". - -The command should be equivalent to: - -.. code:: typescript - - db.runCommand( - { - createIndexes: "datakeys", - indexes: [ - { - name: "keyAltNames_1", - key: { "keyAltNames": 1 }, - unique: true, - partialFilterExpression: { keyAltNames: { $exists: true } } - } - ], - writeConcern: { w: "majority" } - } - ) - -4. Create a ``ClientEncryption`` object (referred to as ``client_encryption``) with ``client`` set as the ``keyVaultClient``. - -5. Using ``client_encryption``, create a data key with a ``local`` KMS provider and the keyAltName "def". - -Case 1: createDataKey() -``````````````````````` - -1. Use ``client_encryption`` to create a new local data key with a keyAltName "abc" and assert the operation does not fail. - -2. Repeat Step 1 and assert the operation fails due to a duplicate key server error (error code 11000). - -3. Use ``client_encryption`` to create a new local data key with a keyAltName "def" and assert the operation fails due to a duplicate key server error (error code 11000). - -Case 2: addKeyAltName() -``````````````````````` - -1. Use ``client_encryption`` to create a new local data key and assert the operation does not fail. - -2. Use ``client_encryption`` to add a keyAltName "abc" to the key created in Step 1 and assert the operation does not fail. - -3. Repeat Step 2, assert the operation does not fail, and assert the returned key document contains the keyAltName "abc" added in Step 2. - -4. Use ``client_encryption`` to add a keyAltName "def" to the key created in Step 1 and assert the operation fails due to a duplicate key server error (error code 11000). - -5. Use ``client_encryption`` to add a keyAltName "def" to the existing key, assert the operation does not fail, and assert the returned key document contains the keyAltName "def" added during Setup. - -14. Decryption Events -~~~~~~~~~~~~~~~~~~~~~ - -Before running each of the following test cases, perform the following Test Setup. - -Test Setup -`````````` - -Create a MongoClient named ``setupClient``. - -Drop and create the collection ``db.decryption_events``. - -Create a ClientEncryption object named ``clientEncryption`` with these options: - -.. code:: typescript - - ClientEncryptionOpts { - keyVaultClient: , - keyVaultNamespace: "keyvault.datakeys", - kmsProviders: { "local": { "key": } } - } - -Create a data key with the "local" KMS provider. Storing the result in a variable named ``keyID``. - -Use ``clientEncryption`` to encrypt the string "hello" with the following ``EncryptOpts``: - -.. code:: typescript - - EncryptOpts { - keyId: , - algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" - } - -Store the result in a variable named ``ciphertext``. - -Copy ``ciphertext`` into a variable named ``malformedCiphertext``. Change the -last byte to a different value. This will produce an invalid HMAC tag. - -Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``: - -.. code:: typescript - - AutoEncryptionOpts { - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: { "local": { "key": } } - } - -Configure ``encryptedClient`` with "retryReads=false". -Register a listener for CommandSucceeded events on ``encryptedClient``. -The listener must store the most recent ``CommandSucceededEvent`` reply for the "aggregate" command. -The listener must store the most recent ``CommandFailedEvent`` error for the "aggregate" command. - -Case 1: Command Error -````````````````````` - -Use ``setupClient`` to configure the following failpoint: - -.. code:: typescript - - { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "errorCode": 123, - "failCommands": [ - "aggregate" - ] - } - } - -Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``. - -Expect an exception to be thrown from the command error. Expect a ``CommandFailedEvent``. - -Case 2: Network Error -````````````````````` - -Use ``setupClient`` to configure the following failpoint: - -.. code:: typescript - - { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "errorCode": 123, - "closeConnection": true, - "failCommands": [ - "aggregate" - ] - } - } - -Use ``encryptedClient`` to run an aggregate on ``db.decryption_events``. - -Expect an exception to be thrown from the network error. Expect a ``CommandFailedEvent``. - -Case 3: Decrypt Error -````````````````````` - -Use ``encryptedClient`` to insert the document ``{ "encrypted": }`` into ``db.decryption_events``. - -Use ``encryptedClient`` to run an aggregate on ``db.decryption_events`` with an empty pipeline. - -Expect an exception to be thrown from the decryption error. -Expect a ``CommandSucceededEvent``. Expect the ``CommandSucceededEvent.reply`` to contain BSON binary for the field ``cursor.firstBatch.encrypted``. - -Case 4: Decrypt Success -``````````````````````` - -Use ``encryptedClient`` to insert the document ``{ "encrypted": }`` into ``db.decryption_events``. - -Use ``encryptedClient`` to run an aggregate on ``db.decryption_events`` with an empty pipeline. - -Expect no exception. -Expect a ``CommandSucceededEvent``. Expect the ``CommandSucceededEvent.reply`` to contain BSON binary for the field ``cursor.firstBatch.encrypted``. - - -15. On-demand AWS Credentials -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -These tests require valid AWS credentials. Refer: `Automatic AWS Credentials`_. - -For these cases, create a ClientEncryption_ object :math:`C` with the following -options: - -.. code-block:: typescript - - ClientEncryptionOpts { - keyVaultClient: , - keyVaultNamespace: "keyvault.datakeys", - kmsProviders: { "aws": {} }, - } - -Case 1: Failure -``````````````` - -Do not run this test case in an environment where AWS credentials are available -(e.g. via environment variables or a metadata URL). (Refer: -`Obtaining credentials for AWS `_) - -Attempt to create a datakey with :math:`C` using the ``"aws"`` KMS provider. -Expect this to fail due to a lack of KMS provider credentials. - -Case 2: Success -``````````````` - -For this test case, the environment variables ``AWS_ACCESS_KEY_ID`` and -``AWS_SECRET_ACCESS_KEY`` must be defined and set to a valid set of AWS -credentials. - -Use the client encryption to create a datakey using the ``"aws"`` KMS provider. -This should successfully load and use the AWS credentials that were defined in -the environment. - -.. _Automatic AWS Credentials: ../client-side-encryption.rst#automatic-aws-credentials -.. _ClientEncryption: ../client-side-encryption.rst#clientencryption -.. _auth-aws: ../../auth/auth.rst#obtaining-credentials - -16. Rewrap -~~~~~~~~~~ - -Case 1: Rewrap with separate ClientEncryption -````````````````````````````````````````````` - -When the following test case requests setting ``masterKey``, use the following values based on the KMS provider: - -For "aws": - -.. code:: javascript - - { - "region": "us-east-1", - "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - } - -For "azure": - -.. code:: javascript - - { - "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", - "keyName": "key-name-csfle" - } - -For "gcp": - -.. code:: javascript - - { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle" - } - -For "kmip": - -.. code:: javascript - - {} - -For "local", do not set a masterKey document. - -Run the following test case for each pair of KMS providers (referred to as ``srcProvider`` and ``dstProvider``). -Include pairs where ``srcProvider`` equals ``dstProvider``. - -1. Drop the collection ``keyvault.datakeys``. - -2. Create a ``ClientEncryption`` object named ``clientEncryption1`` with these options: - - .. code:: typescript - - ClientEncryptionOpts { - keyVaultClient: ; - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: - } - -3. Call ``clientEncryption1.createDataKey`` with ``srcProvider`` and these options: - - .. code:: typescript - - class DataKeyOpts { - masterKey: - } - - Store the return value in ``keyID``. - -4. Call ``clientEncryption1.encrypt`` with the value "test" and these options: - - .. code:: typescript - - class EncryptOpts { - keyId : keyID, - algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" - } - - Store the return value in ``ciphertext``. - -5. Create a ``ClientEncryption`` object named ``clientEncryption2`` with these options: - - .. code:: typescript - - ClientEncryptionOpts { - keyVaultClient: ; - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: - } - -6. Call ``clientEncryption2.rewrapManyDataKey`` with an empty ``filter`` and these options: - - .. code:: typescript - - class RewrapManyDataKeyOpts { - provider: dstProvider - masterKey: - } - - Assert that the returned ``RewrapManyDataKeyResult.bulkWriteResult.modifiedCount`` is 1. - -7. Call ``clientEncryption1.decrypt`` with the ``ciphertext``. Assert the return value is "test". - -8. Call ``clientEncryption2.decrypt`` with the ``ciphertext``. Assert the return value is "test". - - -17. On-demand GCP Credentials -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Refer: `Automatic GCP Credentials`_. - -For these cases, create a ClientEncryption_ object :math:`C` with the following -options: - -.. code-block:: typescript - - ClientEncryptionOpts { - keyVaultClient: , - keyVaultNamespace: "keyvault.datakeys", - kmsProviders: { "gcp": {} }, - } - -Case 1: Failure -``````````````` - -Do not run this test case in an environment with a GCP service account is -attached (e.g. any `GCE equivalent runtime -`_). This may be run in an AWS EC2 instance. - -Attempt to create a datakey with :math:`C` using the ``"gcp"`` KMS provider and -following ``DataKeyOpts``: - -.. code-block:: typescript - - class DataKeyOpts { - masterKey: { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle" - } - } - -Expect the attempt to obtain ``"gcp"`` credentials from the environment to fail. - -Case 2: Success -``````````````` - -This test case must run in a Google Compute Engine (GCE) Virtual Machine with a -service account attached. See `drivers-evergreen-tools/.evergreen/csfle/gcpkms -`_ -for scripts to create a GCE instance for testing. The Evergreen task SHOULD set a -``batchtime`` of 14 days to reduce how often this test case runs. - -Attempt to create a datakey with :math:`C` using the ``"gcp"`` KMS provider and -following ``DataKeyOpts``: - -.. code-block:: typescript - - class DataKeyOpts { - masterKey: { - "projectId": "devprod-drivers", - "location": "global", - "keyRing": "key-ring-csfle", - "keyName": "key-name-csfle" - } - } - -This should successfully load and use the GCP credentials of the service account -attached to the virtual machine. - -Expect the key to be successfully created. - -.. _Automatic GCP Credentials: ../client-side-encryption.rst#automatic-gcp-credentials - - -18. Azure IMDS Credentials -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Refer: `Automatic Azure Credentials `_ - -.. _auto-azure: ../client-side-encryption.rst#obtaining-an-access-token-for-azure-key-vault - -The test cases for IMDS communication are specially designed to not require an -Azure environment, while still exercising the core of the functionality. The -design of these test cases encourages an implementation to separate the concerns -of IMDS communication from the logic of KMS key manipulation. The purpose of -these test cases is to ensure drivers will behave appropriately regardless of -the behavior of the IMDS server. - -For these IMDS credentials tests, a simple stand-in IMDS-imitating HTTP server -is available in drivers-evergreen-tools, at ``.evergreen/csfle/fake_azure.py``. -``fake_azure.py`` is a very simple ``bottle.py`` application. For the easiest -use, it is recommended to execute it through ``bottle.py`` (which is a sibling -file in the same directory):: - - python .evergreen/csfle/bottle.py fake_azure:imds - -This will run the ``imds`` Bottle application defined in the ``fake_azure`` -Python module. ``bottle.py`` accepts additional command line arguments to -control the bind host and TCP port (use ``--help`` for more information). - -For each test case, follow the process for obtaining the token as outlined in -the `automatic Azure credentials section `_ with the following -changes: - -1. Instead of the standard IMDS TCP endpoint of `169.254.169.254:80`, - communicate with the running ``fake_azure`` HTTP server. - -2. For each test case, the behavior of the server may be controlled by attaching - an additional HTTP header to the sent request: ``X-MongoDB-HTTP-TestParams``. - - -Case 1: Success -``````````````` - -Do not set an ``X-MongoDB-HTTP-TestParams`` header. - -Upon receiving a response from ``fake_azure``, the driver must decode the -following information: - -1. HTTP status will be ``200 Okay``. -2. The HTTP body will be a valid JSON string. -3. The access token will be the string ``"magic-cookie"``. -4. The expiry duration of the token will be seventy seconds. -5. The token will have a resource of ``"https://vault.azure.net"`` - - -Case 2: Empty JSON -`````````````````` - -This case addresses a server returning valid JSON with invalid content. - -Set ``X-MongoDB-HTTP-TestParams`` to ``case=empty-json``. - -Upon receiving a response: - -1. HTTP status will be ``200 Okay`` -2. The HTTP body will be a valid JSON string. -3. There will be no access token, expiry duration, or resource. - -The test case should ensure that this error condition is handled gracefully. - - -Case 3: Bad JSON -```````````````` - -This case addresses a server returning malformed JSON. - -Set ``X-MongoDB-HTTP-TestParams`` to ``case=bad-json``. - -Upon receiving a response: - -1. HTTP status will be ``200 Okay`` -2. The response body will contain a malformed JSON string. - -The test case should ensure that this error condition is handled gracefully. - - -Case 4: HTTP 404 -```````````````` - -This case addresses a server returning a "Not Found" response. This is -documented to occur spuriously within an Azure environment. - -Set ``X-MongoDB-HTTP-TestParams`` to ``case=404``. - -Upon receiving a response: - -1. HTTP status will be ``404 Not Found``. -2. The response body is unspecified. - -The test case should ensure that this error condition is handled gracefully. - - -Case 5: HTTP 500 -```````````````` - -This case addresses an IMDS server reporting an internal error. This is -documented to occur spuriously within an Azure environment. - -Set ``X-MongoDB-HTTP-TestParams`` to ``case=500``. - -Upon receiving a response: - -1. HTTP status code will be ``500``. -2. The response body is unspecified. - -The test case should ensure that this error condition is handled gracefully. - - -Case 6: Slow Response -````````````````````` - -This case addresses an IMDS server responding very slowly. Drivers should not -halt the application waiting on a peer to communicate. - -Set ``X-MongoDB-HTTP-TestParams`` to ``case=slow``. - -The HTTP response from the ``fake_azure`` server will take at least 1000 seconds -to complete. The request should fail with a timeout. - -19. Azure IMDS Credentials Integration Test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Refer: `Automatic Azure Credentials `_ - -.. _auto-azure: ../client-side-encryption.rst#obtaining-an-access-token-for-azure-key-vault - -For these cases, create a ClientEncryption_ object :math:`C` with the following -options: - -.. code-block:: typescript - - ClientEncryptionOpts { - keyVaultClient: , - keyVaultNamespace: "keyvault.datakeys", - kmsProviders: { "azure": {} }, - } - -Case 1: Failure -``````````````` - -Do not run this test case in an Azure environment with an attached identity. -This may be run in an AWS EC2 instance. - -Attempt to create a datakey with :math:`C` using the ``"azure"`` KMS provider and -following ``DataKeyOpts``: - -.. code-block:: typescript - - class DataKeyOpts { - masterKey: { - "keyVaultEndpoint": "https://keyvault-drivers-2411.vault.azure.net/keys/", - "keyName": "KEY-NAME" - } - } - -Expect the attempt to obtain ``"azure"`` credentials from the environment to fail. - -Case 2: Success -``````````````` - -This test case must run in an Azure environment with an attached identity. -See `drivers-evergreen-tools/.evergreen/csfle/azurekms -`_ -for scripts to create a Azure instance for testing. The Evergreen task SHOULD set a -``batchtime`` of 14 days to reduce how often this test case runs. - -Attempt to create a datakey with :math:`C` using the ``"azure"`` KMS provider and -following ``DataKeyOpts``: - -.. code-block:: typescript - - class DataKeyOpts { - masterKey: { - "keyVaultEndpoint": "https://keyvault-drivers-2411.vault.azure.net/keys/", - "keyName": "KEY-NAME" - } - } - -This should successfully load and use the Azure credentials of the service account -attached to the virtual machine. - -Expect the key to be successfully created. - -20. Bypass creating mongocryptd client when shared library is loaded -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. note:: - - IMPORTANT: If crypt_shared_ is not visible to the operating system's library - search mechanism, this test should be skipped. - - -The following tests that a mongocryptd client is not created when shared library is in-use. - -#. Start a new thread (referred to as ``listenerThread``) - -#. On ``listenerThread``, create a TcpListener on 127.0.0.1 endpoint and port 27021. Start the listener and wait for establishing connections. - If any connection is established, then signal about this to the main thread. - - Drivers MAY pass a different port if they expect their testing infrastructure to be using port 27021. Pass a port that should be free. - -#. Create a MongoClient configured with auto encryption (referred to as ``client_encrypted``) - - Configure the required options. Use the ``local`` KMS provider as follows: - - .. code:: javascript - - { "local": { "key": } } - - Configure with the ``keyVaultNamespace`` set to ``keyvault.datakeys``. - - Configure the following ``extraOptions``: - - .. code:: javascript - - { - "mongocryptdURI": "mongodb://localhost:27021" - } - -#. Use ``client_encrypted`` to insert the document ``{"unencrypted": "test"}`` into ``db.coll``. - -#. Expect no signal from ``listenerThread``. - - - -21. Automatic Data Encryption Keys -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Automatic Data Encryption Keys tests require MongoDB server 7.0+. The tests must not run against a standalone. - -.. note:: - MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2. - libmongocrypt 1.8.0 is configured to use the QEv2 protocol. - -.. note:: - Skip this test on Serverless until MongoDB Serverless enables the QEv2 protocol. Refer: `DRIVERS-2589 `_ - -For each of the following test cases, assume `DB` is a valid open database -handle, and assume a ClientEncryption_ object `CE` created using the following -options:: - - clientEncryptionOptions: { - keyVaultClient: , - keyVaultNamespace: "keyvault.datakeys", - kmsProviders: { - local: { key: base64Decode(LOCAL_MASTERKEY) }, - aws: { - accessKeyId: , - secretAccessKey: - }, - }, - } - -Run each test case with each of these KMS providers: ``aws``, ``local``. The KMS provider name is referred to as ``kmsProvider``. -When testing ``aws``, use the following as the ``masterKey`` option: - -.. code:: javascript - - { - region: "us-east-1", - key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" - } - -When testing ``local``, set ``masterKey`` to ``null``. - -Case 1: Simple Creation and Validation -`````````````````````````````````````` - -This test is the most basic to verify that CreateEncryptedCollection_ created a -collection with queryable encryption enabled. It verifies that the server -rejects an attempt to insert plaintext in an encrypted fields. - -.. _CreateEncryptedCollection: ../client-side-encryption.rst#create-encrypted-collection-helper -.. _MongoClient: ../client-side-encryption.rst#mongoclient-changes - -.. highlight:: typescript -.. default-role:: math - -1. Create a new create-collection options `Opts` including the following:: - - { - encryptedFields: { - fields: [{ - path: "ssn", - bsonType: "string", - keyId: null - }] - } - } - -2. Invoke `CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)` - to obtain a new collection `Coll`. Expect success. -3. Attempt to insert the following document into `Coll`:: - - { - ssn: "123-45-6789" - } - -4. Expect an error from the insert operation that indicates that the document - failed validation. This error indicates that the server expects to receive an - encrypted field for ``ssn``, but we tried to insert a plaintext field via a - client that is unaware of the encryption requirements. - - -Case 2: Missing ``encryptedFields`` -``````````````````````````````````` - -The CreateEncryptedCollection_ helper should not create a regular collection if -there are no ``encryptedFields`` for the collection being created. Instead, it -should generate an error indicated that the ``encryptedFields`` option is -missing. - -1. Create a new empty create-collection options `Opts`. (i.e. it must not - contain any ``encryptedFields`` options.) -2. Invoke `CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)`. -3. Expect the invocation to fail with an error indicating that - ``encryptedFields`` is not defined for the collection, and expect that no - collection was created within the database. It would be *incorrect* for - CreateEncryptedCollection_ to create a regular collection without queryable - encryption enabled. - - -Case 3: Invalid ``keyId`` -````````````````````````` - -The CreateEncryptedCollection_ helper only inspects ``encryptedFields.fields`` -for ``keyId`` of ``null``. CreateEncryptedCollection_ should forward all other -data as-is, even if it would be malformed. The server should generate an error -when attempting to create a collection with such invalid settings. - -.. note:: - - This test is not required if the type system of the driver has a compile-time - check that fields' ``keyId``\ s are of the correct type. - -1. Create a new create-collection options `Opts` including the following:: - - { - encryptedFields: { - fields: [{ - path: "ssn", - bsonType: "string", - keyId: false, - }] - } - } - -2. Invoke `CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)`. -3. Expect an error from the server indicating a validation error at - ``create.encryptedFields.fields.keyId``, which must be a UUID and not a - boolean value. - -Case 4: Insert encrypted value -`````````````````````````````` - -This test is continuation of the case 1 and provides a way to complete inserting -with encrypted value. - -1. Create a new create-collection options `Opts` including the following:: - - { - encryptedFields: { - fields: [{ - path: "ssn", - bsonType: "string", - keyId: null - }] - } - } - -2. Invoke `CreateEncryptedCollection(CE, DB, "testing1", Opts, kmsProvider, masterKey)` - to obtain a new collection `Coll` and data key `key1`. Expect success. -3. Use `CE` to explicitly encrypt the string "123-45-6789" using - algorithm `Unindexed` and data key `key1`. Refer result as `encryptedPayload`. -4. Attempt to insert the following document into `Coll`:: - - { - ssn: - } - - Expect success. - -22. Range Explicit Encryption -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The Range Explicit Encryption tests require MongoDB server 7.0+. The tests must not run against a standalone. - -.. note:: - MongoDB Server 7.0 introduced a backwards breaking change to the Queryable Encryption (QE) protocol: QEv2. - libmongocrypt 1.8.0 is configured to use the QEv2 protocol. - -.. note:: - Skip this test on Serverless until MongoDB Serverless enables the QEv2 protocol. Refer: `DRIVERS-2589 `_ - -Each of the following test cases must pass for each of the supported types (``DecimalNoPrecision``, ``DecimalPrecision``, ``DoublePrecision``, ``DoubleNoPrecision``, ``Date``, ``Int``, and ``Long``), unless it is stated the type should be skipped. - -Tests for ``DecimalNoPrecision`` must only run against a replica set. ``DecimalNoPrecision`` queries are expected to take a long time and may exceed the default mongos timeout. - -Before running each of the following test cases, perform the following Test Setup. - -Test Setup -`````````` -Load the file for the specific data type being tested ``range-encryptedFields-.json``. For example, for ``Int`` load `range-encryptedFields-Int.json `_ as ``encryptedFields``. - -Load the file `key1-document.json `_ as ``key1Document``. - -Read the ``"_id"`` field of ``key1Document`` as ``key1ID``. - -Drop and create the collection ``db.explicit_encryption`` using ``encryptedFields`` as an option. See `FLE 2 CreateCollection() and Collection.Drop() `_. - -Drop and create the collection ``keyvault.datakeys``. - -Insert ``key1Document`` in ``keyvault.datakeys`` with majority write concern. - -Create a MongoClient named ``keyVaultClient``. - -Create a ClientEncryption object named ``clientEncryption`` with these options: - -.. code:: typescript - - ClientEncryptionOpts { - keyVaultClient: ; - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: { "local": { "key": } } - } - -Create a MongoClient named ``encryptedClient`` with these ``AutoEncryptionOpts``: - -.. code:: typescript - - AutoEncryptionOpts { - keyVaultNamespace: "keyvault.datakeys"; - kmsProviders: { "local": { "key": } } - bypassQueryAnalysis: true - } - -The remaining tasks require setting ``RangeOpts``. `Test Setup: RangeOpts`_ lists the values to use for ``RangeOpts`` for each of the supported data types. - -Use ``clientEncryption`` to encrypt these values: 0, 6, 30, and 200. Ensure the type matches with the type of the encrypted field. For example, if the encrypted field is ``encryptedDoubleNoPrecision`` encrypt the value 6.0. - -Encrypt these values with the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - contentionFactor: 0 - } - -Use ``encryptedClient`` to insert these documents into ``db.explicit_encryption``: - -- ``{ "encrypted": , _id: 0 }`` -- ``{ "encrypted": , _id: 1 }`` -- ``{ "encrypted": , _id: 2 }`` -- ``{ "encrypted": , _id: 3 }`` - - -Test Setup: RangeOpts -````````````````````` -This section lists the values to use for ``RangeOpts`` for each of the supported data types, since each data type requires a different ``RangeOpts``. - -Each test listed in the cases below must pass for all supported data types unless it is stated the type should be skipped. - -#. DecimalNoPrecision - - .. code:: typescript - - class RangeOpts { - sparsity: 1 - } - -#. DecimalPrecision - - .. code:: typescript - - class RangeOpts { - min: { "$numberDecimal": "0" }, - max: { "$numberDecimal": "200" }, - sparsity: 1, - precision: 2 - } - -#. DoubleNoPrecision - - .. code:: typescript - - class RangeOpts { - sparsity: 1 - } - -#. DoublePrecision - - .. code:: typescript - - class RangeOpts { - min: { "$numberDouble": "0" }, - max: { "$numberDouble": "200" }, - sparsity: 1, - precision: 2 - } - -#. Date - - .. code:: typescript - - class RangeOpts { - min: {"$date": { "$numberLong": "0" } } , - max: {"$date": { "$numberLong": "200" } }, - sparsity: 1 - } - -#. Int - - .. code:: typescript - - class RangeOpts { - min: {"$numberInt": "0" } , - max: {"$numberInt": "200" }, - sparsity: 1 - } - -#. Long - - .. code:: typescript - - class RangeOpts { - min: {"$numberLong": "0" } , - max: {"$numberLong": "200" }, - sparsity: 1 - } - -Case 1: can decrypt a payload -````````````````````````````` -Use ``clientEncryption.encrypt()`` to encrypt the value 6. Ensure the encoded BSON type matches the type of the encrypted field. For example, if the encrypted field is ``encryptedLong`` encrypt the 64-bit BSON long value 6, not the 32-bit BSON int value 6. - -Store the result in ``insertPayload``. - -Encrypt with the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - contentionFactor: 0 - } - -Use ``clientEncryption`` to decrypt ``insertPayload``. Assert the returned value equals 6. - -.. note:: - - The type returned by ``clientEncryption.decrypt()`` may differ from the input type to ``clientEncryption.encrypt()`` depending on how the driver unmarshals BSON numerics to language native types. - Example: a driver may unmarshal a BSON long to a numeric type that does not distinguish between int64 and int32. - -Case 2: can find encrypted range and return the maximum -``````````````````````````````````````````````````````` -Use ``clientEncryption.encryptExpression()`` to encrypt this query: - -.. code:: javascript - - //convert 6 and 200 to match the type of the encrypted field. - {"$and": [{"encrypted": {"$gte": 6}}, {"encrypted": {"$lte": 200}}]} - -Use the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts`` to encrypt the query: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - queryType: "rangePreview", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``findPayload`` and sort the results by ``_id``. - -Assert these three documents ``{ "encrypted": 6 }, { "encrypted": 30 }, { "encrypted": 200}`` are returned. - - -Case 3: can find encrypted range and return the minimum -``````````````````````````````````````````````````````` -Use ``clientEncryption.encryptExpression()`` to encrypt this query: - - -.. code:: javascript - - //convert 0 and 6 to match the type of the encrypted field. - {"$and": [{"encrypted": {"$gte": 0}}, {"encrypted": {"$lte": 6}}]} - -Use the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts`` to encrypt the query: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - queryType: "rangePreview", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``findPayload`` and sort the results by ``_id``. - -Assert these two documents ``{ "encrypted": 0 }, { "encrypted": 6 }`` are returned. - -Case 4: can find encrypted range with an open range query -````````````````````````````````````````````````````````` -Use ``clientEncryption.encryptExpression()`` to encrypt this query: - -.. code:: javascript - - //convert 30 to match the type of the encrypted field. - {"$and": [{"encrypted": {"$gt": 30}}]} - -Use the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts`` to encrypt the query: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - queryType: "rangePreview", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``findPayload`` and sort the results by ``_id``. - -Assert that only this document ``{ "encrypted": 200 }`` is returned. - -Case 5: can run an aggregation expression inside $expr -`````````````````````````````````````````````````````` -Use ``clientEncryption.encryptExpression()`` to encrypt this query: - -.. code:: javascript - - {'$and': [ { '$lt': [ '$encrypted', 30 ] } ] } } - -Use the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts`` to encrypt the query: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - queryType: "rangePreview", - contentionFactor: 0 - } - -Store the result in ``findPayload``. - -Use ``encryptedClient`` to run a "find" operation on the ``db.explicit_encryption`` collection with the filter ``{'$expr' : }`` and sort the results by ``_id``. - -Assert that these two documents ``{ "encrypted": 0 }, { "encrypted": 6 }`` are returned. - -Case 6: encrypting a document greater than the maximum errors -````````````````````````````````````````````````````````````` -This test case should be skipped if the encrypted field is ``encryptedDoubleNoPrecision`` or ``encryptedDecimalNoPrecision``. - -Use ``clientEncryption.encrypt()`` to try to encrypt the value 201 with the matching ``RangeOpts`` listed in `Test Setup: RangeOpts`_ and these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - contentionFactor: 0 - } - -Ensure 201 matches the type of the encrypted field. The error should be raised because 201 is greater than the maximum value in ``RangeOpts``. - -Assert that an error was raised. - -Case 7: encrypting a document of a different type errors -```````````````````````````````````````````````````````` -This test case should be skipped if the encrypted field is ``encryptedDoubleNoPrecision`` or ``encryptedDecimalNoPrecision``. - -For all the tests below use these ``EncryptOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - contentionFactor: 0 - } - -If the encrypted field is ``encryptedInt`` encrypt ``{ "encryptedInt": { "$numberDouble": "6" } }``. -Otherwise, encrypt ``{ "encrypted": { "$numberInt": "6" }``. -Assert an error was raised. - - -Case 8: setting precision errors if the type is not a double -```````````````````````````````````````````````````````````` -This test case should be skipped if the encrypted field is ``encryptedDoublePrecision`` or ``encryptedDoubleNoPrecision`` or ``encryptedDecimalPrecision`` or ``encryptedDecimalNoPrecision``. - -Use ``clientEncryption.encrypt()`` to try to encrypt the value 6 with these ``EncryptOpts`` and these ``RangeOpts``: - -.. code:: typescript - - class EncryptOpts { - keyId : - algorithm: "RangePreview", - contentionFactor: 0 - } - - class RangeOpts { - min: 0, - max: 200, - sparsity: 1, - precision: 2, - } - -Assert an error was raised. diff --git a/src/test/spec/json/client-side-encryption/benchmarks.md b/src/test/spec/json/client-side-encryption/benchmarks.md new file mode 100644 index 000000000..64e1694a8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/benchmarks.md @@ -0,0 +1,51 @@ +# In-Use Encryption: Benchmarks + +______________________________________________________________________ + +## Benchmarking Bindings + +Drivers are encouraged to benchmark the bindings to libmongocrypt. Benchmarking may help to identify performance issues +due to the cost of calling between the native language and the C library. + +A handle to libmongocrypt (`mongocrypt_t`) is needed for the benchmark. In the public driver API, `mongocrypt_t` is an +implementation detail contained in a `MongoClient`. The bindings API may more directly interface `mongocrypt_t`. +Example: the Java bindings API contains a +[MongoCrypt class](https://github.com/mongodb/mongo-java-driver/blob/main/mongodb-crypt/src/main/com/mongodb/internal/crypt/capi/MongoCrypt.java) +closely wrapping the `mongocrypt_t`. + +If possible, drivers are encouraged to use the bindings API and mock responses from the MongoDB server. This may help to +narrow the scope of the benchmarked code. See +[BenchmarkRunner.java](https://github.com/mongodb/libmongocrypt/blob/b81e66e0208d13e07c2e5e60b3170f0cfc61e1e2/bindings/java/mongocrypt/benchmarks/src/main/java/com/mongodb/crypt/benchmark/BenchmarkRunner.java) +for an example using the Java bindings API. If that is not possible, the benchmark can be implemented using the +`MongoClient` API, and the benchmark will include the time spent communicating with the MongoDB server. + +### Benchmarking Bulk Decryption + +Set up the benchmark data: + +- Create a data key with the "local" KMS provider. +- Encrypt 1500 string values of the form `value 0001`, `value 0002`, `value 0003`, ... with the algorithm + `AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic`. +- Create a document of the form: + `{ "key0001": , "key0002": , "key0003": }`. +- Create a handle to `mongocrypt_t`. This may be through the bindings API (preferred) or through a `MongoClient` + configured with `AutoEncryptionOpts`. + +Warm up the benchmark: + +- Use the handle to decrypt the document repeatedly for one second. + +Run the benchmark. Repeat benchmark for thread counts: (1, 2, 8, 64): + +- Start threads. Use the same handle between all threads (`mongocrypt_t` is thread-safe). +- In each thread: decrypt the document repeatedly for one second. +- Count the number of decrypt operations performed (ops/sec). +- Repeat 10 times. + +Produce results: + +- Report the median result of the ops/sec for each thread count. + +**Note:** The `mongocrypt_t` handle caches the decrypted Data Encryption Key (DEK) for a fixed time period of one +minute. If the benchmark exceeds one minute, the DEK will be requested again from the key vault collection. Reporting +the median of trials is expected to prevent this impacting results. diff --git a/src/test/spec/json/client-side-encryption/legacy/azureKMS.json b/src/test/spec/json/client-side-encryption/legacy/azureKMS.json index afecf40b0..b0f511137 100644 --- a/src/test/spec/json/client-side-encryption/legacy/azureKMS.json +++ b/src/test/spec/json/client-side-encryption/legacy/azureKMS.json @@ -78,6 +78,17 @@ "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } + }, + "encrypted_string_kmip_delegated": { + "encrypt": { + "keyId": [ + { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba6" + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } } }, "bsonType": "object" diff --git a/src/test/spec/json/client-side-encryption/legacy/azureKMS.yml b/src/test/spec/json/client-side-encryption/legacy/azureKMS.yml index b3c1f6947..a402973ad 100644 --- a/src/test/spec/json/client-side-encryption/legacy/azureKMS.yml +++ b/src/test/spec/json/client-side-encryption/legacy/azureKMS.yml @@ -4,7 +4,7 @@ database_name: &database_name "default" collection_name: &collection_name "default" data: [] -json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip_delegated': {'encrypt': {'keyId': [{'$uuid': '7411e9af-c688-4df7-8143-5e60ae96cba6'}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} key_vault_data: [{'_id': {'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'n+HWZ0ZSVOYA3cvQgP7inN4JSXfOH85IngmeQxRpQHjCCcqT3IFqEWNlrsVHiz3AELimHhX4HKqOLWMUeSIT6emUDDoQX9BAv8DR1+E1w4nGs/NyEneac78EYFkK3JysrFDOgl2ypCCTKAypkn9CkAx1if4cfgQE93LW4kczcyHdGiH36CIxrCDGv1UzAvERN5Qa47DVwsM6a+hWsF2AAAJVnF0wYLLJU07TuRHdMrrphPWXZsFgyV+lRqJ7DDpReKNO8nMPLV/mHqHBHGPGQiRdb9NoJo8CvokGz4+KE8oLwzKf6V24dtwZmRkrsDV4iOhvROAzz+Euo1ypSkL3mw==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1601573901680'}}, 'updateDate': {'$date': {'$numberLong': '1601573901680'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'azure', 'keyVaultEndpoint': 'key-vault-csfle.vault.azure.net', 'keyName': 'key-name-csfle'}, 'keyAltNames': ['altname', 'azure_altname']}] tests: diff --git a/src/test/spec/json/client-side-encryption/legacy/explain.json b/src/test/spec/json/client-side-encryption/legacy/explain.json index 0e451e481..8ca3b48d3 100644 --- a/src/test/spec/json/client-side-encryption/legacy/explain.json +++ b/src/test/spec/json/client-side-encryption/legacy/explain.json @@ -1,7 +1,7 @@ { "runOn": [ { - "minServerVersion": "4.1.10" + "minServerVersion": "7.0.0" } ], "database_name": "default", diff --git a/src/test/spec/json/client-side-encryption/legacy/explain.yml b/src/test/spec/json/client-side-encryption/legacy/explain.yml index c0dd9c57c..325928a72 100644 --- a/src/test/spec/json/client-side-encryption/legacy/explain.yml +++ b/src/test/spec/json/client-side-encryption/legacy/explain.yml @@ -1,5 +1,5 @@ runOn: - - minServerVersion: "4.1.10" + - minServerVersion: "7.0.0" database_name: &database_name "default" collection_name: &collection_name "default" diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.json index dcc3983ae..9b28df2f9 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.yml index 1ef8197c2..51e7a5675 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-BypassQueryAnalysis.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.json index e47c689bf..868095e1e 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", @@ -131,6 +130,9 @@ "command": { "compactStructuredEncryptionData": "default" } + }, + "result": { + "ok": 1 } } ], diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.yml index 8e88cc520..6c1965a5a 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Compact.yml @@ -1,7 +1,6 @@ -# Requires libmongocrypt 1.8.0. +# Requires libmongocrypt 1.8.0. libmongocrypt 1.10.0 has a bug (MONGOCRYPT-699) that may cause this test to fail on server version 7. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -23,6 +22,8 @@ tests: arguments: command: compactStructuredEncryptionData: *collection_name + result: + ok: 1 expectations: - command_started_event: command: diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.json index d5b04b3ea..c266aa6b8 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.json @@ -55,6 +55,38 @@ "result": { "errorContains": "Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption." } + }, + { + "name": "assertCollectionNotExists", + "object": "testRunner", + "arguments": { + "database": "default", + "collection": "enxcol_.encryptedCollection.esc" + } + }, + { + "name": "assertCollectionNotExists", + "object": "testRunner", + "arguments": { + "database": "default", + "collection": "enxcol_.encryptedCollection.ecc" + } + }, + { + "name": "assertCollectionNotExists", + "object": "testRunner", + "arguments": { + "database": "default", + "collection": "enxcol_.encryptedCollection.ecoc" + } + }, + { + "name": "assertCollectionNotExists", + "object": "testRunner", + "arguments": { + "database": "default", + "collection": "encryptedCollection" + } } ] } diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.yml index f55001a42..5cc6ead0f 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection-OldServer.yml @@ -37,3 +37,25 @@ tests: collection: "encryptedCollection" result: errorContains: "Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption." + # Assert no collections were created. + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &esc_collection_name "enxcol_.encryptedCollection.esc" + # ecc collection is no longer created for QEv2 + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &ecc_collection_name "enxcol_.encryptedCollection.ecc" + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: &ecoc_collection_name "enxcol_.encryptedCollection.ecoc" + - name: assertCollectionNotExists + object: testRunner + arguments: + database: *database_name + collection: encryptedCollection diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.json index 819d2eec3..c324be8ab 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", @@ -158,9 +157,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -343,9 +339,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -851,9 +844,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -1048,9 +1038,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -1367,9 +1354,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -1635,9 +1619,6 @@ "command": { "create": "encryptedCollection", "encryptedFields": { - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.yml index ea33865a2..43dbccfc3 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-CreateCollection.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -101,10 +100,6 @@ tests: command: create: *encrypted_collection_name encryptedFields: &encrypted_fields_expectation { - # Expect state collections are not included in the encryptedFields sent to the server. - "escCollection": null, - "ecocCollection": null, - "eccCollection": null, "fields": [ { "path": "firstName", @@ -939,4 +934,4 @@ tests: collection: *encrypted_collection_name result: # Expect error due to server constraints added in SERVER-74069 - errorContains: "Encrypted State Collection name should follow" \ No newline at end of file + errorContains: "Encrypted State Collection name should follow" diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.json index 905d3c945..1fb4c1d1b 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.yml index 8ed6e1d89..952551ba3 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-DecryptExistingData.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.json index e4150eab8..ddfe57b00 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.yml index c9a8aced5..4e1a59c93 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Delete.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json index b579979e9..bdc5c99bc 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml index 13d1129c8..8767132e6 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.json index 0a84d7365..8e0c6dafa 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.yml index 3d6e5ce95..119da443f 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFields-vs-jsonSchema.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.json index 3e0905ead..1c0a057ca 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.yml index 4d85e304d..ec91ebf1c 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-EncryptedFieldsMap-defaults.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.json index 4606fbb93..c5e689a3d 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.yml index 1a2211130..7849b6032 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-FindOneAndUpdate.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.json index c7149d1f5..6e156ffc6 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.yml index 27520226b..2acd97585 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Indexed.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.json index 008b0c959..48280f5bd 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.yml index aeff4a88f..ddba6c743 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-InsertFind-Unindexed.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.json index 0b7e86bca..1e655f0a9 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", @@ -55,7 +54,7 @@ "key_vault_data": [], "tests": [ { - "description": "FLE2 encrypt fails with mising key", + "description": "FLE2 encrypt fails with missing key", "clientOptions": { "autoEncryptOpts": { "kmsProviders": { @@ -86,7 +85,7 @@ ] }, { - "description": "FLE2 decrypt fails with mising key", + "description": "FLE2 decrypt fails with missing key", "clientOptions": { "autoEncryptOpts": { "kmsProviders": { diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.yml index 0ff40dbe8..f4fbbeb5c 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-MissingKey.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] @@ -20,7 +19,7 @@ data: [ encrypted_fields: {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} key_vault_data: [] tests: - - description: "FLE2 encrypt fails with mising key" + - description: "FLE2 encrypt fails with missing key" clientOptions: autoEncryptOpts: kmsProviders: @@ -31,7 +30,7 @@ tests: document: { _id: 1, encryptedIndexed: "123" } result: errorContains: "not all keys requested were satisfied" - - description: "FLE2 decrypt fails with mising key" + - description: "FLE2 decrypt fails with missing key" clientOptions: autoEncryptOpts: kmsProviders: diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.json index 185691d61..a6843c473 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.yml index 66fb5e262..afb79d526 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-NoEncryption.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.json deleted file mode 100644 index dea821bd1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.json +++ /dev/null @@ -1,509 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Date. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.yml deleted file mode 100644 index b03e4d9a2..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Aggregate.yml +++ /dev/null @@ -1,242 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Date. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.json deleted file mode 100644 index 9e4f52587..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.json +++ /dev/null @@ -1,1840 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gte": { - "$date": { - "$numberLong": "0" - } - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$lt": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$lte": { - "$date": { - "$numberLong": "1" - } - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$lt": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Find with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "200" - } - } - } - } - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - }, - "$lt": { - "$date": { - "$numberLong": "2" - } - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gte": { - "$date": { - "$numberLong": "0" - } - }, - "$lte": { - "$date": { - "$numberLong": "200" - } - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$in": [ - { - "$date": { - "$numberLong": "0" - } - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - } - ] - }, - { - "description": "Insert out of range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "-1" - } - } - } - }, - "result": { - "errorContains": "value must be greater than or equal to the minimum value" - } - } - ] - }, - { - "description": "Insert min and max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 200, - "encryptedDate": { - "$date": { - "$numberLong": "200" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 200, - "encryptedDate": { - "$date": { - "$numberLong": "200" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gte": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "1" - } - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$lt": { - "$date": { - "$numberLong": "1" - } - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$lte": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$lt": { - "$date": { - "$numberLong": "0" - } - } - } - } - } - ] - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Aggregate with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "200" - } - } - } - } - } - ] - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - }, - "$lt": { - "$date": { - "$numberLong": "2" - } - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$gte": { - "$date": { - "$numberLong": "0" - } - }, - "$lte": { - "$date": { - "$numberLong": "200" - } - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - }, - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDate": { - "$in": [ - { - "$date": { - "$numberLong": "0" - } - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$numberDouble": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gte": { - "$numberDouble": "0" - } - } - } - }, - "result": { - "errorContains": "value type is a date" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.yml deleted file mode 100644 index 6e5f80265..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Correctness.yml +++ /dev/null @@ -1,423 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } } } - - name: find - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} - result: - errorContains: must be greater than the range minimum - - - description: "Find with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} - result: - errorContains: must be less than the range maximum - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $date: { $numberLong: "0" } } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedDate: { $date: { $numberLong: "1" } } } - result: [*doc1] - - - description: "Find with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $gte: { $date: {$numberLong: "0"}}, $lte: { $date: {$numberLong: "200"} } } } - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } - result: [*doc0] - - - description: "Insert out of range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: { _id: 0, encryptedDate: {$date: { $numberLong: "-1" }}} - result: - errorContains: value must be greater than or equal to the minimum value - - - description: "Insert min and max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: *doc0 - - name: insertOne - arguments: - document: &doc200 { _id: 200, encryptedDate: { $date: { $numberLong: "200" } }} - - name: find - arguments: - filter: {} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc200] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} } - result: - errorContains: must be greater than the range minimum - - - description: "Aggregate with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} } - result: - errorContains: must be less than the range maximum - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $date: { $numberLong: "0" } } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $date: { $numberLong: "1" } } } } - result: [*doc1] - - - description: "Aggregate with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $gte: {$date: {$numberLong: "0"}}, $lte: {$date: {$numberLong: "200"}} } } } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedDate: { $numberDouble: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedDate: { $gte: { $numberDouble: "0" } }} - result: - # expect an error mongocryptd. - errorContains: "value type is a date" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.json deleted file mode 100644 index 7f4094f50..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.json +++ /dev/null @@ -1,437 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Date. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.yml deleted file mode 100644 index e15d19d84..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Delete.yml +++ /dev/null @@ -1,183 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Date. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } - - name: deleteOne - arguments: - filter: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.json deleted file mode 100644 index 5ec060160..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.json +++ /dev/null @@ -1,515 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Date. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - }, - "update": { - "$set": { - "encryptedDate": { - "$date": { - "$numberLong": "2" - } - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedDate": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.yml deleted file mode 100644 index 5f25f9756..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-FindOneAndUpdate.yml +++ /dev/null @@ -1,240 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Date. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } - - name: findOneAndUpdate - arguments: - filter: { encryptedDate: { $gt: {$date: {$numberLong: "0"}}} } - update: { "$set": { "encryptedDate": {$date: {$numberLong: "2"}}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedDate": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.json deleted file mode 100644 index efce1511c..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.json +++ /dev/null @@ -1,500 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Date. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.yml deleted file mode 100644 index d9f9a10d5..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-InsertFind.yml +++ /dev/null @@ -1,236 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Date. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" }} } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" }} } - - name: find - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" }} } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.json deleted file mode 100644 index 7f9fadcda..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.json +++ /dev/null @@ -1,517 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Date. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDate": { - "$date": { - "$numberLong": "0" - } - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDate": { - "$date": { - "$numberLong": "1" - } - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedDate": { - "$gt": { - "$date": { - "$numberLong": "0" - } - } - } - }, - "update": { - "$set": { - "encryptedDate": { - "$date": { - "$numberLong": "2" - } - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDate": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDate", - "bsonType": "date", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$date": { - "$numberLong": "0" - } - }, - "max": { - "$date": { - "$numberLong": "200" - } - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDate": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.yml deleted file mode 100644 index 097d54c18..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Date-Update.yml +++ /dev/null @@ -1,253 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Date. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } }} - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } }} - - name: updateOne - arguments: - filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} - update: { "$set": { "encryptedDate": { $date: { $numberLong: "2" } }}} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedDate": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDate": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDate": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.json deleted file mode 100644 index fb129392b..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.json +++ /dev/null @@ -1,1903 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Decimal. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.yml deleted file mode 100644 index 506dc086b..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Aggregate.yml +++ /dev/null @@ -1,1688 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Decimal. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", - "subType": "00" - } - } - ] - } - \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.json deleted file mode 100644 index 5120aecb7..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.json +++ /dev/null @@ -1,1156 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gte": { - "$numberDecimal": "0.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "1.0" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$lt": { - "$numberDecimal": "1.0" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$lte": { - "$numberDecimal": "1.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0.0" - }, - "$lt": { - "$numberDecimal": "2.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$in": [ - { - "$numberDecimal": "0.0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gte": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "1.0" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$lt": { - "$numberDecimal": "1.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$lte": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0.0" - }, - "$lt": { - "$numberDecimal": "2.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalNoPrecision": { - "$in": [ - { - "$numberDecimal": "0.0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberInt": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gte": { - "$numberInt": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.yml deleted file mode 100644 index 7015e64f1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Correctness.yml +++ /dev/null @@ -1,294 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } - result: [*doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } - result: [*doc0] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } } - result: [*doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedDecimalNoPrecision: { $numberInt: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gte: { $numberInt: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.json deleted file mode 100644 index de81159b4..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.json +++ /dev/null @@ -1,1111 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Decimal. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.yml deleted file mode 100644 index 4e80cd0c3..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Delete.yml +++ /dev/null @@ -1,906 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Decimal. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.json deleted file mode 100644 index 36cf91c88..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.json +++ /dev/null @@ -1,1907 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Decimal. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - }, - "update": { - "$set": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.yml deleted file mode 100644 index e0f0b2b1c..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-FindOneAndUpdate.yml +++ /dev/null @@ -1,1685 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Decimal. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: {$numberDecimal: "0"}} } - update: { "$set": { "encryptedDecimalNoPrecision": {$numberDecimal: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedDecimalNoPrecision": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.json deleted file mode 100644 index 6b5a642aa..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.json +++ /dev/null @@ -1,1894 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Decimal. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.yml deleted file mode 100644 index 9647dfe85..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-InsertFind.yml +++ /dev/null @@ -1,1681 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Decimal. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - - name: find - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.json deleted file mode 100644 index 8cfb7b525..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.json +++ /dev/null @@ -1,1911 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Decimal. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - }, - "update": { - "$set": { - "encryptedDecimalNoPrecision": { - "$numberDecimal": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDecimalNoPrecision": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalNoPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.yml deleted file mode 100644 index 86626403f..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Decimal-Update.yml +++ /dev/null @@ -1,1698 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. - topology: [ "replicaset" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Decimal. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } - - name: updateOne - arguments: - filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } - update: { "$set": { "encryptedDecimalNoPrecision": { $numberDecimal: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedDecimalNoPrecision": { - "$gt": { - "$binary": { - "base64": "DeFiAAADcGF5bG9hZACxYgAABGcAnWIAAAMwAH0AAAAFZAAgAAAAAJu2KgiI8vM+kz9qD3ZQzFQY5qbgYqCqHG5R4jAlnlwXBXMAIAAAAAAAUXxFXsz764T79sGCdhxvNd5b6E/9p61FonsHyEIhogVsACAAAAAAt19RL3Oo5ni5L8kcvgOJYLgVYyXJExwP8pkuzLG7f/kAAzEAfQAAAAVkACAAAAAAPQPvL0ARjujSv2Rkm8r7spVsgeC1K3FWcskGGZ3OdDIFcwAgAAAAACgNn660GmefR8jLqzgR1u5O+Uocx9GyEHiBqVGko5FZBWwAIAAAAADflr+fsnZngm6KRWYgHa9JzK+bXogWl9evBU9sQUHPHQADMgB9AAAABWQAIAAAAAD2Zi6kcxmaD2mY3VWrP+wYJMPg6cSBIYPapxaFQxYFdQVzACAAAAAAM/cV36BLBY3xFBXsXJY8M9EHHOc/qrmdc2CJmj3M89gFbAAgAAAAAOpydOrKxx6m2gquSDV2Vv3w10GocmNCFeOo/fRhRH9JAAMzAH0AAAAFZAAgAAAAAOaNqI9srQ/mI9gwbk+VkizGBBH/PPWOVusgnfPk3tY1BXMAIAAAAAAc96O/pwKCmHCagT6T/QV/wz4vqO+R22GsZ1dse2Vg6QVsACAAAAAAgzIak+Q3UFLTHXPmJ+MuEklFtR3eLtvM+jdKkmGCV/YAAzQAfQAAAAVkACAAAAAA0XlQgy/Yu97EQOjronl9b3dcR1DFn3deuVhtTLbJZHkFcwAgAAAAACoMnpVl6EFJak8A+t5N4RFnQhkQEBnNAx8wDqmq5U/dBWwAIAAAAACR26FJif673qpwF1J1FEkQGJ1Ywcr/ZW6JQ7meGqzt1QADNQB9AAAABWQAIAAAAAAOtpNexRxfv0yRFvZO9DhlkpU4mDuAb8ykdLnE5Vf1VAVzACAAAAAAeblFKm/30orP16uQpZslvsoS8s0xfNPIBlw3VkHeekYFbAAgAAAAAPEoHj87sYE+nBut52/LPvleWQBzB/uaJFnosxp4NRO2AAM2AH0AAAAFZAAgAAAAAIr8xAFm1zPmrvW4Vy5Ct0W8FxMmyPmFzdWVzesBhAJFBXMAIAAAAABYeeXjJEzTHwxab6pUiCRiZjxgtN59a1y8Szy3hfkg+gVsACAAAAAAJuoY4rF8mbI+nKb+5XbZShJ8191o/e8ZCRHE0O4Ey8MAAzcAfQAAAAVkACAAAAAAl+ibLk0/+EwoqeC8S8cGgAtjtpQWGEZDsybMPnrrkwEFcwAgAAAAAHPPBudWgQ+HUorLDpJMqhS9VBF2VF5aLcxgrM1s+yU7BWwAIAAAAAAcCcBR2Vyv5pAFbaOU97yovuOi1+ATDnLLcAUqHecXcAADOAB9AAAABWQAIAAAAACR9erwLTb+tcWFZgJ2MEfM0PKI9uuwIjDTHADRFgD+SQVzACAAAAAAcOop8TXsGUVQoKhzUllMYWxL93xCOkwtIpV8Q6hiSYYFbAAgAAAAAKXKmh4V8veYwob1H03Q3p3PN8SRAaQwDT34KlNVUjiDAAM5AH0AAAAFZAAgAAAAALv0vCPgh7QpmM8Ug6ad5ioZJCh7pLMdT8FYyQioBQ6KBXMAIAAAAADsCPyIG8t6ApQkRk1fX/sfc1kpuWCWP8gAEpnYoBSHrQVsACAAAAAAJe/r67N6d8uTiogvfoR9rEXbIDjyLb9EVdqkayFFGaYAAzEwAH0AAAAFZAAgAAAAAIW4AxJgYoM0pcNTwk1RSbyjZGIqgKL1hcTJmNrnZmoPBXMAIAAAAAAZpfx3EFO0vY0f1eHnE0PazgqeNDTaj+pPJMUNW8lFrAVsACAAAAAAP+Um2vwW6Bj6vuz9DKz6+6aWkoKoEmFNoiz/xXm7lOsAAzExAH0AAAAFZAAgAAAAAKliO6L9zgeuufjj174hvmQGNRbmYYs9yAirL7OxwEW3BXMAIAAAAAAqU7vs3DWUQ95Eq8OejwWnD0GuXd+ASi/uD6S0l8MM1QVsACAAAAAAb9legYzsfctBPpHyl7YWpPmLr5QiNZFND/50N1vv2MUAAzEyAH0AAAAFZAAgAAAAAOGQcCBkk+j/Kzjt/Cs6g3BZPJG81wIHBS8JewHGpgk+BXMAIAAAAABjrxZXWCkdzrExwCgyHaafuPSQ4V4x2k9kUCAqUaYKDQVsACAAAAAADBU6KefT0v8zSmseaMNmQxKjJar72y7MojLFhkEHqrUAAzEzAH0AAAAFZAAgAAAAAPmCNEt4t97waOSd5hNi2fNCdWEkmcFJ37LI9k4Az4/5BXMAIAAAAABX7DuDPNg+duvELf3NbLWkPMFw2HGLgWGHyVWcPvSNCAVsACAAAAAAS7El1FtZ5STh8Q1FguvieyYX9b2DF1DFVsb9hzxXYRsAAzE0AH0AAAAFZAAgAAAAAD4vtVUYRNB+FD9yoQ2FVJH3nMeJeKbi6eZfth638YqbBXMAIAAAAAANCuUB4OdmuD6LaDK2f3vaqfgYYvg40wDXOBbcFjTqLwVsACAAAAAA9hqC2VoJBjwR7hcQ45xO8ZVojwC83jiRacCaDj6Px2gAAzE1AH0AAAAFZAAgAAAAAJPIRzjmTjbdIvshG6UslbEOd797ZSIdjGAhGWxVQvK1BXMAIAAAAABgmJ0Jh8WLs9IYs/a7DBjDWd8J3thW/AGJK7zDnMeYOAVsACAAAAAAi9zAsyAuou2oiCUHGc6QefLUkACa9IgeBhGu9W/r0X8AAzE2AH0AAAAFZAAgAAAAAABQyKQPoW8wGPIqnsTv69+DzIdRkohRhOhDmyVHkw9WBXMAIAAAAAAqWA2X4tB/h3O1Xlawtz6ndI6WaTwgU1QYflL35opu5gVsACAAAAAAWI/Gj5aZMwDIxztqmVL0g5LBcI8EdKEc2UA28pnekQoAAzE3AH0AAAAFZAAgAAAAACB7NOyGQ1Id3MYnxtBXqyZ5Ul/lHH6p1b10U63DfT6bBXMAIAAAAADpOryIcndxztkHSfLN3Kzq29sD8djS0PspDSqERMqokQVsACAAAAAADatsMW4ezgnyi1PiP7xk+gA4AFIN/fb5uJqfVkjg4UoAAzE4AH0AAAAFZAAgAAAAAKVfXLfs8XA14CRTB56oZwV+bFJN5BHraTXbqEXZDmTkBXMAIAAAAAASRWTsfGOpqdffiOodoqIgBzG/yzFyjR5CfUsIUIWGpgVsACAAAAAAkgCHbCwyX640/0Ni8+MoYxeHUiC+FSU4Mn9jTLYtgZgAAzE5AH0AAAAFZAAgAAAAAH/aZr4EuS0/noQR9rcF8vwoaxnxrwgOsSJ0ys8PkHhGBXMAIAAAAACd7ObGQW7qfddcvyxRTkPuvq/PHu7+6I5dxwS1Lzy5XAVsACAAAAAA3q0eKdV7KeU3pc+CtfypKR7BPxwaf30yu0j9FXeOOboAAzIwAH0AAAAFZAAgAAAAAKvlcpFFNq0oA+urq3w6d80PK1HHHw0H0yVWvU9aHijXBXMAIAAAAADWnAHQ5Fhlcjawki7kWzdqjM2f6IdGJblojrYElWjsZgVsACAAAAAAO0wvY66l24gx8nRxyVGC0QcTztIi81Kx3ndRhuZr6W4AAzIxAH0AAAAFZAAgAAAAAH/2aMezEOddrq+dNOkDrdqf13h2ttOnexZsJxG1G6PNBXMAIAAAAABNtgnibjC4VKy5poYjvdsBBnVvDTF/4mmEAxsXVgZVKgVsACAAAAAAqvadzJFLqQbs8WxgZ2D2X+XnaPSDMLCVVgWxx5jnLcYAAzIyAH0AAAAFZAAgAAAAAF2wZoDL6/V59QqO8vdRZWDpXpkV4h4KOCSn5e7x7nmzBXMAIAAAAADLZBu7LCYjbThaVUqMK14H/elrVOYIKJQCx4C9Yjw37gVsACAAAAAAEh6Vs81jLU204aGpL90fmYTm5i5R8/RT1uIbg6VU3HwAAzIzAH0AAAAFZAAgAAAAAH27yYaLn9zh2CpvaoomUPercSfJRUmBY6XFqmhcXi9QBXMAIAAAAAAUwumVlIYIs9JhDhSj0R0+59psCMsFk94E62VxkPt42QVsACAAAAAAT5x2hCCd2bpmpnyWaxas8nSxTc8e4C9DfKaqr0ABEysAAzI0AH0AAAAFZAAgAAAAALMg2kNAO4AFFs/mW3In04yFeN4AP6Vo0klyUoT06RquBXMAIAAAAAAgGWJbeIdwlpqXCyVIYSs0dt54Rfc8JF4b8uYc+YUj0AVsACAAAAAAWHeWxIkyvXTOWvfZzqtPXjfGaWWKjGSIQENTU3zBCrsAAzI1AH0AAAAFZAAgAAAAALas/i1T2DFCEmrrLEi7O2ngJZyFHialOoedVXS+OjenBXMAIAAAAAA1kK0QxY4REcGxHeMkgumyF7iwlsRFtw9MlbSSoQY7uAVsACAAAAAAUNlpMJZs1p4HfsD4Q4WZ4TBEi6Oc2fX34rzyynqWCdwAAzI2AH0AAAAFZAAgAAAAAP1TejmWg1CEuNSMt6NUgeQ5lT+oBoeyF7d2l5xQrbXWBXMAIAAAAABPX0kj6obggdJShmqtVfueKHplH4ZrXusiwrRDHMOKeQVsACAAAAAAIYOsNwC3DA7fLcOzqdr0bOFdHCfmK8tLwPoaE9uKOosAAzI3AH0AAAAFZAAgAAAAAMrKn+QPa/NxYezNhlOX9nyEkN1kE/gW7EuZkVqYl0b8BXMAIAAAAABUoZMSPUywRGfX2EEencJEKH5x/P9ySUVrhStAwgR/LgVsACAAAAAAMgZFH6lQIIDrgHnFeslv3ld20ynwQjQJt3cAp4GgrFkAAzI4AH0AAAAFZAAgAAAAAMmD1+a+oVbiUZd1HuZqdgtdVsVKwuWAn3/M1B6QGBM3BXMAIAAAAACLyytOYuZ9WEsIrrtJbXUx4QgipbaAbmlJvSZVkGi0CAVsACAAAAAA4v1lSp5H9BB+HYJ4bH43tC8aeuPZMf78Ng1JOhJh190AAzI5AH0AAAAFZAAgAAAAAOVKV7IuFwmYP1qVv8h0NvJmfPICu8yQhzjG7oJdTLDoBXMAIAAAAABL70XLfQLKRsw1deJ2MUvxSWKxpF/Ez73jqtbLvqbuogVsACAAAAAAvfgzIorXxE91dDt4nQxYfntTsx0M8Gzdsao5naQqcRUAAzMwAH0AAAAFZAAgAAAAAKS/1RSAQma+xV9rz04IcdzmavtrBDjOKPM+Z2NEyYfPBXMAIAAAAAAOJDWGORDgfRv8+w5nunh41wXb2hCA0MRzwnLnQtIqPgVsACAAAAAAf42C1+T7xdHEFF83+c2mF5S8PuuL22ogXXELnRAZ4boAAzMxAH0AAAAFZAAgAAAAAFeq8o82uNY1X8cH6OhdTzHNBUnCChsEDs5tm0kPBz3qBXMAIAAAAABaxMBbsaeEj/EDtr8nZfrhhhirBRPJwVamDo5WwbgvTQVsACAAAAAAMbH453A+BYAaDOTo5kdhV1VdND1avNwvshEG/4MIJjQAAzMyAH0AAAAFZAAgAAAAAI8IKIfDrohHh2cjspJHCovqroSr5N3QyVtNzFvT5+FzBXMAIAAAAABXHXteKG0DoOMmECKp6ro1MZNQvXGzqTDdZ0DUc8QfFAVsACAAAAAA/w5s++XYmO+9TWTbtGc3n3ndV4T9JUribIbF4jmDLSMAAzMzAH0AAAAFZAAgAAAAAJkHvm15kIu1OtAiaByj5ieWqzxiu/epK6c/9+KYIrB0BXMAIAAAAACzg5TcyANk0nes/wCJudd1BwlkWWF6zw3nGclq5v3SJQVsACAAAAAAvruXHTT3irPJLyWpI1j/Xwf2FeIE/IV+6Z49pqRzISoAAzM0AH0AAAAFZAAgAAAAAAYSOvEWWuSg1Aym7EssNLR+xsY7e9BcwsX4JKlnSHJcBXMAIAAAAABT48eY3PXVDOjw7JpNjOe1j2JyI3LjDnQoqZ8Je5B2KgVsACAAAAAAU2815RR57TQ9uDg0XjWjBkAKvf8yssxDMzrM4+FqP6AAAzM1AH0AAAAFZAAgAAAAAGQxC9L1e9DfO5XZvX1yvc3hTLtQEdKO9FPMkyg0Y9ZABXMAIAAAAADtmcMNJwdWLxQEArMGZQyzpnu+Z5yMmPAkvgq4eAKwNQVsACAAAAAAJ88zt4Y/Hoqh+zrf6KCOiUwHbOzCxSfp6k/qsZaYGEgAAzM2AH0AAAAFZAAgAAAAADLHK2LNCNRO0pv8n4fAsxwtUqCNnVK8rRgNiQfXpHSdBXMAIAAAAACf16EBIHRKD3SzjRW+LMOl+47QXA3CJhMzlcqyFRW22AVsACAAAAAAMGz4fAOa0EoVv90fUffwLjBrQhHATf+NdlgCR65vujAAAzM3AH0AAAAFZAAgAAAAAHiZJiXKNF8bbukQGsdYkEi95I+FSBHy1I5/hK2uEZruBXMAIAAAAADE+lZBa8HDUJPN+bF6xI9x4N7GF9pj3vBR7y0BcfFhBAVsACAAAAAAGIEN6sfqq30nyxW4dxDgXr/jz5HmvA9T1jx/pKCn4zgAAzM4AH0AAAAFZAAgAAAAAI1oa2OIw5TvhT14tYCGmhanUoYcCZtNbrVbeoMldHNZBXMAIAAAAAAx2nS0Ipblf2XOgBiUOuJFBupBhe7nb6QPLZlA4aMPCgVsACAAAAAA9xu828hugIgo0E3de9dZD+gTpVUGlwtDba+tw/WcbUoAAzM5AH0AAAAFZAAgAAAAABgTWS3Yap7Q59hii/uPPimHWXsr+DUmsqfwt/X73qsOBXMAIAAAAACKK05liW5KrmEAvtpCB1WUltruzUylDDpjea//UlWoOAVsACAAAAAAcgN4P/wakJ5aJK5c1bvJBqpVGND221dli2YicPFfuAYAAzQwAH0AAAAFZAAgAAAAABOAnBPXDp6i9TISQXvcNKwGDLepZTu3cKrB4vKnSCjBBXMAIAAAAADjjzZO7UowAAvpwyG8BNOVqLCccMFk3aDK4unUeft5ywVsACAAAAAA4zkCd4k9gvfXoD1C7vwTjNcdVJwEARh8h/cxZ4PNMfgAAzQxAH0AAAAFZAAgAAAAAHN8hyvT1lYrAsdiV5GBdd5jhtrAYE/KnSjw2Ka9hjz9BXMAIAAAAAD794JK7EeXBs+D7yOVK7nWF8SbZ/7U8gZ7nnT9JFNwTAVsACAAAAAAg8Wt1HO3NhByq2ggux2a4Lo6Gryr24rEFIqh2acrwWMAAzQyAH0AAAAFZAAgAAAAAO93bPrq8bsnp1AtNd9ETnXIz0lH/2HYN/vuw9wA3fyFBXMAIAAAAABHlls5fbaF2oAGqptC481XQ4eYxInTC29aElfmVZgDUgVsACAAAAAANoQXEWpXJpgrSNK/cKi/m7oYhuSRlp1IZBF0bqTEATcAAzQzAH0AAAAFZAAgAAAAAL1YsAZm1SA0ztU6ySIrQgCCA74V6rr0/4iIygCcaJL6BXMAIAAAAADTXWTHWovGmUR1Zg9l/Aqq9H5mOCJQQrb/Dfae7e3wKAVsACAAAAAA5dunyJK6/SVfDD0t9QlNBcFqoZnf9legRjHaLSKAoQMAAzQ0AH0AAAAFZAAgAAAAAEoFAeHk0RZ9kD+cJRD3j7PcE5gzWKnyBrF1I/MDNp5mBXMAIAAAAACgHtc2hMBRSZjKw8RAdDHK+Pi1HeyjiBuAslGVNcW5tAVsACAAAAAAXzBLfq+GxRtX4Wa9fazA49DBLG6AjZm2XODStJKH8D0AAzQ1AH0AAAAFZAAgAAAAAAW+7DmSN/LX+/0uBVJDHIc2dhxAGz4+ehyyz8fAnNGoBXMAIAAAAAA6Ilw42EvvfLJ3Eq8Afd+FjPoPcQutZO6ltmCLEr8kxQVsACAAAAAAbbZalyo07BbFjPFlYmbmv0z023eT9eLkHqeVUnfUAUAAAzQ2AH0AAAAFZAAgAAAAANBdV7M7kuYO3EMoQItAbXv4t2cIhfaT9V6+s4cg9djlBXMAIAAAAABvz4MIvZWxxrcJCL5qxLfFhXiUYB1OLHdKEjco94SgDgVsACAAAAAAK2GVGvyPIKolF/ECcmfmkVcf1/IZNcaTv96N92yGrkEAAzQ3AH0AAAAFZAAgAAAAAMoAoiAn1kc79j5oPZtlMWHMhhgwNhLUnvqkqIFvcH1NBXMAIAAAAADcJTW7WiCyW0Z9YDUYwppXhLj4Ac1povpJvcAq+i48MQVsACAAAAAAIGxGDzoeB3PTmudl4+j6piQB++e33EEzuzAiXcqGxvUAAzQ4AH0AAAAFZAAgAAAAACI3j5QP7dWHpcT6WO/OhsWwRJNASBYqIBDNzW8IorEyBXMAIAAAAABxUpBSjXwCKDdGP9hYU+RvyR+96kChfvyyRC4jZmztqAVsACAAAAAAvBCHguWswb4X0xdcAryCvZgQuthXzt7597bJ5VxAMdgAAzQ5AH0AAAAFZAAgAAAAAKsbycEuQSeNrF8Qnxqw3x3og8JmQabwGqnDbqzFRVrrBXMAIAAAAACno/3ef2JZJS93SVVzmOZSN+jjJHT8s0XYq2M46d2sLAVsACAAAAAAAt5zLJG+/j4K8rnkFtAn8IvdUVNefe6utJ3rdzgwudIAAzUwAH0AAAAFZAAgAAAAAPXIcoO8TiULqlxzb74NFg+I8kWX5uXIDUPnh2DobIoMBXMAIAAAAADR6/drkdTpnr9g1XNvKDwtBRBdKn7c2c4ZNUVK5CThdQVsACAAAAAAJqOA1c6KVog3F4Hb/GfDb3jCxXDRTqpXWSbMH4ePIJsAAzUxAH0AAAAFZAAgAAAAAEa03ZOJmfHT6/nVadvIw71jVxEuIloyvxXraYEW7u7pBXMAIAAAAADzRlBJK75FLiKjz3djqcgjCLo/e3yntI3MnPS48OORhgVsACAAAAAAnQhx4Rnyj081XrLRLD5NLpWmRWCsd0M9Hl7Jl19R0h8AAzUyAH0AAAAFZAAgAAAAAKx8NLSZUU04pSSGmHa5fh2oLHsEN5mmNMNHL95/tuC9BXMAIAAAAAA59hcXVaN3MNdHoo11OcH1aPRzHCwpVjO9mGfMz4xh3QVsACAAAAAAYIPdjV2XbPj7dBeHPwnwhVU7zMuJ+xtMUW5mIOYtmdAAAzUzAH0AAAAFZAAgAAAAAHNKAUxUqBFNS9Ea9NgCZoXMWgwhP4x0/OvoaPRWMquXBXMAIAAAAABUZ551mnP4ZjX+PXU9ttomzuOpo427MVynpkyq+nsYCQVsACAAAAAALnVK5p2tTTeZEh1zYt4iqKIQT9Z0si//Hy1L85oF+5IAAzU0AH0AAAAFZAAgAAAAALfGXDlyDVcGaqtyHkLT0qpuRhJQLgCxtznazhFtuyn/BXMAIAAAAABipxlXDq14C62pXhwAeen5+syA+/C6bN4rtZYcO4zKwAVsACAAAAAAXUf0pzUq0NhLYagWDap4uEiwq5rLpcx29rWbt1NYMsMAAzU1AH0AAAAFZAAgAAAAANoEr8sheJjg4UCfBkuUzarU9NFoy1xwbXjs5ifVDeA9BXMAIAAAAABPoyTf6M+xeZVGES4aNzVlq7LgjqZXJ/QunjYVusGUEAVsACAAAAAA1hA2gMeZZPUNytk9K+lB1RCqWRudRr7GtadJlExJf8oAAzU2AH0AAAAFZAAgAAAAAKvDiK+xjlBe1uQ3SZTNQl2lClIIvpP/5CHwY6Kb3WlgBXMAIAAAAAANnxImq5MFbWaRBHdJp+yD09bVlcFtiFDYsy1eDZj+iQVsACAAAAAAWtsyO+FxMPSIezwsV1TJD8ZrXAdRnQM6DJ+f+1V3qEkAAzU3AH0AAAAFZAAgAAAAAF49IlFH9RmSUSvUQpEPUedEksrQUcjsOv44nMkwXhjzBXMAIAAAAADJtWGbk0bZzmk20obz+mNsp86UCu/nLLlbg7ppxYn7PgVsACAAAAAA3k0Tj/XgPQtcYijH8cIlQoe/VXf15q1nrZNmg7yWYEgAAzU4AH0AAAAFZAAgAAAAAOuSJyuvz50lp3BzXlFKnq62QkN2quNU1Gq1IDsnFoJCBXMAIAAAAAAqavH1d93XV3IzshWlMnzznucadBF0ND092/2ApI1AcAVsACAAAAAAzUrK4kpoKCmcpdZlZNI13fddjdoAseVe67jaX1LobIIAAzU5AH0AAAAFZAAgAAAAALtgC4Whb4ZdkCiI30zY6fwlsxSa7lEaOAU3SfUXr02XBXMAIAAAAACgdZ6U1ZVgUaZZwbIaCdlANpCw6TZV0bwg3DS1NC/mnAVsACAAAAAAzI49hdpp0PbO7S2KexISxC16sE73EUAEyuqUFAC/J48AAzYwAH0AAAAFZAAgAAAAAF6PfplcGp6vek1ThwenMHVkbZgrc/dHgdsgx1VdPqZ5BXMAIAAAAACha3qhWkqmuwJSEXPozDO8y1ZdRLyzt9Crt2vjGnT7AAVsACAAAAAA7nvcU59+LwxGupSF21jAeAE0x7JE94tjRkJfgM1yKU8AAzYxAH0AAAAFZAAgAAAAAKoLEhLvLjKc7lhOJfx+VrGJCx9tXlOSa9bxQzGR6rfbBXMAIAAAAAAIDK5wNnjRMBzET7x/KAMExL/zi1IumJM92XTgXfoPoAVsACAAAAAAFkUYWFwNr815dEdFqp+TiIozDcq5IBNVkyMoDjharDQAAzYyAH0AAAAFZAAgAAAAADoQv6lutRmh5scQFvIW6K5JBquLxszuygM1tzBiGknIBXMAIAAAAADAD+JjW7FoBQ76/rsECmmcL76bmyfXpUU/awqIsZdO+wVsACAAAAAAPFHdLw3jssmEXsgtvl/RBNaUCRA1kgSwsofG364VOvQAAzYzAH0AAAAFZAAgAAAAAJNHUGAgn56KekghO19d11nai3lAh0JAlWfeP+6w4lJBBXMAIAAAAAD9XGJlvz59msJvA6St9fKW9CG4JoHV61rlWWnkdBRLzwVsACAAAAAAxwP/X/InJJHmrjznvahIMgj6pQR30B62UtHCthSjrP0AAzY0AH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVsACAAAAAAuPSXVmJUAUpTQg/A9Bu1hYczZF58KEhVofakygbsvJQAAzY1AH0AAAAFZAAgAAAAANpIljbxHOM7pydY877gpRQvYY2TGK7igqgGsavqGPBABXMAIAAAAAAqHyEu9gpurPOulApPnr0x9wrygY/7mXe9rAC+tPK80wVsACAAAAAA7gkPzNsS3gCxdFBWbSW9tkBjoR5ib+saDvpGSB3A3ogAAzY2AH0AAAAFZAAgAAAAAGR+gEaZTeGNgG9BuM1bX2R9ed4FCxBA9F9QvdQDAjZwBXMAIAAAAABSkrYFQ6pf8MZ1flgmeIRkxaSh/Eep4Btdx4QYnGGnwAVsACAAAAAApRovMiV00hm/pEcT4XBsyPNw0eo8RLAX/fuabjdU+uwAAzY3AH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVsACAAAAAA/qxvuvJbAHwwhfrPVpmCFzNvg2cU/NXaWgqgYUZpgXwAAzY4AH0AAAAFZAAgAAAAADgyPqQdqQrgfmJjRFAILTHzXbdw5kpKyfeoEcy6YYG/BXMAIAAAAAAE+3XsBQ8VAxAkN81au+f3FDeCD/s7KoZD+fnM1MJSSAVsACAAAAAAhRnjrXecwV0yeCWKJ5J/x12Xx4qVJahsCEVHB/1U2rcAAzY5AH0AAAAFZAAgAAAAAI0CT7JNngTCTUSei1Arw7eHWCD0jumv2rb7imjWIlWABXMAIAAAAABSP8t6ya0SyCphXMwnru6ZUDXWElN0NfBvEOhDvW9bJQVsACAAAAAAGWeGmBNDRaMtvm7Rv+8TJ2sJ4WNXKcp3tqpv5Se9Ut4AAzcwAH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVsACAAAAAA5hmY4ptuNxULGf87SUFXQWGAONsL9U29duh8xqsHtxoAAzcxAH0AAAAFZAAgAAAAAHIkVuNDkSS1cHIThKc/O0r2/ubaABTOi8Q1r/dvBAsEBXMAIAAAAADdHYqchEiJLM340c3Q4vJABmmth3+MKzwLYlsG6GS7sQVsACAAAAAADa+KP/pdTiG22l+ZWd30P1iHjnBF4zSNRdFm0oEK82kAAzcyAH0AAAAFZAAgAAAAAJmoDILNhC6kn3masElfnjIjP1VjsjRavGk1gSUIjh1NBXMAIAAAAAD97Ilvp3XF8T6MmVVcxMPcdL80RgQ09UoC6PnoOvZ1IQVsACAAAAAA2RK3Xng6v8kpvfVW9tkVXjpE+BSnx9/+Fw85Evs+kUEAAzczAH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVsACAAAAAAfb1VZZCqs2ivYbRzX4p5CtaCkKW+g20Pr57FWXzEZi8AAzc0AH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVsACAAAAAAG4YYkTp80EKo59mlHExDodRQFR7njhR5dmISwUJ6ukAAAzc1AH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVsACAAAAAAAqPQi9luYAu3GrFCEsVjd9z2zIDcp6SPTR2w6KQEr+IAAzc2AH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVsACAAAAAAD7K1Fx4gFaaizkIUrf+EGXQeG7QX1jadhGc6Ji471H8AAzc3AH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVsACAAAAAAOWul0oVqJ9CejD2RqphhTC98DJeRQy5EwbNerU2+4l8AAzc4AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVsACAAAAAADlE+oc1ins+paNcaOZJhBlKlObDJ4VQORWjFYocM4LgAAzc5AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVsACAAAAAAoHqiwGOUkBu8SX5U1yHho+UIFdSN2MdQN5s6bQ0EsJYAAzgwAH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVsACAAAAAADyQnHGH2gF4w4L8axUsSTf6Ubk7L5/eoFOJk12MtZAoAAzgxAH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVsACAAAAAAgvxRbXDisNnLY3pfsjDdnFLtkvYUC4lhA68eBXc7KAwAAzgyAH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVsACAAAAAAxKPaIBKVx3jTA+R/el7P7AZ7efrmTGjJs3Hj/YdMddwAAzgzAH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVsACAAAAAAqyEjfKC8lYoIfoXYHUqHZPoaA6EK5BAZy5dxXZmay4kAAzg0AH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVsACAAAAAA6Ct9nNMKyRazn1OKnRKagm746CGu+jyhbL1qJnZxGi0AAzg1AH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVsACAAAAAAnc1xCKfdvbUEc8F7XZqlNn1C+hZTtC0I9I3LL06iaNkAAzg2AH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVsACAAAAAAfsLZsEDcWSuNsIo/TD1ReyQW75HPMgmuKZuWFOLKRLoAAzg3AH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVsACAAAAAACzfjbTBH7IwDU91OqLAz94RFkoqBOkzKAqQb55gT4/MAAzg4AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVsACAAAAAAaPxcOIxnU7But88/yadOuDJDMcCywwrRitaxMODT4msAAzg5AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVsACAAAAAAZYK/f9H4OeihmpiFMH7Wm7uLvs2s92zNA8wyrNZTsuMAAzkwAH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVsACAAAAAAGPnYVvo2ulO9z4LGd/69NAklfIcZqZvFX2KK0s+FcTUAAzkxAH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVsACAAAAAA6NhRTYFL/Sz4tao7vpPjLNgAJ0FX6P/IyMW65qT6YsMAAzkyAH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVsACAAAAAA/JcUoyKacCB1NfmH8vYqC1f7rd13KShrQqV2r9QBP44AAzkzAH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVsACAAAAAAxXd9Mh6R3mnJy8m7UfqMKi6oD5DlZpkaOz6bEjMOdiwAAzk0AH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVsACAAAAAAL/D5Sze/ZoEanZLK0IeEkhgVkxEjMWVCfmJaD3a8uNIAAzk1AH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVsACAAAAAAVLHNg0OUVqZ7WGOP53BkTap9FOw9dr1P4J8HxqFqU04AAzk2AH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVsACAAAAAAs9c9SM49/pWmyUQKslpt3RTMBNSRppfNO0JBvUqHPg0AAzk3AH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVsACAAAAAAJOP+EHz6awDb1qK2bZQ3kTV7wsj5Daj/IGAWh4g7omAAAzk4AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVsACAAAAAA4ml/nm0gJNTcJ4vuD+T2Qfq2fQZlibJp/j6MOGDrbHMAAzk5AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVsACAAAAAACUqiFqHZdGbwAA+hN0YUE5zFg+H+dabIB4dj5/75W/YAAzEwMAB9AAAABWQAIAAAAADJDdC9aEFl4Y8J/awHbnXGHjfP+VXQilPHJg7ewaJI7AVzACAAAAAAE+tqRl6EcBMXvbr4GDiNIYObTsYpa1n6BJk9EjIJVicFbAAgAAAAAJVc+HYYqa0m1Hq6OiRX8c0iRnJYOt6AJAJoG0sG3GMSAAMxMDEAfQAAAAVkACAAAAAA3F9rjEKhpoHuTULVGgfUsGGwJs3bISrXkFP1v6KoQLgFcwAgAAAAAIBf0tXw96Z/Ds0XSIHX/zk3MzUR/7WZR/J6FpxRWChtBWwAIAAAAABWrjGlvKYuTS2s8L9rYy8Hf0juFGJfwQmxVIjkTmFIGQADMTAyAH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVsACAAAAAA0nWwYzXQ1+EkDvnGq+SMlq20z+j32Su+i/A95SggPb4AAzEwMwB9AAAABWQAIAAAAACMtPm12YtdEAvqu6Eji1yuRXnu1RJP6h0l7pH3lSH4MwVzACAAAAAAENyCFfyUAh1veQBGx+cxiB7Sasrj41jzCGflZkB5cRMFbAAgAAAAAKdI2LMqISr/T5vuJPg6ZRBm5fVi2aQCc4ra3A4+AjbDAAMxMDQAfQAAAAVkACAAAAAAvlI4lDcs6GB1cnm/Tzo014CXWqidCdyE5t2lknWQd4QFcwAgAAAAAD60SpNc4O2KT7J0llKdSpcX1/Xxs97N715a1HsTFkmBBWwAIAAAAABuuRkJWAH1CynggBt1/5sPh9PoGiqTlS24D/OE2uHXLQADMTA1AH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVsACAAAAAAHErCYycpqiIcCZHdmPL1hi+ovLQk4TAvENpfLdTRamQAAzEwNgB9AAAABWQAIAAAAABb6LXDWqCp1beQgQjj8I3sRTtFhlrmiBi+h/+ikmrvugVzACAAAAAA9stpgTecT7uTyaGNs3K9Bp0A7R0QaIAOfscyMXHBPX8FbAAgAAAAAHUt+McyXrJ1H8SwnHNVO181Ki8vDAM1f7XI26mg95ZDAAMxMDcAfQAAAAVkACAAAAAA97NTT+81PhDhgptNtp4epzA0tP4iNb9j1AWkiiiKGM8FcwAgAAAAAKPbHg7ise16vxmdPCzksA/2Mn/qST0L9Xe8vnQugVkcBWwAIAAAAABB0EMXfvju4JU/mUH/OvxWbPEl9NJkcEp4iCbkXI41fAADMTA4AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVsACAAAAAAOZLQ6o7e4mVfDzbpQioa4d3RoTvqwgnbmc5Qh2wsZuoAAzEwOQB9AAAABWQAIAAAAADQnslvt6Hm2kJPmqsTVYQHE/wWeZ4bE1XSkt7TKy0r1gVzACAAAAAA8URTA4ZMrhHPvlp53TH6FDCzS+0+61qHm5XK6UiOrKEFbAAgAAAAAHQbgTCdZcbdA0avaTmZXUKnIS7Nwf1tNrcXDCw+PdBRAAMxMTAAfQAAAAVkACAAAAAAhujlgFPFczsdCGXtQ/002Ck8YWQHHzvWvUHrkbjv4rwFcwAgAAAAALbV0lLGcSGfE7mDM3n/fgEvi+ifjl7WZ5b3aqjDNvx9BWwAIAAAAACbceTZy8E3QA1pHmPN5kTlOx3EO8kJM5PUjTVftw1VpgADMTExAH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVsACAAAAAAaLvJDrqBESTNZSdcXsd+8GXPl8ZkUsGpeYuyYVv/kygAAzExMgB9AAAABWQAIAAAAACfw9/te4GkHZAapC9sDMHHHZgmlTrccyJDPFciOMSOcwVzACAAAAAAIIC1ZpHObvmMwUfqDRPl4C1aeuHwujM1G/yJbvybMNAFbAAgAAAAAAs9x1SnVpMfNv5Bm1aXGwHmbbI9keWa9HRD35XuCBK5AAMxMTMAfQAAAAVkACAAAAAAkxHJRbnShpPOylLoDdNShfILeA1hChKFQY9qQyZ5VmsFcwAgAAAAAKidrY+rC3hTY+YWu2a7fuMH2RD/XaiTIBW1hrxNCQOJBWwAIAAAAACW0kkqMIzIFMn7g+R0MI8l15fr3k/w/mHtY5n6SYTEwAADMTE0AH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVsACAAAAAAQQOkIQtkyNavqCnhQbNg3HfqrJdsAGaoxSJePJl1qXsAAzExNQB9AAAABWQAIAAAAABxMy7X5hf7AXGDz3Y/POu1ZpkMlNcSvSP92NOO/Gs7wAVzACAAAAAAHJshWo2T5wU2zvqCyJzcJQKQaHFHpCpMc9oWBXkpUPoFbAAgAAAAAGeiJKzlUXAvL0gOlW+Hz1mSa2HsV4RGmyLmCHlzbAkoAAMxMTYAfQAAAAVkACAAAAAAlqbslixl7Zw3bRlibZbe/WmKw23k8uKeIzPKYEtbIy0FcwAgAAAAAHEKwpUxkxOfef5HYvulXPmdbzTivwdwrSYIHDeNRcpcBWwAIAAAAADuPckac21Hrg/h0kt5ShJwVEZ9rx6SOHd2+HDjqxEWTQADMTE3AH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVsACAAAAAAcvBV+SykDYhmRFyVYwFYB9oBKBSHr55Jdz2cXeowsUQAAzExOAB9AAAABWQAIAAAAAAm83FA9yDUpwkbKTihe7m53u+DivS9BU2b4vQMtCVQ2AVzACAAAAAAz3m1UB/AbZPa4QSKFDnUgHaT78+6iGOFAtouiBorEgEFbAAgAAAAAIgbpyYtJj5513Z5XYqviH/HXG/5+mqR52iBbfqMmDtZAAMxMTkAfQAAAAVkACAAAAAAJRzYK0PUwr9RPG2/7yID0WgcTJPB2Xjccp5LAPDYunkFcwAgAAAAAIIh24h3DrltAzNFhF+MEmPrZtzr1PhCofhChZqfCW+jBWwAIAAAAAAzRNXtL5o9VXMk5D5ylI0odPDJDSZZry1wfN+TedH70gADMTIwAH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVsACAAAAAAS19m7DccQxgryOsBJ3GsCs37yfQqNi1G+S6fCXpEhn4AAzEyMQB9AAAABWQAIAAAAAAC/I4TQRtCl12YZmdGz17X4GqSQgfwCPgRBwdHmdwu+QVzACAAAAAAx8f3z2ut/RAZhleari4vCEE+tNIn4ikjoUwzitfQ588FbAAgAAAAAJci0w1ZB8W2spJQ+kMpod6HSCtSR2jrabOH+B0fj3A4AAMxMjIAfQAAAAVkACAAAAAADGB5yU2XT0fse/MPWgvBvZikVxrl5pf3S5K1hceKWooFcwAgAAAAAIxTmlLHMjNaVDEfJbXvRez0SEPWFREBJCT6qTHsrljoBWwAIAAAAAAlswzAl81+0DteibwHD+CG5mZJrfHXa9NnEFRtXybzzwADMTIzAH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVsACAAAAAAZa0/FI/GkZR7CtX18Xg9Tn9zfxkD0UoaSt+pIO5t1t4AAzEyNAB9AAAABWQAIAAAAAAfPUoy7QyZKhIIURso+mkP9qr1izbjETqF5s22GwjCjAVzACAAAAAAvLMsIDQ/go4VUxeh50UHmsvMvfx51cwyONnRD2odvC0FbAAgAAAAAKMb+1CodEalAFnDrEL1Ndt8ztamZ+9134m9Kp3GQgd+AAMxMjUAfQAAAAVkACAAAAAAE3ZqUar0Bq2zWbARE0bAv98jBlK9UJ73/xcwdMWWlSkFcwAgAAAAAK4M+MmC+9sFiFsumMyJZQKxWmmJiuG9H7IzKw083xxkBWwAIAAAAAAqkAONzhvMhkyL1D/6h7QQxEkdhC3p2WjXH+VGq5qCqQADMTI2AH0AAAAFZAAgAAAAAMo8FJiOq63cAmyk2O7eI7GcbQh/1j4RrMTqly3rexftBXMAIAAAAADjVmpd0WiRGTw/gAqEgGolt2EI7Csv14vKdmYoMD0aAgVsACAAAAAA07XQBzBUQMNw7F2/YxJjZNuPVpHTTgbLd1oGk77+bygAAzEyNwB9AAAABWQAIAAAAACu5IGaIx7A3Jvly/kzlCsSA4s3iJwuIl8jEdRH0k93NwVzACAAAAAA9NRUyxYE+t0Xyosyt6vIfMFW/vBoYg6sR+jBNs4JAxIFbAAgAAAAAAzyZ91dx+0oMlOVAjRGiMrPySikY/U9eMEB4WJb3uWtAAMxMjgAfQAAAAVkACAAAAAALkRy0GJInXYLA+cgjs6Myb0a+Gu9hgXhHvhLNoGWfckFcwAgAAAAANbALyt9zCSvwnLaWCd2/y2eoB7qkWTvv1Ldu8r40JPuBWwAIAAAAAD4Fl5bV5sz4isIE9bX+lmAp+aAKaZgVYVZeVfrItkCZAADMTI5AH0AAAAFZAAgAAAAAGoUK/DSWhT8LZhszSUqDbTrp8cSA7rdqmADKL+MILtTBXMAIAAAAABHnEE9bVa6lvhfhEMkkV2kzSSxH/sMW/FIJuw3CzWs6wVsACAAAAAAanavcBdqZxgRGKvEK95wTmeL1K1CeDSXZsXUAs81uOgAAzEzMAB9AAAABWQAIAAAAAC922ZDQE3h2fQKibGMZ9hV0WNlmrPYYSdtaSyYxsWYqgVzACAAAAAAagMovciKK6WVjIc2cCj8nK5O/gVOFFVeVAJpRp89tmQFbAAgAAAAAKcTFfPQzaFiAtSFhqbN02sCE1BKWJSrRfGN5L6oZwzkAAMxMzEAfQAAAAVkACAAAAAAtK+JqX3K/z2txjAU15DgX4y90DS2YLfIJFolCOkJJJwFcwAgAAAAAMnR5V7gfX7MNqqUdL5AkWlkhyFXaBRVNej+Rcn8lrQkBWwAIAAAAAA2cDNRXZuiC241TGRvdFyctJnrNcdbZOP9zHio81tkngADMTMyAH0AAAAFZAAgAAAAAAeGrIMK/bac6kPczxbvRYqKMkcpeI2FjdMpD91FDWIvBXMAIAAAAAAix62z1LeS8yvSXCl5gHSIomjyx76fF3S1lp9k900hygVsACAAAAAAiYwzf2m71aWFD5ajcXyW2JX2EzQOkBroTGMg29nLPYIAAzEzMwB9AAAABWQAIAAAAACphf298InM0Us4HT8o1W1MGw0D/02vd7Jh+U0h7qaFaQVzACAAAAAAFXtk7YpqsOJxsqGWSIL+YcBE96G3Zz9D31gPqDW94y8FbAAgAAAAAAOrS1KVA94rjB1jZ1pPocpCeBG+B14RzWoHqVDpp7JbAAMxMzQAfQAAAAVkACAAAAAATLDS2cuDVM3yDMuWNgk2iGKBTzPpfJMbvxVOSY39ZfcFcwAgAAAAAPT5wRi2cLHIUflXzm6EQB/m7xdThP80ir1VV/JBBqvxBWwAIAAAAAB9lEtZS0aXCFbCtSbhnis27S5IPcfWGygHW8AHn3QqzwADMTM1AH0AAAAFZAAgAAAAAJNjExiZVX7jfFGfYpQu16qxLN0YPqVU/5CQ/Y67YSinBXMAIAAAAABMpm2+6KrkRUlXzQoMPHrQmIO6dkQz66tYdfTeA3dKqQVsACAAAAAAFXobHiMLvNZuEPr8jtewCX2J93EZG3JNeyVg92fue6YAAzEzNgB9AAAABWQAIAAAAABlFkYtLCx901X6QVVMkSn6Z7k30UF4xHaA0OZJJ9bdyQVzACAAAAAATez+F9GHcGzTp7jjv4feboUNb8JCkIp4EqcPFisnq7MFbAAgAAAAACE7JvOpBgMoZ7kRd4QbxIhxukPTUxXpzhjnBHiR7XoRAAMxMzcAfQAAAAVkACAAAAAA8NJKN0IxZnruhswGQkiruv8Ih0EMwDcSZx/Xasup9dkFcwAgAAAAAKaJZRxzA+Igeydvuk6cSwUHXcrmT4PjhuPu//FslpdnBWwAIAAAAAD53Rok1Vq/PMAnXmarqoHJ0PEyYUBmVESa9hIpCv/G9QADMTM4AH0AAAAFZAAgAAAAABHxHdEClz7hbSSgE58+dWLlSMJnoPz+jFxp4bB1GmLQBXMAIAAAAAD3nSvT6aGD+A110J/NwEfp0nPutlmuB5B+wA3CC3noGAVsACAAAAAA3Apjd+TapONB7k5wBVwTWgn8t+Sq2oyyU5/+as109RcAAzEzOQB9AAAABWQAIAAAAAC/o8qW/ifk3KuJ01VFkyNLgQafxB5/bGs2G5VyyVafOwVzACAAAAAA1bMqAFGDHSl6BYNLbxApvkAv2K1/oafywiX0MDz1dGUFbAAgAAAAAHJXLlId3edFoniLD/9K2A5973MeP2Ro31flDyqm3l5QAAMxNDAAfQAAAAVkACAAAAAAY2V8I1bz3a1AxTtmED6UhdhA09huFkuuEX8R+d/WDPUFcwAgAAAAAPTVoNRiI76tcRKqd+JBBVyy4+YcKST42p0QX2BtmQ2VBWwAIAAAAACcxt9hg14WqPNiDv1MkqVljM2e2KJEv53lA17LhV6ZigADMTQxAH0AAAAFZAAgAAAAAO2kSsW0WGN9AOtK4xK2SHrGhWiaAbMEKT4iZkRpaDN/BXMAIAAAAABKGzQcPM8LT2dwOggxoWjv/1imYWabbG/G4kBw8OWaxAVsACAAAAAAC9hLK1dScQTAqg+YAG3ObdPzg2Xet57HmOFpGmyUR9UAAzE0MgB9AAAABWQAIAAAAAAiCwzNEEaH/mDam68IdDftnhthyUFdb+ZCNSBQ91WlHQVzACAAAAAA7tHyHcxCzmbJeFYZyPm4mEgkTGKOvwY4MX82OvH0Jn8FbAAgAAAAAAb5IAbZ1hXCNegQ+S+C9i/Z8y6sS8KeU04V6hXa2ml6AAMxNDMAfQAAAAVkACAAAAAAGuCHVNJSuoVkpPOnS5s89GuA+BLi2IPBUr2Bg1sWEPIFcwAgAAAAAEl1gncS5/xO7bQ/KQSstRV3rOT2SW6nV92ZANeG2SR6BWwAIAAAAAA9LOcKmhek8F2wAh8yvT/vjp2gaouuO+Hmv10lwAeWPAADMTQ0AH0AAAAFZAAgAAAAAMfxz7gEaoCdPvXrubDhCZUS0ARLZc1svgbXgMDlVBPgBXMAIAAAAAB6a5dDA3fuT5Vz2KvAcbUEFX/+B7Nw2p1QqbPoQ5TTuAVsACAAAAAAcf/y75UOuI62A6vWH7bYr/5Jz+nirZVYK/81trN6XOQAAzE0NQB9AAAABWQAIAAAAACnYsqF/VzmjIImC9+dqrHO1TM6lJ6fRwM0mM6Wf6paOwVzACAAAAAA5tgZzch8uDCR1ky3SllVaKVpxAlbrhvlNDTazZZRZOAFbAAgAAAAALeGiLJS4z2zhgVpxzyPdRYyACP9QzQBOob34YrIZumCAAMxNDYAfQAAAAVkACAAAAAAEC0sIVmadtW4YMuRXH7RpAhXclsd+3bmqGXCMeaT014FcwAgAAAAABPpXh0uzpsJJB+IRUNajmMB9WGwswfpw5T9xk3Xj6ANBWwAIAAAAAAmf+NYh9TZ/QRu3w/GQz66n7DtfbJijN3G7KzeL8lstAADMTQ3AH0AAAAFZAAgAAAAABaIB3n49Xm9cOafSrQsE0WCcYp8rMIO/qVwIlMF5YLRBXMAIAAAAAC9EyWJV3xOu9bzgdJ/yX+ko7qLf1u3AxNMataW2C9EzQVsACAAAAAAvVbDkLxXx2DcMLifIQ3K0IIJcLcAG9DUrNfI6aoUjNcAAzE0OAB9AAAABWQAIAAAAAA5rZItA/cocRnngYqcJ3nBXQ+l688aKz3EQyLbYYunPAVzACAAAAAAwKyA+L7TgxztPClLrIMk2JXR+w7c04N3ZOqPgjvrIvsFbAAgAAAAACzvZ33h6aWEe8hmo+1f6OXJ72FY5hvWaUuha64ZV3KFAAMxNDkAfQAAAAVkACAAAAAA3htn7oHJ0YYpIrs+Mzyh85Ys67HwAdv5LQl1mCdoMWkFcwAgAAAAAEHjCtNNLenHuSIYux6ezAHsXDaj2DlTF67ToDhDDe6HBWwAIAAAAAD+P4H0sk9jOd+7vOANt2/1Ectb+4ZRGPE8GkHWNXW3MgADMTUwAH0AAAAFZAAgAAAAAEnt18Km/nqggfIJWxzTr9r3hnXNaueG6XO9A5G11LnGBXMAIAAAAAD7QxzGMN/ard5TfFLecE6uusMmXG2+RBsBR+/NCQHUwAVsACAAAAAAQEZ1ZZ8GC8rdbg7s87OM5Gr9qkTXS9+P5DuAZxj5Gl4AAzE1MQB9AAAABWQAIAAAAAAVAKK/GoY8AACu/hyMpO4hdLq6JnEyWNzkyci9sbaD/wVzACAAAAAA2HmeqpMlvvBpV2zQTYIRmsc4MFlfHRwLof0ycJgMg/MFbAAgAAAAACdltCeWi5E/q1Li1eXLChpM2D9QQSGLBZ82NklQSc0oAAMxNTIAfQAAAAVkACAAAAAAhHyq1GQC/GiMwpYjcsfkNxolJ10ARKjIjfkW1Wipzi0FcwAgAAAAAD/uaGWxTDq87F8XZ6CrFI+RNa8yMqfSZdqK00Kj833BBWwAIAAAAAD6aEdOO0CsQGagioOCvANPCEHSpJ8BSixlPBq5ERhB7AADMTUzAH0AAAAFZAAgAAAAABAJJxHoZD+MQBWqm9UM9Dd3z5ZohIZGWRaRVRsMptKQBXMAIAAAAADrE/ca+gqj/SH4oao4wE4qn2ovoTydzcMbDbrfnUs3zAVsACAAAAAAeNCIQN6hVnGJinytQRFGlQ2ocoprXNqpia+BSxzl+uwAAzE1NAB9AAAABWQAIAAAAAAv01wz7VG9mTepjXQi6Zma+7b/OVBaKVkWNbgDLr1mFgVzACAAAAAA0I5sxz8r6wkCp5Tgvr+iL4p6MxSOq5d3e1kZG+0b7NkFbAAgAAAAAIA32v6oGkAOS96HexGouNTex+tLahtx9QF2dgGClk6WAAMxNTUAfQAAAAVkACAAAAAAWXecRwxSon68xaa9THXnRDw5ZfzARKnvvjTjtbae6T0FcwAgAAAAAPh0UfUMEo7eILCMv2tiJQe1bF9qtXq7GJtC6H5Va4fIBWwAIAAAAADqFr1ThRrTXNgIOrJWScO9mk86Ufi95IDu5gi4vP+HWQADMTU2AH0AAAAFZAAgAAAAAEY5WL8/LpX36iAB1wlQrMO/xHVjoO9BePVzbUlBYo+bBXMAIAAAAABoKcpadDXUARedDvTmzUzWPe1jTuvD0z9oIcZmKuiSXwVsACAAAAAAJuJbwuaMrAFoI+jU/IYr+k4RzAqITrOjAd3HWCpJHqEAAzE1NwB9AAAABWQAIAAAAADnJnWqsfx0xqNnqfFGCxIplVu8mXjaHTViJT9+y2RuTgVzACAAAAAAWAaSCwIXDwdYxWf2NZTly/iKVfG/KDjHUcA1BokN5sMFbAAgAAAAAJVxavipE0H4/JQvhagdytXBZ8qGooeXpkbPQ1RfYMVHAAMxNTgAfQAAAAVkACAAAAAAsPG7LaIpJvcwqcbtfFUpIjj+vpNj70Zjaw3eV9T+QYsFcwAgAAAAAJQ71zi0NlCyY8ZQs3IasJ4gB1PmWx57HpnlCf3+hmhqBWwAIAAAAACD58TO6d+71GaOoS+r73rAxliAO9GMs4Uc8JbOTmC0OwADMTU5AH0AAAAFZAAgAAAAAAGiSqKaQDakMi1W87rFAhkogfRAevnwQ41onWNUJKtuBXMAIAAAAAASgiDpXfGh7E47KkOD8MAcX8+BnDShlnU5JAGdnPdqOAVsACAAAAAAI+2TTQIgbFq4Yr3lkzGwhG/tqChP7hRAx2W0fNaH6jcAAzE2MAB9AAAABWQAIAAAAAB7L4EnhjKA5xJD3ORhH2wOA1BvpnQ+7IjRYi+jjVEaJAVzACAAAAAAuhBIm0nL3FJnVJId+7CKDASEo+l2E89Z9/5aWSITK4AFbAAgAAAAALtSICOzQDfV9d+gZuYxpEj6cCeHnKTT+2G3ceP2H65kAAMxNjEAfQAAAAVkACAAAAAAaROn1NaDZFOGEWw724dsXBAm6bgmL5i0cki6QZQNrOoFcwAgAAAAANVT8R6UvhrAlyqYlxtmnvkR4uYK/hlvyQmBu/LP6/3ZBWwAIAAAAAD+aHNMP/X+jcRHyUtrCNkk1KfMtoD3GTmShS8pWGLt+AADMTYyAH0AAAAFZAAgAAAAADqSR5e0/Th59LrauDA7OnGD1Xr3H3NokfVxzDWOFaN7BXMAIAAAAACt30faNwTWRbvmykDpiDYUOCwA6QDbBBYBFWS7rdOB4AVsACAAAAAAF7SvnjjRk5v2flFOKaBAEDvjXaL1cpjsQLtK2fv9zdQAAzE2MwB9AAAABWQAIAAAAADmtb1ZgpZjSeodPG/hIVlsnS8hoRRwRbrTVx89VwL62AVzACAAAAAAi38e1g6sEyVfSDkzZbaZXGxKI/zKNbMasOl2LYoWrq8FbAAgAAAAAALACk0KcCDN/Kv8WuazY8ORtUGkOZ5Dsm0ys1oOppp/AAMxNjQAfQAAAAVkACAAAAAAf/f7AWVgBxoKjr7YsEQ4w/fqSvuQWV2HMiA3rQ7ur0sFcwAgAAAAADkkeJozP6FFhUdRIN74H4UhIHue+eVbOs1NvbdWYFQrBWwAIAAAAAB55FlHAkmTzAYj/TWrGkRJw2EhrVWUnZXDoMYjyfB/ZwADMTY1AH0AAAAFZAAgAAAAAI2WEOymtuFpdKi4ctanPLnlQud+yMKKb8p/nfKmIy56BXMAIAAAAADVKrJmhjr1rfF3p+T+tl7UFd1B7+BfJRk0e7a4im7ozgVsACAAAAAA5E7Ti3PnFiBQoCcb/DN7V1uM3Xd6VKiexPKntssFL7kAAzE2NgB9AAAABWQAIAAAAAAuHU9Qd79hjyvKOujGanSGDIQlxzsql8JytTZhEnPw+AVzACAAAAAAjF2gV/4+sOHVgDd/oR5wDi9zL7NGpGD+NsEpGXy/a4QFbAAgAAAAAJzMoyojYV6Ed/LpVN5zge93Odv3U7JgP7wxeRaJZGTdAAMxNjcAfQAAAAVkACAAAAAA7dQDkt3iyWYCT94d7yqUtPPwp4qkC0ddu+HFdHgVKEkFcwAgAAAAANuYvtvZBTEq4Rm9+5eb7VuFopowkrAuv86PGP8Q8/QvBWwAIAAAAACeqXoAOQOE4j0zRMlkVd8plaW0RX1npsFvB38Xmzv7sAADMTY4AH0AAAAFZAAgAAAAAAwnZSDhL4tNGYxlHPhKYB8s28dY5ScSwiKZm3UhT8U3BXMAIAAAAABDoY6dhivufTURQExyC9Gx3ocpl09bgbbQLChj3qVGbgVsACAAAAAAF+1nS7O0v85s3CCy+9HkdeoEfm2C6ZiNbPMMnSfsMHUAAzE2OQB9AAAABWQAIAAAAAC2VuRdaC4ZJmLdNOvD6R2tnvkyARteqXouJmI46V306QVzACAAAAAAMn1Z6B35wFTX9mEYAPM+IiJ5hauEwfD0CyIvBrxHg7IFbAAgAAAAAOG6DvDZkT9B/xZWmjao2AevN7MMbs3Oh9YJeSd/hZ+hAAMxNzAAfQAAAAVkACAAAAAAVerb7qVNy457rNOHOgDSKyWl5ojun7iWrv1uHPXrIZQFcwAgAAAAAIDcYS9j5z+gx0xdJj09L7876r/vjvKTi/d3bXDE3PhyBWwAIAAAAADuhVLqb1Bkrx8aNymS+bx2cL8GvLFNH4SAi690DUgnWQADMTcxAH0AAAAFZAAgAAAAAH/E44yLxKCJjuSmU9A8SEhbmkDOx1PqqtYcZtgOzJdrBXMAIAAAAABgLh9v2HjBbogrRoQ82LS6KjZQnzjxyJH4PH+F3jupSAVsACAAAAAAIlO46ehXp4TqpDV0t6op++KO+uWBFh8iFORZjmx2IjkAAzE3MgB9AAAABWQAIAAAAAAlNUdDL+f/SSQ5074mrq0JNh7CTXwTbbhsQyDwWeDVMwVzACAAAAAANIH2IlSNG0kUw4qz0budjcWn8mNR9cJlYUqPYdonucAFbAAgAAAAAJMrOUOyiu5Y3sV76zwEFct8L7+i8WGlQI2+8z2W2kzaAAMxNzMAfQAAAAVkACAAAAAASZ+CvUDtlk/R4HAQ3a+PHrKeY/8ifAfh0oXYFqliu80FcwAgAAAAAJelpzPgM65OZFt/mvGGpwibclQ49wH+1gbUGzd9OindBWwAIAAAAAD9qeDchteEpVXWcycmD9kl9449C1dOw0r60TBm5jK+cQADMTc0AH0AAAAFZAAgAAAAAN9fkoUVbvFV2vMNMAkak4gYfEnzwKI3eDM3pnDK5q3lBXMAIAAAAACnDkgVNVNUlbQ9RhR6Aot2nVy+U4km6+GHPkLr631jEAVsACAAAAAANzg/BnkvkmvOr8nS4omF+q9EG/4oisB+ul4YHi938hwAAzE3NQB9AAAABWQAIAAAAAASyK3b1nmNCMptVEGOjwoxYLLS9fYWm/Zxilqea0jpEQVzACAAAAAADDHsGrbqlKGEpxlvfyqOJKQJjwJrzsrB7k3HG0AUJbkFbAAgAAAAAKwx3S4XfDZh4+LuI9jf7XgUh5qiefNv87JD4qvVRfPSAAMxNzYAfQAAAAVkACAAAAAAlSP9iK31GlcG9MKGbLmq+VXMslURr+As736rrVNXcsUFcwAgAAAAAAvbj0zfq9zzi8XReheKFbCB+h9IsOLgXPPpI5vrEJNZBWwAIAAAAABXvoZhaQE7ogWjeBjceVkp03N20cKYP3TA8vuNsgpfAgADMTc3AH0AAAAFZAAgAAAAAOJNORH8Bev97gVU7y6bznOxJ+E6Qoykur1QP76hG1/7BXMAIAAAAAC+C1PtOOrSZgzBAGhr+dPe/kR0JUw9GTwLVNr61xC1aAVsACAAAAAAeA/L8MQIXkamaObtMPLpoDoi5FypA5WAPtMeMrgi0eQAAzE3OAB9AAAABWQAIAAAAAAKcHzLUomavInN6upPkyWhAqYQACP/vdVCIYpiy6U6HgVzACAAAAAATsR4KItY6R2+U7Gg6sJdaEcf58gjd1OulyWovIqfxKcFbAAgAAAAAFbm10ko67ahboAejQdAV0U2uA5OhZYdb8XUFJ8OL46LAAMxNzkAfQAAAAVkACAAAAAAqTOLiMpCdR59tLZzzIPqJvbCNvz2XQL9ust0qYaehtcFcwAgAAAAAArefox/3k5xGOeiw2m6NUdzuGxmPwcu5IFcj+jMwHgHBWwAIAAAAADLZGFJ7MQd5JXMgMXjqZO5LDLxcFClcXPlnRMWRn+1oAADMTgwAH0AAAAFZAAgAAAAAIPSqSeVzSRgNVNmrPYHmUMgykCY27NbdDUNhE5kx/SgBXMAIAAAAAAhX90nNfxyXmZe/+btZ7q6xMX4PFyj0paM1ccJ/5IUUQVsACAAAAAA419oHmD2W0SYoOMwhrhrp8jf68fg9hTkaRdCuVd3CN0AAzE4MQB9AAAABWQAIAAAAACLn5DxiqAosHGXIAY96FwFKjeqrzXWf3VJIQMwx1fl4gVzACAAAAAAindvU27nveutopdvuHmzdENBbeGFtI3Qcsr07jxmvm8FbAAgAAAAAPvl9pBStQvP4OGkN5v0MghUY6djm9n7XdKKfrW0l1sMAAMxODIAfQAAAAVkACAAAAAA7i2S6rHRSPBwZEn59yxaS7HiYBOmObIkeyCcFU42kf8FcwAgAAAAAGb3RSEyBmgarkTvyLWtOLJcPwCKbCRkESG4RZjVmY4iBWwAIAAAAADB2/wo5CSHR4ANtifY6ZRXNTO5+O8qP82DfAiAeanpZwADMTgzAH0AAAAFZAAgAAAAAFz+M+H/Z94mdPW5oP51B4HWptp1rxcMWAjnlHvWJDWrBXMAIAAAAACBFEOQyL7ZHu4Cq33QvXkmKuH5ibG/Md3RaED9CtG5HwVsACAAAAAAfggtJTprQ/yZzj7y5z9KvXsdeXMWP0yUXMMJqpOwI88AAzE4NAB9AAAABWQAIAAAAAAE7c2x3Z3aM1XGfLNk/XQ9jCazNRbGhVm7H8c2NjS5ywVzACAAAAAARJ9h8fdcwA19velF3L/Wcvi2rCzewlKZ2nA0p8bT9uwFbAAgAAAAAJtWe6b4wK2Hae2dZm/OEpYQnvoZjz4Sz5IgJC2wInecAAMxODUAfQAAAAVkACAAAAAAVoRt9B9dNVvIMGN+ea5TzRzQC+lqSZ8dd/170zU5o9cFcwAgAAAAAEwM95XZin5mv2yhCI8+ugtKuvRVmNgzzIQN0yi1+9aIBWwAIAAAAAAMGBq72n00rox3uqhxSB98mkenTGCdbbUF1gXrgottzgADMTg2AH0AAAAFZAAgAAAAAKRDkjyWv/etlYT4GyoXrmBED2FgZHnhc+l9Wsl06cH2BXMAIAAAAABohlpm3K850Vndf3NmNE0hHqDlNbSR8/IvMidQ3LnIZAVsACAAAAAAW42nGHa6q2MCAaaPVwaIDfr8QLyQwjKq23onZJYsqVsAAzE4NwB9AAAABWQAIAAAAAC3DFh5oklLCNLY90bgWm68dFXz65JpAZSp1K99MBTPAQVzACAAAAAAQgZecmxEUZVHoptEQClDwAf8smI3WynQ/i+JBP0g+kQFbAAgAAAAAEUSQGVnAPISD6voD0DiBUqyWKgt2rta0tjmoe+LNt6IAAMxODgAfQAAAAVkACAAAAAAQ5WKvWSB503qeNlOI2Tpjd5blheNr6OBO8pfJfPNstcFcwAgAAAAAKwHgQLSDJ5NwLBQbY5OnblQIsVDpGV7q3RCbFLD1U4/BWwAIAAAAACQ5nED99LnpbqXZuUOUjnO2HTphEAFBjLD4OZeDEYybgADMTg5AH0AAAAFZAAgAAAAAGfhFY3RGRm5ZgWRQef1tXxHBq5Y6fXaLAR4yJhrTBplBXMAIAAAAACKEF0ApLoB6lP2UqTFsTQYNc9OdDrs/vziPGzttGVLKQVsACAAAAAArOO6FyfNRyBi0sPT5iye7M8d16MTLcwRfodZq4uCYKEAAzE5MAB9AAAABWQAIAAAAAAIM73gPcgzgotYHLeMa2zAU4mFsr7CbILUZWfnuKSwagVzACAAAAAAJCSu98uV8xv88f2BIOWzt6p+6EjQStMBdkGPUkgN79cFbAAgAAAAAMGqPGMPxXbmYbVfSa/japvUljht1zZT33TY7ZjAiuPfAAMxOTEAfQAAAAVkACAAAAAAkWmHCUsiMy1pwZTHxVPBzPTrWFBUDqHNrVqcyyt7nO8FcwAgAAAAAMv2CebFRG/br7USELR98sIdgE9OQCRBGV5JZCO+uPMgBWwAIAAAAABt7qSmn3gxJu7aswsbUiwvO+G6lXj/Xhx+J/zQyZxzLAADMTkyAH0AAAAFZAAgAAAAAGInUYv0lP/rK7McM8taEHXRefk8Q2AunrvWqdfSV7UaBXMAIAAAAACE+WPxJ3gan7iRTbIxXXx+bKVcaf8kP4JD8DcwU0aL7wVsACAAAAAAUC4eTprX4DUZn2X+UXYU6QjtiXk+u57yoOPBbPQUmDkAAzE5MwB9AAAABWQAIAAAAACmHlg2ud3cplXlTsNTpvNnY6Qm1Fce0m899COamoDjaQVzACAAAAAArtJQeJIlepBWRU2aYar7+YGYVQ7dfDc1oxgTmA8r9q0FbAAgAAAAAOk45vg5VqZHAFCO3i0Z52SZi5RADf8NXwf68T5yad/DAAMxOTQAfQAAAAVkACAAAAAApzcWSAbZWV/Rq+ylRNqqlJqNVR4fhXrz4633/MQOQgcFcwAgAAAAAN/jz/bsEleiuCl+li83EWlG6UMHA8CyaOMRKCkXkSCPBWwAIAAAAAC3Sd+Qg+uFDKpGZHbrQgokXHQ1az1aFl4YK343OB6hcQAAEmNtAAAAAAAAAAAAABBwYXlsb2FkSWQAAAAAABBmaXJzdE9wZXJhdG9yAAEAAAAA", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDecimalNoPrecision": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.json deleted file mode 100644 index 801beefe1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.json +++ /dev/null @@ -1,585 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DecimalPrecision. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.yml deleted file mode 100644 index 98defcd9e..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Aggregate.yml +++ /dev/null @@ -1,330 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DecimalPrecision. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.json deleted file mode 100644 index b8a695361..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.json +++ /dev/null @@ -1,1648 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gte": { - "$numberDecimal": "0.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "1.0" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$lt": { - "$numberDecimal": "1.0" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$lte": { - "$numberDecimal": "1.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$lt": { - "$numberDecimal": "0.0" - } - } - } - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Find with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "200.0" - } - } - } - }, - "result": { - "errorContains": "must be less than the range max" - } - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0.0" - }, - "$lt": { - "$numberDecimal": "2.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gte": { - "$numberDecimal": "0.0" - }, - "$lte": { - "$numberDecimal": "200.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$in": [ - { - "$numberDecimal": "0.0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Insert out of range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "-1" - } - } - }, - "result": { - "errorContains": "value must be greater than or equal to the minimum value" - } - } - ] - }, - { - "description": "Insert min and max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 200, - "encryptedDecimalPrecision": { - "$numberDecimal": "200.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 200, - "encryptedDecimalPrecision": { - "$numberDecimal": "200.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gte": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "1.0" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$lt": { - "$numberDecimal": "1.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$lte": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$lt": { - "$numberDecimal": "0.0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Aggregate with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "200.0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be less than the range max" - } - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0.0" - }, - "$lt": { - "$numberDecimal": "2.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$gte": { - "$numberDecimal": "0.0" - }, - "$lte": { - "$numberDecimal": "200.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDecimalPrecision": { - "$in": [ - { - "$numberDecimal": "0.0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0.0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberInt": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gte": { - "$numberInt": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.yml deleted file mode 100644 index aa7ef7525..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Correctness.yml +++ /dev/null @@ -1,425 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0.0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1.0" } } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} - result: - errorContains: must be greater than the range minimum - - - description: "Find with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} - result: - errorContains: must be less than the range max - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } - result: [*doc1] - - - description: "Find with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } - result: [*doc0] - - - description: "Insert out of range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "-1" }} - result: - errorContains: value must be greater than or equal to the minimum value - - - description: "Insert min and max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: *doc0 - - name: insertOne - arguments: - document: &doc200 { _id: 200, encryptedDecimalPrecision: { $numberDecimal: "200.0" }} - - name: find - arguments: - filter: {} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc200] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} } - result: - errorContains: must be greater than the range minimum - - - description: "Aggregate with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} } - result: - errorContains: must be less than the range max - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } } - result: [*doc1] - - - description: "Aggregate with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedDecimalPrecision: { $numberInt: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gte: { $numberInt: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.json deleted file mode 100644 index 1abb59bfd..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.json +++ /dev/null @@ -1,471 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DecimalPrecision. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.yml deleted file mode 100644 index 446745fdc..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Delete.yml +++ /dev/null @@ -1,227 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DecimalPrecision. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.json deleted file mode 100644 index 8d763431f..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.json +++ /dev/null @@ -1,589 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DecimalPrecision. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - }, - "update": { - "$set": { - "encryptedDecimalPrecision": { - "$numberDecimal": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml deleted file mode 100644 index f90616a88..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-FindOneAndUpdate.yml +++ /dev/null @@ -1,328 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DecimalPrecision. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedDecimalPrecision: { $gt: {$numberDecimal: "0"}} } - update: { "$set": { "encryptedDecimalPrecision": {$numberDecimal: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedDecimalPrecision": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": { - "$numberInt": "0" - }, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": { - "$numberInt": "1" - }, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.json deleted file mode 100644 index 5407fba18..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.json +++ /dev/null @@ -1,572 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DecimalPrecision. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.yml deleted file mode 100644 index e8e76cc95..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-InsertFind.yml +++ /dev/null @@ -1,320 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DecimalPrecision. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } - - name: find - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.json deleted file mode 100644 index e5d1a4e05..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.json +++ /dev/null @@ -1,589 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DecimalPrecision. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDecimalPrecision": { - "$numberDecimal": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDecimalPrecision": { - "$numberDecimal": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedDecimalPrecision": { - "$gt": { - "$numberDecimal": "0" - } - } - }, - "update": { - "$set": { - "encryptedDecimalPrecision": { - "$numberDecimal": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDecimalPrecision": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDecimalPrecision", - "bsonType": "decimal", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDecimal": "0.0" - }, - "max": { - "$numberDecimal": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDecimalPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.yml deleted file mode 100644 index 87dfe7077..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DecimalPrecision-Update.yml +++ /dev/null @@ -1,337 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DecimalPrecision. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } - - name: updateOne - arguments: - filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } - update: { "$set": { "encryptedDecimalPrecision": { $numberDecimal: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedDecimalPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDecimalPrecision": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDecimalPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.json deleted file mode 100644 index d8c9cacdc..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.json +++ /dev/null @@ -1,1133 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Double. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.yml deleted file mode 100644 index e9711a81f..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Aggregate.yml +++ /dev/null @@ -1,914 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Double. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.json deleted file mode 100644 index 65594bcb1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.json +++ /dev/null @@ -1,1158 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gte": { - "$numberDouble": "0.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "1.0" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$lt": { - "$numberDouble": "1.0" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$lte": { - "$numberDouble": "1.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0.0" - }, - "$lt": { - "$numberDouble": "2.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$in": [ - { - "$numberDouble": "0.0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gte": { - "$numberDouble": "0.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "1.0" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$lt": { - "$numberDouble": "1.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$lte": { - "$numberDouble": "1.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0.0" - }, - "$lt": { - "$numberDouble": "2.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoubleNoPrecision": { - "$in": [ - { - "$numberDouble": "0.0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberInt": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gte": { - "$numberInt": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.yml deleted file mode 100644 index 38e6caf44..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Correctness.yml +++ /dev/null @@ -1,293 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } - result: [*doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } - result: [*doc0] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } } - result: [*doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedDoubleNoPrecision: { $numberInt: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gte: { $numberInt: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.json deleted file mode 100644 index 392e722f1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.json +++ /dev/null @@ -1,727 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Double. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.yml deleted file mode 100644 index 7992fcdd9..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Delete.yml +++ /dev/null @@ -1,519 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Double. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.json deleted file mode 100644 index bbcfb321f..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.json +++ /dev/null @@ -1,1137 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Double. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0" - } - } - }, - "update": { - "$set": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.yml deleted file mode 100644 index 9fbdfdbb1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-FindOneAndUpdate.yml +++ /dev/null @@ -1,912 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Double. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: {$numberDouble: "0"}} } - update: { "$set": { "encryptedDoubleNoPrecision": {$numberDouble: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedDoubleNoPrecision": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.json deleted file mode 100644 index 9f2c7c991..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.json +++ /dev/null @@ -1,1124 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Double. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.yml deleted file mode 100644 index 306cb7261..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-InsertFind.yml +++ /dev/null @@ -1,908 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Double. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - - name: find - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.json deleted file mode 100644 index ce03576f8..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.json +++ /dev/null @@ -1,1141 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Double. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$numberDouble": "0" - } - } - }, - "update": { - "$set": { - "encryptedDoubleNoPrecision": { - "$numberDouble": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDoubleNoPrecision": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoubleNoPrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoubleNoPrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.yml deleted file mode 100644 index 19912facf..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Double-Update.yml +++ /dev/null @@ -1,925 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Double. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } - - name: updateOne - arguments: - filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } - update: { "$set": { "encryptedDoubleNoPrecision": { $numberDouble: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedDoubleNoPrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDoubleNoPrecision": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoubleNoPrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.json deleted file mode 100644 index b121c72f1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.json +++ /dev/null @@ -1,581 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DoublePrecision. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.yml deleted file mode 100644 index 2700de4ad..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Aggregate.yml +++ /dev/null @@ -1,326 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DoublePrecision. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.json deleted file mode 100644 index 6b42ecfe8..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.json +++ /dev/null @@ -1,1648 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gte": { - "$numberDouble": "0.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "1.0" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$lt": { - "$numberDouble": "1.0" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$lte": { - "$numberDouble": "1.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$lt": { - "$numberDouble": "0.0" - } - } - } - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Find with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "200.0" - } - } - } - }, - "result": { - "errorContains": "must be less than the range max" - } - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0.0" - }, - "$lt": { - "$numberDouble": "2.0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gte": { - "$numberDouble": "0.0" - }, - "$lte": { - "$numberDouble": "200.0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$in": [ - { - "$numberDouble": "0.0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Insert out of range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "-1" - } - } - }, - "result": { - "errorContains": "value must be greater than or equal to the minimum value" - } - } - ] - }, - { - "description": "Insert min and max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 200, - "encryptedDoublePrecision": { - "$numberDouble": "200.0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 200, - "encryptedDoublePrecision": { - "$numberDouble": "200.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gte": { - "$numberDouble": "0.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "1.0" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$lt": { - "$numberDouble": "1.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$lte": { - "$numberDouble": "1.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$lt": { - "$numberDouble": "0.0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Aggregate with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "200.0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be less than the range max" - } - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0.0" - }, - "$lt": { - "$numberDouble": "2.0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$gte": { - "$numberDouble": "0.0" - }, - "$lte": { - "$numberDouble": "200.0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1.0" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedDoublePrecision": { - "$in": [ - { - "$numberDouble": "0.0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0.0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberInt": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Int", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gte": { - "$numberInt": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.yml deleted file mode 100644 index 6508db7d1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Correctness.yml +++ /dev/null @@ -1,425 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0.0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1.0" } } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} - result: - errorContains: must be greater than the range minimum - - - description: "Find with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} - result: - errorContains: must be less than the range max - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $numberDouble: "0.0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedDoublePrecision: { $numberDouble: "1.0" } } - result: [*doc1] - - - description: "Find with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } - result: [*doc0] - - - description: "Insert out of range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: { _id: 0, encryptedDoublePrecision: { $numberDouble: "-1" }} - result: - errorContains: value must be greater than or equal to the minimum value - - - description: "Insert min and max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: *doc0 - - name: insertOne - arguments: - document: &doc200 { _id: 200, encryptedDoublePrecision: { $numberDouble: "200.0" }} - - name: find - arguments: - filter: {} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc200] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} } - result: - errorContains: must be greater than the range minimum - - - description: "Aggregate with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} } - result: - errorContains: must be less than the range max - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $numberDouble: "0.0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $numberDouble: "1.0" } } } - result: [*doc1] - - - description: "Aggregate with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedDoublePrecision: { $numberInt: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Int" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gte: { $numberInt: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.json deleted file mode 100644 index a5c397d0b..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.json +++ /dev/null @@ -1,469 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DoublePrecision. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.yml deleted file mode 100644 index e5a35c8ee..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Delete.yml +++ /dev/null @@ -1,225 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DoublePrecision. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.json deleted file mode 100644 index b6df9463e..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.json +++ /dev/null @@ -1,585 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DoublePrecision. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0" - } - } - }, - "update": { - "$set": { - "encryptedDoublePrecision": { - "$numberDouble": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml deleted file mode 100644 index e74b6ecb7..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-FindOneAndUpdate.yml +++ /dev/null @@ -1,324 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DoublePrecision. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedDoublePrecision: { $gt: {$numberDouble: "0"}} } - update: { "$set": { "encryptedDoublePrecision": {$numberDouble: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedDoublePrecision": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.json deleted file mode 100644 index 1cea25545..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.json +++ /dev/null @@ -1,572 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DoublePrecision. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.yml deleted file mode 100644 index bc15c9b88..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-InsertFind.yml +++ /dev/null @@ -1,320 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DoublePrecision. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } - - name: find - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.json deleted file mode 100644 index 7703c9057..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.json +++ /dev/null @@ -1,589 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range DoublePrecision. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedDoublePrecision": { - "$numberDouble": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedDoublePrecision": { - "$numberDouble": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedDoublePrecision": { - "$gt": { - "$numberDouble": "0" - } - } - }, - "update": { - "$set": { - "encryptedDoublePrecision": { - "$numberDouble": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDoublePrecision": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedDoublePrecision", - "bsonType": "double", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberDouble": "0.0" - }, - "max": { - "$numberDouble": "200.0" - }, - "precision": { - "$numberInt": "2" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedDoublePrecision": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.yml deleted file mode 100644 index 3211b5047..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-DoublePrecision-Update.yml +++ /dev/null @@ -1,339 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range DoublePrecision. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } - - name: updateOne - arguments: - filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } - update: { "$set": { "encryptedDoublePrecision": { $numberDouble: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedDoublePrecision": { - "$gt": { - "$binary": { - "base64": "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", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedDoublePrecision": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedDoublePrecision": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.json deleted file mode 100644 index 9c2536264..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.json +++ /dev/null @@ -1,485 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Int. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.yml deleted file mode 100644 index cb7765faa..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Aggregate.yml +++ /dev/null @@ -1,242 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Int. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedInt": { $gt: {$numberInt: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.json deleted file mode 100644 index 58ccf3efc..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.json +++ /dev/null @@ -1,1642 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gte": { - "$numberInt": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "1" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$lt": { - "$numberInt": "1" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$lte": { - "$numberInt": "1" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$lt": { - "$numberInt": "0" - } - } - } - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Find with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "200" - } - } - } - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - }, - "$lt": { - "$numberInt": "2" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$numberInt": "0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$numberInt": "1" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gte": { - "$numberInt": "0" - }, - "$lte": { - "$numberInt": "200" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$in": [ - { - "$numberInt": "0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - } - ] - }, - { - "description": "Insert out of range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "-1" - } - } - }, - "result": { - "errorContains": "value must be greater than or equal to the minimum value" - } - } - ] - }, - { - "description": "Insert min and max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 200, - "encryptedInt": { - "$numberInt": "200" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 200, - "encryptedInt": { - "$numberInt": "200" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gte": { - "$numberInt": "0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$numberInt": "1" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$lt": { - "$numberInt": "1" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$lte": { - "$numberInt": "1" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$lt": { - "$numberInt": "0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Aggregate with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$numberInt": "200" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - }, - "$lt": { - "$numberInt": "2" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$numberInt": "0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$numberInt": "1" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$gte": { - "$numberInt": "0" - }, - "$lte": { - "$numberInt": "200" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - }, - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedInt": { - "$in": [ - { - "$numberInt": "0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberDouble": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gte": { - "$numberDouble": "0" - } - } - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.yml deleted file mode 100644 index 77206aed6..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Correctness.yml +++ /dev/null @@ -1,424 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: find - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $gte: { $numberInt: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "1" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $lt: { $numberInt: "1" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $lte: { $numberInt: "1" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $lt: { $numberInt: "0" } }} - result: - errorContains: must be greater than the range minimum - - - description: "Find with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "200" } }} - result: - errorContains: must be less than the range maximum - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $numberInt: "0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedInt: { $numberInt: "1" } } - result: [*doc1] - - - description: "Find with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } - result: [*doc0] - - - description: "Insert out of range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: { _id: 0, encryptedInt: { $numberInt: "-1" }} - result: - errorContains: value must be greater than or equal to the minimum value - - - description: "Insert min and max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: *doc0 - - name: insertOne - arguments: - document: &doc200 { _id: 200, encryptedInt: { $numberInt: "200" }} - - name: find - arguments: - filter: {} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc200] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $gte: { $numberInt: "0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $gt: { $numberInt: "1" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $lt: { $numberInt: "1" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $lte: { $numberInt: "1" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $lt: { $numberInt: "0" } }} } - result: - errorContains: must be greater than the range minimum - - - description: "Aggregate with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $gt: { $numberInt: "200" } }} } - result: - errorContains: must be less than the range maximum - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $numberInt: "0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $numberInt: "1" } } } - result: [*doc1] - - - description: "Aggregate with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.json deleted file mode 100644 index b20b2750b..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.json +++ /dev/null @@ -1,415 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Int. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.yml deleted file mode 100644 index 59f58650e..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Delete.yml +++ /dev/null @@ -1,183 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Int. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedInt": { $gt: {$numberInt: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.json deleted file mode 100644 index f9c189ace..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.json +++ /dev/null @@ -1,489 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Int. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - }, - "update": { - "$set": { - "encryptedInt": { - "$numberInt": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedInt": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.yml deleted file mode 100644 index f49f319e2..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-FindOneAndUpdate.yml +++ /dev/null @@ -1,240 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Int. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedInt: { $gt: {$numberInt: "0"}} } - update: { "$set": { "encryptedInt": {$numberInt: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedInt": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.json deleted file mode 100644 index 874d4760c..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.json +++ /dev/null @@ -1,476 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Int. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.yml deleted file mode 100644 index d848bbdf7..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-InsertFind.yml +++ /dev/null @@ -1,236 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Int. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: find - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.json deleted file mode 100644 index c2b62b4d1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.json +++ /dev/null @@ -1,493 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Int. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedInt": { - "$numberInt": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedInt": { - "$gt": { - "$numberInt": "0" - } - } - }, - "update": { - "$set": { - "encryptedInt": { - "$numberInt": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedInt": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedInt": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.yml deleted file mode 100644 index a18878f55..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Int-Update.yml +++ /dev/null @@ -1,255 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Int. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } - - name: updateOne - arguments: - filter: { encryptedInt: { $gt: { $numberInt: "0" } } } - update: { "$set": { "encryptedInt": { $numberInt: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedInt": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedInt": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedInt": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.json deleted file mode 100644 index afc0f97be..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.json +++ /dev/null @@ -1,485 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Long. Aggregate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "default", - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ], - "cursor": {}, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "aggregate" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.yml deleted file mode 100644 index 9837cf6e4..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Aggregate.yml +++ /dev/null @@ -1,242 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Long. Aggregate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: aggregate - arguments: - pipeline: [{ $match: { "encryptedLong": { $gt: {$numberLong: "0" }} } }] - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - aggregate: *collection_name - pipeline: [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - } - ] - cursor: {} - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: aggregate - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.json deleted file mode 100644 index cda941de8..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.json +++ /dev/null @@ -1,1642 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Find with $gt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gte": { - "$numberLong": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "1" - } - } - } - }, - "result": [] - } - ] - }, - { - "description": "Find with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$lt": { - "$numberLong": "1" - } - } - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - } - ] - }, - { - "description": "Find with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$lte": { - "$numberLong": "1" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$lt": { - "$numberLong": "0" - } - } - } - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Find with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "200" - } - } - } - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Find with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - }, - "$lt": { - "$numberLong": "2" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$numberLong": "0" - } - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$numberLong": "1" - } - } - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gte": { - "$numberLong": "0" - }, - "$lte": { - "$numberLong": "200" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Find with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$in": [ - { - "$numberLong": "0" - } - ] - } - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - } - ] - }, - { - "description": "Insert out of range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "-1" - } - } - }, - "result": { - "errorContains": "value must be greater than or equal to the minimum value" - } - } - ] - }, - { - "description": "Insert min and max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 200, - "encryptedLong": { - "$numberLong": "200" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - } - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 200, - "encryptedLong": { - "$numberLong": "200" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gte": { - "$numberLong": "0" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $gt with no results", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$numberLong": "1" - } - } - } - } - ] - }, - "result": [] - } - ] - }, - { - "description": "Aggregate with $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$lt": { - "$numberLong": "1" - } - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lte", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$lte": { - "$numberLong": "1" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $lt below min", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$lt": { - "$numberLong": "0" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be greater than the range minimum" - } - } - ] - }, - { - "description": "Aggregate with $gt above max", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$numberLong": "200" - } - } - } - } - ] - }, - "result": { - "errorContains": "must be less than the range maximum" - } - } - ] - }, - { - "description": "Aggregate with $gt and $lt", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - }, - "$lt": { - "$numberLong": "2" - } - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with equality", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$numberLong": "0" - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$numberLong": "1" - } - } - } - ] - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with full range", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$gte": { - "$numberLong": "0" - }, - "$lte": { - "$numberLong": "200" - } - } - } - }, - { - "$sort": { - "_id": 1 - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - }, - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ] - }, - { - "description": "Aggregate with $in", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "encryptedLong": { - "$in": [ - { - "$numberLong": "0" - } - ] - } - } - } - ] - }, - "result": [ - { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - ] - } - ] - }, - { - "description": "Wrong type: Insert Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberDouble": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gte": { - "$numberDouble": "0" - } - } - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.yml deleted file mode 100644 index 4df64cb67..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Correctness.yml +++ /dev/null @@ -1,423 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Find with $gt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: find - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "0" } }} - result: [*doc1] - - - description: "Find with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $gte: { $numberLong: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "1" } }} - result: [] - - - description: "Find with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $lt: { $numberLong: "1" } }} - result: [*doc0] - - - description: "Find with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $lte: { $numberLong: "1" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $lt: { $numberLong: "0" } }} - result: - errorContains: must be greater than the range minimum - - - description: "Find with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "200" } }} - result: - errorContains: must be less than the range maximum - - - description: "Find with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} - result: [*doc1] - - - description: "Find with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $numberLong: "0" } } - result: [*doc0] - - name: find - arguments: - filter: { encryptedLong: { $numberLong: "1" } } - result: [*doc1] - - - description: "Find with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc1] - - - description: "Find with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: find - arguments: - filter: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } - result: [*doc0] - - - description: "Insert out of range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: { _id: 0, encryptedLong: { $numberLong: "-1" }} - result: - errorContains: value must be greater than or equal to the minimum value - - - description: "Insert min and max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: *doc0 - - name: insertOne - arguments: - document: &doc200 { _id: 200, encryptedLong: { $numberLong: "200" }} - - name: find - arguments: - filter: {} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: [*doc0, *doc200] - - - description: "Aggregate with $gte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $gte: { $numberLong: "0" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $gt with no results" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $gt: { $numberLong: "1" } }} } - result: [] - - - description: "Aggregate with $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $lt: { $numberLong: "1" } }} } - result: [*doc0] - - - description: "Aggregate with $lte" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $lte: { $numberLong: "1" } }} } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $lt below min" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $lt: { $numberLong: "0" } }} } - result: - errorContains: must be greater than the range minimum - - - description: "Aggregate with $gt above max" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $gt: { $numberLong: "200" } }} } - result: - errorContains: must be less than the range maximum - - - description: "Aggregate with $gt and $lt" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} } - result: [*doc1] - - - description: "Aggregate with equality" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $numberLong: "0" } } } - result: [*doc0] - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $numberLong: "1" } } } - result: [*doc1] - - - description: "Aggregate with full range" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } } - # sort so results from range queries are ordered. - - { $sort: { _id: 1 }} - result: [*doc0, *doc1] - - - description: "Aggregate with $in" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: *doc0 } - - name: insertOne - arguments: { document: *doc1 } - - name: aggregate - arguments: - pipeline: - - { $match: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } } - result: [*doc0] - - - description: "Wrong type: Insert Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedLong: { $numberDouble: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: find - arguments: - filter: { encryptedLong: { $gte: { $numberDouble: "0" } }} - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.json deleted file mode 100644 index ad344e21b..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.json +++ /dev/null @@ -1,415 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Long. Delete.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - } - }, - "result": { - "deletedCount": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "delete": "default", - "deletes": [ - { - "q": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "delete" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.yml deleted file mode 100644 index 1df1150c7..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Delete.yml +++ /dev/null @@ -1,183 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Long. Delete." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: deleteOne - arguments: - filter: { "encryptedLong": { $gt: {$numberLong: "0" }} } - result: - deletedCount: 1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - delete: *collection_name - deletes: [ - { - "q": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "limit": 1 - } - ] - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: delete - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.json deleted file mode 100644 index d44720046..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.json +++ /dev/null @@ -1,489 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Long. FindOneAndUpdate.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - }, - "update": { - "$set": { - "encryptedLong": { - "$numberLong": "2" - } - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default", - "query": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "update": { - "$set": { - "encryptedLong": { - "$$type": "binData" - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "findAndModify" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.yml deleted file mode 100644 index 24f5d45c9..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-FindOneAndUpdate.yml +++ /dev/null @@ -1,240 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Long. FindOneAndUpdate." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: findOneAndUpdate - arguments: - filter: { encryptedLong: { $gt: {$numberLong: "0"}} } - update: { "$set": { "encryptedLong": {$numberLong: "2"}}} - returnDocument: Before - result: *doc1 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - findAndModify: *collection_name - query: { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - } - update: { "$set": {"encryptedLong": { $$type: "binData" }} } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: findAndModify - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.json deleted file mode 100644 index 4eb837f28..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.json +++ /dev/null @@ -1,476 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Long. Insert and Find.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - } - }, - "result": [ - { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.yml deleted file mode 100644 index 8e2f826f7..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-InsertFind.yml +++ /dev/null @@ -1,236 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Long. Insert and Find." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: find - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "0" } } } - result: [*doc1] - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - find: *collection_name - filter: - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: find - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "bE1vqWj3KNyM7cCYUv/cnYm8BPaUL3eMp5syTHq6NF4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.json deleted file mode 100644 index 3ba7f17c1..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.json +++ /dev/null @@ -1,493 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "FLE2 Range Long. Update.", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedLong": { - "$numberLong": "0" - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedLong": { - "$numberLong": "1" - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "encryptedLong": { - "$gt": { - "$numberLong": "0" - } - } - }, - "update": { - "$set": { - "encryptedLong": { - "$numberLong": "2" - } - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command_name": "update", - "command": { - "update": "default", - "ordered": true, - "updates": [ - { - "q": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedLong": { - "$$type": "binData" - } - } - } - } - ], - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedLong", - "bsonType": "long", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberLong": "0" - }, - "max": { - "$numberLong": "200" - } - } - } - ] - } - } - }, - "$db": "default" - } - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - }, - { - "_id": 1, - "encryptedLong": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.yml deleted file mode 100644 index c293a6409..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-Long-Update.yml +++ /dev/null @@ -1,255 +0,0 @@ - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "FLE2 Range Long. Update." - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: - document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } - - name: insertOne - arguments: - document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } - - name: updateOne - arguments: - filter: { encryptedLong: { $gt: { $numberLong: "0" } } } - update: { "$set": { "encryptedLong": { $numberLong: "2" } }} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectations: - - command_started_event: - command: - listCollections: 1 - filter: - name: *collection_name - command_name: listCollections - - command_started_event: - command: - find: datakeys - filter: { - "$or": [ - { - "_id": { - "$in": [ - {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - } - $db: keyvault - readConcern: { level: "majority" } - command_name: find - - command_started_event: - command: - insert: *collection_name - documents: - - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command: - insert: *collection_name - documents: - - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } - ordered: true - encryptionInformation: - type: 1 - schema: - default.default: - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - command_name: insert - - command_started_event: - command_name: update - command: - - "update": "default" - "ordered": true - "updates": [ - { - "q": { - "encryptedLong": { - "$gt": { - "$binary": { - "base64": "DUkFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAAA==", - "subType": "06" - } - } - } - }, - "u": { - "$set": { - "encryptedLong": { $$type: "binData" } - } - } - } - ] - encryptionInformation: - type: 1 - schema: - "default.default": - # libmongocrypt applies escCollection and ecocCollection to outgoing command. - escCollection: "enxcol_.default.esc" - ecocCollection: "enxcol_.default.ecoc" - <<: *encrypted_fields - "$db": "default" - - - outcome: - collection: - # Outcome is checked using a separate MongoClient without auto encryption. - data: - - - { - "_id": 0, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "5nRutVIyq7URVOVtbE4vM01APSIajAVnsShMwjBlzkM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", - "subType": "00" - } - } - ] - } - - - { - "_id": 1, - "encryptedLong": { $$type: "binData" }, - "__safeContent__": [ - { - "$binary": { - "base64": "DLCAJs+W2PL2DV5YChCL6dYrQNr+j4p3L7xhVaub4ic=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", - "subType": "00" - } - }, - { - "$binary": { - "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", - "subType": "00" - } - } - ] - } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.json deleted file mode 100644 index e5e9ddc82..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedInt", - "bsonType": "int", - "queries": { - "queryType": "rangePreview", - "contention": { - "$numberLong": "0" - }, - "sparsity": { - "$numberLong": "1" - }, - "min": { - "$numberInt": "0" - }, - "max": { - "$numberInt": "200" - } - } - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "Wrong type: Insert Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberDouble": "0" - } - } - }, - "result": { - "errorContains": "cannot encrypt element" - } - } - ] - }, - { - "description": "Wrong type: Find Double", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 0, - "encryptedInt": { - "$numberInt": "0" - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "encryptedInt": { - "$gte": { - "$numberDouble": "0" - } - } - }, - "sort": { - "_id": 1 - } - }, - "result": { - "errorContains": "field type is not supported" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.yml deleted file mode 100644 index ada65209e..000000000 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Range-WrongType.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Test correctness results. -# Does not include command monitoring expectations or outcome assertions to make tests more readable. - -# Requires libmongocrypt 1.8.0. -runOn: - - minServerVersion: "7.0.0" - serverless: "forbid" - # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. - # FLE 2 Encrypted collections are not supported on standalone. - topology: [ "replicaset", "sharded", "load-balanced" ] -database_name: &database_name "default" -collection_name: &collection_name "default" -data: [] -encrypted_fields: &encrypted_fields { 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'rangePreview', 'contention': {'$numberLong': '0'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} -key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] -tests: - - description: "Wrong type: Insert Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } - result: - # Expect an error from mongocryptd. - errorContains: "cannot encrypt element" - - - description: "Wrong type: Find Double" - clientOptions: - autoEncryptOpts: - kmsProviders: - local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} - operations: - - name: insertOne - arguments: { document: { _id: 0, encryptedInt: { $numberInt: "0" }} } - - name: find - arguments: - filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} - # sort so results from range queries are ordered. - sort: { _id: 1 } - result: - # expect an error from libmongocrypt. - errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json new file mode 100644 index 000000000..59241927c --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.json @@ -0,0 +1,289 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Compact works with 'range' fields", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "command_name": "compactStructuredEncryptionData", + "arguments": { + "command": { + "compactStructuredEncryptionData": "default" + } + }, + "result": { + "ok": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "compactStructuredEncryptionData": "default", + "compactionTokens": { + "encryptedInt": { + "ecoc": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + }, + "anchorPaddingToken": { + "$binary": { + "base64": "QxKJD2If48p0l8NAXf2Kr0aleMd/dATSjBK6hTpNMyc=", + "subType": "00" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "compactStructuredEncryptionData" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.yml new file mode 100644 index 000000000..4a36409d3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Compact.yml @@ -0,0 +1,93 @@ +# Requires libmongocrypt 1.10.1. +runOn: + - minServerVersion: "8.0.0" # Require range v2 support on server. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Compact works with 'range' fields" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + # Insert before running compact to ensure the server errors if `encryptionInformation` is incorrectly omitted. + - name: insertOne + arguments: + document: { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: runCommand + object: database + command_name: compactStructuredEncryptionData + arguments: + command: + compactStructuredEncryptionData: *collection_name + result: + ok: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: &encryptionInformation + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + compactStructuredEncryptionData: *collection_name + compactionTokens: + "encryptedInt": { + "ecoc": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + }, + "anchorPaddingToken": { + "$binary": { + "base64": "QxKJD2If48p0l8NAXf2Kr0aleMd/dATSjBK6hTpNMyc=", + "subType": "00" + } + } + } + encryptionInformation: *encryptionInformation + command_name: compactStructuredEncryptionData \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.json new file mode 100644 index 000000000..df2161cc3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.json @@ -0,0 +1,508 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Date. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.yml new file mode 100644 index 000000000..cc5fd4d26 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Aggregate.yml @@ -0,0 +1,229 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.json new file mode 100644 index 000000000..fae25a1c0 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.json @@ -0,0 +1,1842 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gte": { + "$date": { + "$numberLong": "0" + } + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$lt": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$lte": { + "$date": { + "$numberLong": "1" + } + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$lt": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Find with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "200" + } + } + } + } + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + }, + "$lt": { + "$date": { + "$numberLong": "2" + } + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gte": { + "$date": { + "$numberLong": "0" + } + }, + "$lte": { + "$date": { + "$numberLong": "200" + } + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$in": [ + { + "$date": { + "$numberLong": "0" + } + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + } + ] + }, + { + "description": "Insert out of range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "-1" + } + } + } + }, + "result": { + "errorContains": "value must be greater than or equal to the minimum value" + } + } + ] + }, + { + "description": "Insert min and max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 200, + "encryptedDate": { + "$date": { + "$numberLong": "200" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 200, + "encryptedDate": { + "$date": { + "$numberLong": "200" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gte": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "1" + } + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$lt": { + "$date": { + "$numberLong": "1" + } + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$lte": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$lt": { + "$date": { + "$numberLong": "0" + } + } + } + } + } + ] + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Aggregate with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "200" + } + } + } + } + } + ] + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + }, + "$lt": { + "$date": { + "$numberLong": "2" + } + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$gte": { + "$date": { + "$numberLong": "0" + } + }, + "$lte": { + "$date": { + "$numberLong": "200" + } + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + }, + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDate": { + "$in": [ + { + "$date": { + "$numberLong": "0" + } + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$numberDouble": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gte": { + "$numberDouble": "0" + } + } + } + }, + "result": { + "errorContains": "value type is a date" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.yml new file mode 100644 index 000000000..f7ed9fb93 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Correctness.yml @@ -0,0 +1,422 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } } } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $date: { $numberLong: "0" } } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDate: { $date: { $numberLong: "1" } } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $gte: { $date: {$numberLong: "0"}}, $lte: { $date: {$numberLong: "200"} } } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDate: {$date: { $numberLong: "-1" }}} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDate: { $date: { $numberLong: "200" } }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gte: { $date: { $numberLong: "0" } } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "1" } } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "1" } } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lte: { $date: { $numberLong: "1" } } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $lt: { $date: { $numberLong: "0" } } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "200" } } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gt: { $date: { $numberLong: "0" } }, $lt: { $date: {$numberLong: "2"}} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $date: { $numberLong: "0" } } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $date: { $numberLong: "1" } } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $gte: {$date: {$numberLong: "0"}}, $lte: {$date: {$numberLong: "200"}} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDate: { $in: [ {$date: {$numberLong: "0"}} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDate: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDate: { $gte: { $numberDouble: "0" } }} + result: + # expect an error mongocryptd. + errorContains: "value type is a date" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.json new file mode 100644 index 000000000..b4f15d9b1 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.json @@ -0,0 +1,442 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Date. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.yml new file mode 100644 index 000000000..188b75940 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Delete.yml @@ -0,0 +1,176 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: deleteOne + arguments: + filter: { "encryptedDate": { $gt: {$date: {$numberLong: "0" }}} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.json new file mode 100644 index 000000000..97ab4aaeb --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.json @@ -0,0 +1,514 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Date. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + }, + "update": { + "$set": { + "encryptedDate": { + "$date": { + "$numberLong": "2" + } + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedDate": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.yml new file mode 100644 index 000000000..2c53412e9 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-FindOneAndUpdate.yml @@ -0,0 +1,227 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: {$date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: {$date: { $numberLong: "1" }} } + - name: findOneAndUpdate + arguments: + filter: { encryptedDate: { $gt: {$date: {$numberLong: "0"}}} } + update: { "$set": { "encryptedDate": {$date: {$numberLong: "2"}}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDate": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.json new file mode 100644 index 000000000..a011c388e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.json @@ -0,0 +1,499 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Date. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.yml new file mode 100644 index 000000000..8188fc6e3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-InsertFind.yml @@ -0,0 +1,223 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" }} } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" }} } + - name: find + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" }} } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.json new file mode 100644 index 000000000..6bab6499f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.json @@ -0,0 +1,516 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Date. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDate": { + "$date": { + "$numberLong": "0" + } + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDate": { + "$date": { + "$numberLong": "1" + } + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedDate": { + "$gt": { + "$date": { + "$numberLong": "0" + } + } + } + }, + "update": { + "$set": { + "encryptedDate": { + "$date": { + "$numberLong": "2" + } + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDate": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDate", + "bsonType": "date", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$date": { + "$numberLong": "0" + } + }, + "max": { + "$date": { + "$numberLong": "200" + } + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDate": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.yml new file mode 100644 index 000000000..3018726b6 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Date-Update.yml @@ -0,0 +1,240 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDate', 'bsonType': 'date', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$date': {'$numberLong': '0'}}, 'max': {'$date': {'$numberLong': '200'}}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Date. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDate: { $date: { $numberLong: "0" } }} + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDate: { $date: { $numberLong: "1" } }} + - name: updateOne + arguments: + filter: { encryptedDate: { $gt: { $date: { $numberLong: "0" } } }} + update: { "$set": { "encryptedDate": { $date: { $numberLong: "2" } }}} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDate": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDate": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAJbW4AAAAAAAAAAAAJbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDate": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDate": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.json new file mode 100644 index 000000000..d1a82c216 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.json @@ -0,0 +1,1902 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Decimal. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.yml new file mode 100644 index 000000000..97a95b2d9 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Aggregate.yml @@ -0,0 +1,1675 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } + \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.json new file mode 100644 index 000000000..4316a31c3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.json @@ -0,0 +1,1158 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gte": { + "$numberDecimal": "0.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "1.0" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$lt": { + "$numberDecimal": "1.0" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$lte": { + "$numberDecimal": "1.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0.0" + }, + "$lt": { + "$numberDecimal": "2.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$in": [ + { + "$numberDecimal": "0.0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gte": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "1.0" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$lt": { + "$numberDecimal": "1.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$lte": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0.0" + }, + "$lt": { + "$numberDecimal": "2.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalNoPrecision": { + "$in": [ + { + "$numberDecimal": "0.0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberInt": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gte": { + "$numberInt": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.yml new file mode 100644 index 000000000..815462479 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Correctness.yml @@ -0,0 +1,293 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } + result: [*doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } + result: [*doc0] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $gte: { $numberDecimal: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $lt: { $numberDecimal: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $lte: { $numberDecimal: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $numberDecimal: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalNoPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDecimalNoPrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.json new file mode 100644 index 000000000..19cae3c64 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.json @@ -0,0 +1,1116 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Decimal. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "DR1jAAADcGF5bG9hZACxYgAABGcAnWIAAAMwAH0AAAAFZAAgAAAAAJu2KgiI8vM+kz9qD3ZQzFQY5qbgYqCqHG5R4jAlnlwXBXMAIAAAAAAAUXxFXsz764T79sGCdhxvNd5b6E/9p61FonsHyEIhogVsACAAAAAAt19RL3Oo5ni5L8kcvgOJYLgVYyXJExwP8pkuzLG7f/kAAzEAfQAAAAVkACAAAAAAPQPvL0ARjujSv2Rkm8r7spVsgeC1K3FWcskGGZ3OdDIFcwAgAAAAACgNn660GmefR8jLqzgR1u5O+Uocx9GyEHiBqVGko5FZBWwAIAAAAADflr+fsnZngm6KRWYgHa9JzK+bXogWl9evBU9sQUHPHQADMgB9AAAABWQAIAAAAAD2Zi6kcxmaD2mY3VWrP+wYJMPg6cSBIYPapxaFQxYFdQVzACAAAAAAM/cV36BLBY3xFBXsXJY8M9EHHOc/qrmdc2CJmj3M89gFbAAgAAAAAOpydOrKxx6m2gquSDV2Vv3w10GocmNCFeOo/fRhRH9JAAMzAH0AAAAFZAAgAAAAAOaNqI9srQ/mI9gwbk+VkizGBBH/PPWOVusgnfPk3tY1BXMAIAAAAAAc96O/pwKCmHCagT6T/QV/wz4vqO+R22GsZ1dse2Vg6QVsACAAAAAAgzIak+Q3UFLTHXPmJ+MuEklFtR3eLtvM+jdKkmGCV/YAAzQAfQAAAAVkACAAAAAA0XlQgy/Yu97EQOjronl9b3dcR1DFn3deuVhtTLbJZHkFcwAgAAAAACoMnpVl6EFJak8A+t5N4RFnQhkQEBnNAx8wDqmq5U/dBWwAIAAAAACR26FJif673qpwF1J1FEkQGJ1Ywcr/ZW6JQ7meGqzt1QADNQB9AAAABWQAIAAAAAAOtpNexRxfv0yRFvZO9DhlkpU4mDuAb8ykdLnE5Vf1VAVzACAAAAAAeblFKm/30orP16uQpZslvsoS8s0xfNPIBlw3VkHeekYFbAAgAAAAAPEoHj87sYE+nBut52/LPvleWQBzB/uaJFnosxp4NRO2AAM2AH0AAAAFZAAgAAAAAIr8xAFm1zPmrvW4Vy5Ct0W8FxMmyPmFzdWVzesBhAJFBXMAIAAAAABYeeXjJEzTHwxab6pUiCRiZjxgtN59a1y8Szy3hfkg+gVsACAAAAAAJuoY4rF8mbI+nKb+5XbZShJ8191o/e8ZCRHE0O4Ey8MAAzcAfQAAAAVkACAAAAAAl+ibLk0/+EwoqeC8S8cGgAtjtpQWGEZDsybMPnrrkwEFcwAgAAAAAHPPBudWgQ+HUorLDpJMqhS9VBF2VF5aLcxgrM1s+yU7BWwAIAAAAAAcCcBR2Vyv5pAFbaOU97yovuOi1+ATDnLLcAUqHecXcAADOAB9AAAABWQAIAAAAACR9erwLTb+tcWFZgJ2MEfM0PKI9uuwIjDTHADRFgD+SQVzACAAAAAAcOop8TXsGUVQoKhzUllMYWxL93xCOkwtIpV8Q6hiSYYFbAAgAAAAAKXKmh4V8veYwob1H03Q3p3PN8SRAaQwDT34KlNVUjiDAAM5AH0AAAAFZAAgAAAAALv0vCPgh7QpmM8Ug6ad5ioZJCh7pLMdT8FYyQioBQ6KBXMAIAAAAADsCPyIG8t6ApQkRk1fX/sfc1kpuWCWP8gAEpnYoBSHrQVsACAAAAAAJe/r67N6d8uTiogvfoR9rEXbIDjyLb9EVdqkayFFGaYAAzEwAH0AAAAFZAAgAAAAAIW4AxJgYoM0pcNTwk1RSbyjZGIqgKL1hcTJmNrnZmoPBXMAIAAAAAAZpfx3EFO0vY0f1eHnE0PazgqeNDTaj+pPJMUNW8lFrAVsACAAAAAAP+Um2vwW6Bj6vuz9DKz6+6aWkoKoEmFNoiz/xXm7lOsAAzExAH0AAAAFZAAgAAAAAKliO6L9zgeuufjj174hvmQGNRbmYYs9yAirL7OxwEW3BXMAIAAAAAAqU7vs3DWUQ95Eq8OejwWnD0GuXd+ASi/uD6S0l8MM1QVsACAAAAAAb9legYzsfctBPpHyl7YWpPmLr5QiNZFND/50N1vv2MUAAzEyAH0AAAAFZAAgAAAAAOGQcCBkk+j/Kzjt/Cs6g3BZPJG81wIHBS8JewHGpgk+BXMAIAAAAABjrxZXWCkdzrExwCgyHaafuPSQ4V4x2k9kUCAqUaYKDQVsACAAAAAADBU6KefT0v8zSmseaMNmQxKjJar72y7MojLFhkEHqrUAAzEzAH0AAAAFZAAgAAAAAPmCNEt4t97waOSd5hNi2fNCdWEkmcFJ37LI9k4Az4/5BXMAIAAAAABX7DuDPNg+duvELf3NbLWkPMFw2HGLgWGHyVWcPvSNCAVsACAAAAAAS7El1FtZ5STh8Q1FguvieyYX9b2DF1DFVsb9hzxXYRsAAzE0AH0AAAAFZAAgAAAAAD4vtVUYRNB+FD9yoQ2FVJH3nMeJeKbi6eZfth638YqbBXMAIAAAAAANCuUB4OdmuD6LaDK2f3vaqfgYYvg40wDXOBbcFjTqLwVsACAAAAAA9hqC2VoJBjwR7hcQ45xO8ZVojwC83jiRacCaDj6Px2gAAzE1AH0AAAAFZAAgAAAAAJPIRzjmTjbdIvshG6UslbEOd797ZSIdjGAhGWxVQvK1BXMAIAAAAABgmJ0Jh8WLs9IYs/a7DBjDWd8J3thW/AGJK7zDnMeYOAVsACAAAAAAi9zAsyAuou2oiCUHGc6QefLUkACa9IgeBhGu9W/r0X8AAzE2AH0AAAAFZAAgAAAAAABQyKQPoW8wGPIqnsTv69+DzIdRkohRhOhDmyVHkw9WBXMAIAAAAAAqWA2X4tB/h3O1Xlawtz6ndI6WaTwgU1QYflL35opu5gVsACAAAAAAWI/Gj5aZMwDIxztqmVL0g5LBcI8EdKEc2UA28pnekQoAAzE3AH0AAAAFZAAgAAAAACB7NOyGQ1Id3MYnxtBXqyZ5Ul/lHH6p1b10U63DfT6bBXMAIAAAAADpOryIcndxztkHSfLN3Kzq29sD8djS0PspDSqERMqokQVsACAAAAAADatsMW4ezgnyi1PiP7xk+gA4AFIN/fb5uJqfVkjg4UoAAzE4AH0AAAAFZAAgAAAAAKVfXLfs8XA14CRTB56oZwV+bFJN5BHraTXbqEXZDmTkBXMAIAAAAAASRWTsfGOpqdffiOodoqIgBzG/yzFyjR5CfUsIUIWGpgVsACAAAAAAkgCHbCwyX640/0Ni8+MoYxeHUiC+FSU4Mn9jTLYtgZgAAzE5AH0AAAAFZAAgAAAAAH/aZr4EuS0/noQR9rcF8vwoaxnxrwgOsSJ0ys8PkHhGBXMAIAAAAACd7ObGQW7qfddcvyxRTkPuvq/PHu7+6I5dxwS1Lzy5XAVsACAAAAAA3q0eKdV7KeU3pc+CtfypKR7BPxwaf30yu0j9FXeOOboAAzIwAH0AAAAFZAAgAAAAAKvlcpFFNq0oA+urq3w6d80PK1HHHw0H0yVWvU9aHijXBXMAIAAAAADWnAHQ5Fhlcjawki7kWzdqjM2f6IdGJblojrYElWjsZgVsACAAAAAAO0wvY66l24gx8nRxyVGC0QcTztIi81Kx3ndRhuZr6W4AAzIxAH0AAAAFZAAgAAAAAH/2aMezEOddrq+dNOkDrdqf13h2ttOnexZsJxG1G6PNBXMAIAAAAABNtgnibjC4VKy5poYjvdsBBnVvDTF/4mmEAxsXVgZVKgVsACAAAAAAqvadzJFLqQbs8WxgZ2D2X+XnaPSDMLCVVgWxx5jnLcYAAzIyAH0AAAAFZAAgAAAAAF2wZoDL6/V59QqO8vdRZWDpXpkV4h4KOCSn5e7x7nmzBXMAIAAAAADLZBu7LCYjbThaVUqMK14H/elrVOYIKJQCx4C9Yjw37gVsACAAAAAAEh6Vs81jLU204aGpL90fmYTm5i5R8/RT1uIbg6VU3HwAAzIzAH0AAAAFZAAgAAAAAH27yYaLn9zh2CpvaoomUPercSfJRUmBY6XFqmhcXi9QBXMAIAAAAAAUwumVlIYIs9JhDhSj0R0+59psCMsFk94E62VxkPt42QVsACAAAAAAT5x2hCCd2bpmpnyWaxas8nSxTc8e4C9DfKaqr0ABEysAAzI0AH0AAAAFZAAgAAAAALMg2kNAO4AFFs/mW3In04yFeN4AP6Vo0klyUoT06RquBXMAIAAAAAAgGWJbeIdwlpqXCyVIYSs0dt54Rfc8JF4b8uYc+YUj0AVsACAAAAAAWHeWxIkyvXTOWvfZzqtPXjfGaWWKjGSIQENTU3zBCrsAAzI1AH0AAAAFZAAgAAAAALas/i1T2DFCEmrrLEi7O2ngJZyFHialOoedVXS+OjenBXMAIAAAAAA1kK0QxY4REcGxHeMkgumyF7iwlsRFtw9MlbSSoQY7uAVsACAAAAAAUNlpMJZs1p4HfsD4Q4WZ4TBEi6Oc2fX34rzyynqWCdwAAzI2AH0AAAAFZAAgAAAAAP1TejmWg1CEuNSMt6NUgeQ5lT+oBoeyF7d2l5xQrbXWBXMAIAAAAABPX0kj6obggdJShmqtVfueKHplH4ZrXusiwrRDHMOKeQVsACAAAAAAIYOsNwC3DA7fLcOzqdr0bOFdHCfmK8tLwPoaE9uKOosAAzI3AH0AAAAFZAAgAAAAAMrKn+QPa/NxYezNhlOX9nyEkN1kE/gW7EuZkVqYl0b8BXMAIAAAAABUoZMSPUywRGfX2EEencJEKH5x/P9ySUVrhStAwgR/LgVsACAAAAAAMgZFH6lQIIDrgHnFeslv3ld20ynwQjQJt3cAp4GgrFkAAzI4AH0AAAAFZAAgAAAAAMmD1+a+oVbiUZd1HuZqdgtdVsVKwuWAn3/M1B6QGBM3BXMAIAAAAACLyytOYuZ9WEsIrrtJbXUx4QgipbaAbmlJvSZVkGi0CAVsACAAAAAA4v1lSp5H9BB+HYJ4bH43tC8aeuPZMf78Ng1JOhJh190AAzI5AH0AAAAFZAAgAAAAAOVKV7IuFwmYP1qVv8h0NvJmfPICu8yQhzjG7oJdTLDoBXMAIAAAAABL70XLfQLKRsw1deJ2MUvxSWKxpF/Ez73jqtbLvqbuogVsACAAAAAAvfgzIorXxE91dDt4nQxYfntTsx0M8Gzdsao5naQqcRUAAzMwAH0AAAAFZAAgAAAAAKS/1RSAQma+xV9rz04IcdzmavtrBDjOKPM+Z2NEyYfPBXMAIAAAAAAOJDWGORDgfRv8+w5nunh41wXb2hCA0MRzwnLnQtIqPgVsACAAAAAAf42C1+T7xdHEFF83+c2mF5S8PuuL22ogXXELnRAZ4boAAzMxAH0AAAAFZAAgAAAAAFeq8o82uNY1X8cH6OhdTzHNBUnCChsEDs5tm0kPBz3qBXMAIAAAAABaxMBbsaeEj/EDtr8nZfrhhhirBRPJwVamDo5WwbgvTQVsACAAAAAAMbH453A+BYAaDOTo5kdhV1VdND1avNwvshEG/4MIJjQAAzMyAH0AAAAFZAAgAAAAAI8IKIfDrohHh2cjspJHCovqroSr5N3QyVtNzFvT5+FzBXMAIAAAAABXHXteKG0DoOMmECKp6ro1MZNQvXGzqTDdZ0DUc8QfFAVsACAAAAAA/w5s++XYmO+9TWTbtGc3n3ndV4T9JUribIbF4jmDLSMAAzMzAH0AAAAFZAAgAAAAAJkHvm15kIu1OtAiaByj5ieWqzxiu/epK6c/9+KYIrB0BXMAIAAAAACzg5TcyANk0nes/wCJudd1BwlkWWF6zw3nGclq5v3SJQVsACAAAAAAvruXHTT3irPJLyWpI1j/Xwf2FeIE/IV+6Z49pqRzISoAAzM0AH0AAAAFZAAgAAAAAAYSOvEWWuSg1Aym7EssNLR+xsY7e9BcwsX4JKlnSHJcBXMAIAAAAABT48eY3PXVDOjw7JpNjOe1j2JyI3LjDnQoqZ8Je5B2KgVsACAAAAAAU2815RR57TQ9uDg0XjWjBkAKvf8yssxDMzrM4+FqP6AAAzM1AH0AAAAFZAAgAAAAAGQxC9L1e9DfO5XZvX1yvc3hTLtQEdKO9FPMkyg0Y9ZABXMAIAAAAADtmcMNJwdWLxQEArMGZQyzpnu+Z5yMmPAkvgq4eAKwNQVsACAAAAAAJ88zt4Y/Hoqh+zrf6KCOiUwHbOzCxSfp6k/qsZaYGEgAAzM2AH0AAAAFZAAgAAAAADLHK2LNCNRO0pv8n4fAsxwtUqCNnVK8rRgNiQfXpHSdBXMAIAAAAACf16EBIHRKD3SzjRW+LMOl+47QXA3CJhMzlcqyFRW22AVsACAAAAAAMGz4fAOa0EoVv90fUffwLjBrQhHATf+NdlgCR65vujAAAzM3AH0AAAAFZAAgAAAAAHiZJiXKNF8bbukQGsdYkEi95I+FSBHy1I5/hK2uEZruBXMAIAAAAADE+lZBa8HDUJPN+bF6xI9x4N7GF9pj3vBR7y0BcfFhBAVsACAAAAAAGIEN6sfqq30nyxW4dxDgXr/jz5HmvA9T1jx/pKCn4zgAAzM4AH0AAAAFZAAgAAAAAI1oa2OIw5TvhT14tYCGmhanUoYcCZtNbrVbeoMldHNZBXMAIAAAAAAx2nS0Ipblf2XOgBiUOuJFBupBhe7nb6QPLZlA4aMPCgVsACAAAAAA9xu828hugIgo0E3de9dZD+gTpVUGlwtDba+tw/WcbUoAAzM5AH0AAAAFZAAgAAAAABgTWS3Yap7Q59hii/uPPimHWXsr+DUmsqfwt/X73qsOBXMAIAAAAACKK05liW5KrmEAvtpCB1WUltruzUylDDpjea//UlWoOAVsACAAAAAAcgN4P/wakJ5aJK5c1bvJBqpVGND221dli2YicPFfuAYAAzQwAH0AAAAFZAAgAAAAABOAnBPXDp6i9TISQXvcNKwGDLepZTu3cKrB4vKnSCjBBXMAIAAAAADjjzZO7UowAAvpwyG8BNOVqLCccMFk3aDK4unUeft5ywVsACAAAAAA4zkCd4k9gvfXoD1C7vwTjNcdVJwEARh8h/cxZ4PNMfgAAzQxAH0AAAAFZAAgAAAAAHN8hyvT1lYrAsdiV5GBdd5jhtrAYE/KnSjw2Ka9hjz9BXMAIAAAAAD794JK7EeXBs+D7yOVK7nWF8SbZ/7U8gZ7nnT9JFNwTAVsACAAAAAAg8Wt1HO3NhByq2ggux2a4Lo6Gryr24rEFIqh2acrwWMAAzQyAH0AAAAFZAAgAAAAAO93bPrq8bsnp1AtNd9ETnXIz0lH/2HYN/vuw9wA3fyFBXMAIAAAAABHlls5fbaF2oAGqptC481XQ4eYxInTC29aElfmVZgDUgVsACAAAAAANoQXEWpXJpgrSNK/cKi/m7oYhuSRlp1IZBF0bqTEATcAAzQzAH0AAAAFZAAgAAAAAL1YsAZm1SA0ztU6ySIrQgCCA74V6rr0/4iIygCcaJL6BXMAIAAAAADTXWTHWovGmUR1Zg9l/Aqq9H5mOCJQQrb/Dfae7e3wKAVsACAAAAAA5dunyJK6/SVfDD0t9QlNBcFqoZnf9legRjHaLSKAoQMAAzQ0AH0AAAAFZAAgAAAAAEoFAeHk0RZ9kD+cJRD3j7PcE5gzWKnyBrF1I/MDNp5mBXMAIAAAAACgHtc2hMBRSZjKw8RAdDHK+Pi1HeyjiBuAslGVNcW5tAVsACAAAAAAXzBLfq+GxRtX4Wa9fazA49DBLG6AjZm2XODStJKH8D0AAzQ1AH0AAAAFZAAgAAAAAAW+7DmSN/LX+/0uBVJDHIc2dhxAGz4+ehyyz8fAnNGoBXMAIAAAAAA6Ilw42EvvfLJ3Eq8Afd+FjPoPcQutZO6ltmCLEr8kxQVsACAAAAAAbbZalyo07BbFjPFlYmbmv0z023eT9eLkHqeVUnfUAUAAAzQ2AH0AAAAFZAAgAAAAANBdV7M7kuYO3EMoQItAbXv4t2cIhfaT9V6+s4cg9djlBXMAIAAAAABvz4MIvZWxxrcJCL5qxLfFhXiUYB1OLHdKEjco94SgDgVsACAAAAAAK2GVGvyPIKolF/ECcmfmkVcf1/IZNcaTv96N92yGrkEAAzQ3AH0AAAAFZAAgAAAAAMoAoiAn1kc79j5oPZtlMWHMhhgwNhLUnvqkqIFvcH1NBXMAIAAAAADcJTW7WiCyW0Z9YDUYwppXhLj4Ac1povpJvcAq+i48MQVsACAAAAAAIGxGDzoeB3PTmudl4+j6piQB++e33EEzuzAiXcqGxvUAAzQ4AH0AAAAFZAAgAAAAACI3j5QP7dWHpcT6WO/OhsWwRJNASBYqIBDNzW8IorEyBXMAIAAAAABxUpBSjXwCKDdGP9hYU+RvyR+96kChfvyyRC4jZmztqAVsACAAAAAAvBCHguWswb4X0xdcAryCvZgQuthXzt7597bJ5VxAMdgAAzQ5AH0AAAAFZAAgAAAAAKsbycEuQSeNrF8Qnxqw3x3og8JmQabwGqnDbqzFRVrrBXMAIAAAAACno/3ef2JZJS93SVVzmOZSN+jjJHT8s0XYq2M46d2sLAVsACAAAAAAAt5zLJG+/j4K8rnkFtAn8IvdUVNefe6utJ3rdzgwudIAAzUwAH0AAAAFZAAgAAAAAPXIcoO8TiULqlxzb74NFg+I8kWX5uXIDUPnh2DobIoMBXMAIAAAAADR6/drkdTpnr9g1XNvKDwtBRBdKn7c2c4ZNUVK5CThdQVsACAAAAAAJqOA1c6KVog3F4Hb/GfDb3jCxXDRTqpXWSbMH4ePIJsAAzUxAH0AAAAFZAAgAAAAAEa03ZOJmfHT6/nVadvIw71jVxEuIloyvxXraYEW7u7pBXMAIAAAAADzRlBJK75FLiKjz3djqcgjCLo/e3yntI3MnPS48OORhgVsACAAAAAAnQhx4Rnyj081XrLRLD5NLpWmRWCsd0M9Hl7Jl19R0h8AAzUyAH0AAAAFZAAgAAAAAKx8NLSZUU04pSSGmHa5fh2oLHsEN5mmNMNHL95/tuC9BXMAIAAAAAA59hcXVaN3MNdHoo11OcH1aPRzHCwpVjO9mGfMz4xh3QVsACAAAAAAYIPdjV2XbPj7dBeHPwnwhVU7zMuJ+xtMUW5mIOYtmdAAAzUzAH0AAAAFZAAgAAAAAHNKAUxUqBFNS9Ea9NgCZoXMWgwhP4x0/OvoaPRWMquXBXMAIAAAAABUZ551mnP4ZjX+PXU9ttomzuOpo427MVynpkyq+nsYCQVsACAAAAAALnVK5p2tTTeZEh1zYt4iqKIQT9Z0si//Hy1L85oF+5IAAzU0AH0AAAAFZAAgAAAAALfGXDlyDVcGaqtyHkLT0qpuRhJQLgCxtznazhFtuyn/BXMAIAAAAABipxlXDq14C62pXhwAeen5+syA+/C6bN4rtZYcO4zKwAVsACAAAAAAXUf0pzUq0NhLYagWDap4uEiwq5rLpcx29rWbt1NYMsMAAzU1AH0AAAAFZAAgAAAAANoEr8sheJjg4UCfBkuUzarU9NFoy1xwbXjs5ifVDeA9BXMAIAAAAABPoyTf6M+xeZVGES4aNzVlq7LgjqZXJ/QunjYVusGUEAVsACAAAAAA1hA2gMeZZPUNytk9K+lB1RCqWRudRr7GtadJlExJf8oAAzU2AH0AAAAFZAAgAAAAAKvDiK+xjlBe1uQ3SZTNQl2lClIIvpP/5CHwY6Kb3WlgBXMAIAAAAAANnxImq5MFbWaRBHdJp+yD09bVlcFtiFDYsy1eDZj+iQVsACAAAAAAWtsyO+FxMPSIezwsV1TJD8ZrXAdRnQM6DJ+f+1V3qEkAAzU3AH0AAAAFZAAgAAAAAF49IlFH9RmSUSvUQpEPUedEksrQUcjsOv44nMkwXhjzBXMAIAAAAADJtWGbk0bZzmk20obz+mNsp86UCu/nLLlbg7ppxYn7PgVsACAAAAAA3k0Tj/XgPQtcYijH8cIlQoe/VXf15q1nrZNmg7yWYEgAAzU4AH0AAAAFZAAgAAAAAOuSJyuvz50lp3BzXlFKnq62QkN2quNU1Gq1IDsnFoJCBXMAIAAAAAAqavH1d93XV3IzshWlMnzznucadBF0ND092/2ApI1AcAVsACAAAAAAzUrK4kpoKCmcpdZlZNI13fddjdoAseVe67jaX1LobIIAAzU5AH0AAAAFZAAgAAAAALtgC4Whb4ZdkCiI30zY6fwlsxSa7lEaOAU3SfUXr02XBXMAIAAAAACgdZ6U1ZVgUaZZwbIaCdlANpCw6TZV0bwg3DS1NC/mnAVsACAAAAAAzI49hdpp0PbO7S2KexISxC16sE73EUAEyuqUFAC/J48AAzYwAH0AAAAFZAAgAAAAAF6PfplcGp6vek1ThwenMHVkbZgrc/dHgdsgx1VdPqZ5BXMAIAAAAACha3qhWkqmuwJSEXPozDO8y1ZdRLyzt9Crt2vjGnT7AAVsACAAAAAA7nvcU59+LwxGupSF21jAeAE0x7JE94tjRkJfgM1yKU8AAzYxAH0AAAAFZAAgAAAAAKoLEhLvLjKc7lhOJfx+VrGJCx9tXlOSa9bxQzGR6rfbBXMAIAAAAAAIDK5wNnjRMBzET7x/KAMExL/zi1IumJM92XTgXfoPoAVsACAAAAAAFkUYWFwNr815dEdFqp+TiIozDcq5IBNVkyMoDjharDQAAzYyAH0AAAAFZAAgAAAAADoQv6lutRmh5scQFvIW6K5JBquLxszuygM1tzBiGknIBXMAIAAAAADAD+JjW7FoBQ76/rsECmmcL76bmyfXpUU/awqIsZdO+wVsACAAAAAAPFHdLw3jssmEXsgtvl/RBNaUCRA1kgSwsofG364VOvQAAzYzAH0AAAAFZAAgAAAAAJNHUGAgn56KekghO19d11nai3lAh0JAlWfeP+6w4lJBBXMAIAAAAAD9XGJlvz59msJvA6St9fKW9CG4JoHV61rlWWnkdBRLzwVsACAAAAAAxwP/X/InJJHmrjznvahIMgj6pQR30B62UtHCthSjrP0AAzY0AH0AAAAFZAAgAAAAAHgYoMGjEE6fAlAhICv0+doHcVX8CmMVxyq7+jlyGrvmBXMAIAAAAAC/5MQZgTHuIr/O5Z3mXPvqrom5JTQ8IeSpQGhO9sB+8gVsACAAAAAAuPSXVmJUAUpTQg/A9Bu1hYczZF58KEhVofakygbsvJQAAzY1AH0AAAAFZAAgAAAAANpIljbxHOM7pydY877gpRQvYY2TGK7igqgGsavqGPBABXMAIAAAAAAqHyEu9gpurPOulApPnr0x9wrygY/7mXe9rAC+tPK80wVsACAAAAAA7gkPzNsS3gCxdFBWbSW9tkBjoR5ib+saDvpGSB3A3ogAAzY2AH0AAAAFZAAgAAAAAGR+gEaZTeGNgG9BuM1bX2R9ed4FCxBA9F9QvdQDAjZwBXMAIAAAAABSkrYFQ6pf8MZ1flgmeIRkxaSh/Eep4Btdx4QYnGGnwAVsACAAAAAApRovMiV00hm/pEcT4XBsyPNw0eo8RLAX/fuabjdU+uwAAzY3AH0AAAAFZAAgAAAAAFNprhQ3ZwIcYbuzLolAT5n/vc14P9kUUQComDu6eFyKBXMAIAAAAAAcx9z9pk32YbPV/sfPZl9ALIEVsqoLXgqWLVK/tP+heAVsACAAAAAA/qxvuvJbAHwwhfrPVpmCFzNvg2cU/NXaWgqgYUZpgXwAAzY4AH0AAAAFZAAgAAAAADgyPqQdqQrgfmJjRFAILTHzXbdw5kpKyfeoEcy6YYG/BXMAIAAAAAAE+3XsBQ8VAxAkN81au+f3FDeCD/s7KoZD+fnM1MJSSAVsACAAAAAAhRnjrXecwV0yeCWKJ5J/x12Xx4qVJahsCEVHB/1U2rcAAzY5AH0AAAAFZAAgAAAAAI0CT7JNngTCTUSei1Arw7eHWCD0jumv2rb7imjWIlWABXMAIAAAAABSP8t6ya0SyCphXMwnru6ZUDXWElN0NfBvEOhDvW9bJQVsACAAAAAAGWeGmBNDRaMtvm7Rv+8TJ2sJ4WNXKcp3tqpv5Se9Ut4AAzcwAH0AAAAFZAAgAAAAAD/FIrGYFDjyYmVb7oTMVwweWP7A6F9LnyIuNO4MjBnXBXMAIAAAAACIZgJCQRZu7NhuNMyOqCn1tf+DfU1qm10TPCfj5JYV3wVsACAAAAAA5hmY4ptuNxULGf87SUFXQWGAONsL9U29duh8xqsHtxoAAzcxAH0AAAAFZAAgAAAAAHIkVuNDkSS1cHIThKc/O0r2/ubaABTOi8Q1r/dvBAsEBXMAIAAAAADdHYqchEiJLM340c3Q4vJABmmth3+MKzwLYlsG6GS7sQVsACAAAAAADa+KP/pdTiG22l+ZWd30P1iHjnBF4zSNRdFm0oEK82kAAzcyAH0AAAAFZAAgAAAAAJmoDILNhC6kn3masElfnjIjP1VjsjRavGk1gSUIjh1NBXMAIAAAAAD97Ilvp3XF8T6MmVVcxMPcdL80RgQ09UoC6PnoOvZ1IQVsACAAAAAA2RK3Xng6v8kpvfVW9tkVXjpE+BSnx9/+Fw85Evs+kUEAAzczAH0AAAAFZAAgAAAAAI5bm3YO0Xgf0VT+qjVTTfvckecM3Cwqj7DTKZXf8/NXBXMAIAAAAAD/m+h8fBhWaHm6Ykuz0WX1xL4Eme3ErLObyEVJf8NCywVsACAAAAAAfb1VZZCqs2ivYbRzX4p5CtaCkKW+g20Pr57FWXzEZi8AAzc0AH0AAAAFZAAgAAAAANqo4+p6qdtCzcB4BX1wQ6llU7eFBnuu4MtZwp4B6mDlBXMAIAAAAAAGiz+VaukMZ+6IH4jtn4KWWdKK4/W+O+gRioQDrfzpMgVsACAAAAAAG4YYkTp80EKo59mlHExDodRQFR7njhR5dmISwUJ6ukAAAzc1AH0AAAAFZAAgAAAAAPrFXmHP2Y4YAm7b/aqsdn/DPoDkv7B8egWkfe23XsM1BXMAIAAAAAAGhwpKAr7skeqHm3oseSbO7qKNhmYsuUrECBxJ5k+D2AVsACAAAAAAAqPQi9luYAu3GrFCEsVjd9z2zIDcp6SPTR2w6KQEr+IAAzc2AH0AAAAFZAAgAAAAABzjYxwAjXxXc0Uxv18rH8I3my0Aguow0kTwKyxbrm+cBXMAIAAAAADVbqJVr6IdokuhXkEtXF0C2gINLiAjMVN20lE20Vmp2QVsACAAAAAAD7K1Fx4gFaaizkIUrf+EGXQeG7QX1jadhGc6Ji471H8AAzc3AH0AAAAFZAAgAAAAAFMm2feF2fFCm/UC6AfIyepX/xJDSmnnolQIBnHcPmb5BXMAIAAAAABLI11kFrQoaNVZFmq/38aRNImPOjdJh0Lo6irI8M/AaAVsACAAAAAAOWul0oVqJ9CejD2RqphhTC98DJeRQy5EwbNerU2+4l8AAzc4AH0AAAAFZAAgAAAAAJvXB3KyNiNtQko4SSzo/9b2qmM2zU9CQTTDfLSBWMgRBXMAIAAAAAAvjuVP7KsLRDeqVqRziTKpBrjVyqKiIbO9Gw8Wl2wFTAVsACAAAAAADlE+oc1ins+paNcaOZJhBlKlObDJ4VQORWjFYocM4LgAAzc5AH0AAAAFZAAgAAAAAPGdcxDiid8z8XYnfdDivNMYVPgBKdGOUw6UStU+48CdBXMAIAAAAAARj6g1Ap0eEfuCZ4X2TsEw+Djrhto3fA5nLwPaY0vCTgVsACAAAAAAoHqiwGOUkBu8SX5U1yHho+UIFdSN2MdQN5s6bQ0EsJYAAzgwAH0AAAAFZAAgAAAAAP5rGPrYGt3aKob5f/ldP0qrW7bmWvqnKY4QwdDWz400BXMAIAAAAADTQkW2ymaaf/bhteOOGmSrIR97bAnJx+yN3yMj1bTeewVsACAAAAAADyQnHGH2gF4w4L8axUsSTf6Ubk7L5/eoFOJk12MtZAoAAzgxAH0AAAAFZAAgAAAAAAlz6wJze5UkIxKpJOZFGCOf3v2KByWyI6NB6JM9wNcBBXMAIAAAAABUC7P/neUIHHoZtq0jFVBHY75tSFYr1Y5S16YN5XxC1QVsACAAAAAAgvxRbXDisNnLY3pfsjDdnFLtkvYUC4lhA68eBXc7KAwAAzgyAH0AAAAFZAAgAAAAAFJ8AtHcjia/9Y5pLEc3qVgH5xKiXw12G9Kn2A1EY8McBXMAIAAAAAAxe7Bdw7eUSBk/oAawa7uicTEDgXLymRNhBy1LAxhDvwVsACAAAAAAxKPaIBKVx3jTA+R/el7P7AZ7efrmTGjJs3Hj/YdMddwAAzgzAH0AAAAFZAAgAAAAAO8uwQUaKFb6vqR3Sv3Wn4QAonC2exOC9lGG1juqP5DtBXMAIAAAAABZf1KyJgQg8/Rf5c02DgDK2aQu0rNCOvaL60ohDHyY+gVsACAAAAAAqyEjfKC8lYoIfoXYHUqHZPoaA6EK5BAZy5dxXZmay4kAAzg0AH0AAAAFZAAgAAAAAE8YtqyRsGCeiR6hhiyisR/hccmK4nZqIMzO4lUBmEFzBXMAIAAAAAC1UYOSKqAeG1UJiKjWFVskRhuFKpj9Ezy+lICZvFlN5AVsACAAAAAA6Ct9nNMKyRazn1OKnRKagm746CGu+jyhbL1qJnZxGi0AAzg1AH0AAAAFZAAgAAAAAPhCrMausDx1QUIEqp9rUdRKyM6a9AAx7jQ3ILIu8wNIBXMAIAAAAACmH8lotGCiF2q9VQxhsS+7LAZv79VUAsOUALaGxE/EpAVsACAAAAAAnc1xCKfdvbUEc8F7XZqlNn1C+hZTtC0I9I3LL06iaNkAAzg2AH0AAAAFZAAgAAAAAOBi/GAYFcstMSJPgp3VkMiuuUUCrZytvqYaU8dwm8v2BXMAIAAAAACEZSZVyD3pKzGlbdwlYmWQhHHTV5SnNLknl2Gw8IaUTQVsACAAAAAAfsLZsEDcWSuNsIo/TD1ReyQW75HPMgmuKZuWFOLKRLoAAzg3AH0AAAAFZAAgAAAAAIQuup+YGfH3mflzWopN8J1X8o8a0d9CSGIvrA5HOzraBXMAIAAAAADYvNLURXsC2ITMqK14LABQBI+hZZ5wNf24JMcKLW+84AVsACAAAAAACzfjbTBH7IwDU91OqLAz94RFkoqBOkzKAqQb55gT4/MAAzg4AH0AAAAFZAAgAAAAAKsh0ADyOnVocFrOrf6MpTrNvAj8iaiE923DPryu124gBXMAIAAAAADg24a8NVE1GyScc6tmnTbmu5ulzO+896fE92lN08MeswVsACAAAAAAaPxcOIxnU7But88/yadOuDJDMcCywwrRitaxMODT4msAAzg5AH0AAAAFZAAgAAAAAKkVC2Y6HtRmv72tDnPUSjJBvse7SxLqnr09/Uuj9sVVBXMAIAAAAABYNFUkH7ylPMN+Bc3HWX1e0flGYNbtJNCY9SltJCW/UAVsACAAAAAAZYK/f9H4OeihmpiFMH7Wm7uLvs2s92zNA8wyrNZTsuMAAzkwAH0AAAAFZAAgAAAAADDggcwcb/Yn1Kk39sOHsv7BO/MfP3m/AJzjGH506Wf9BXMAIAAAAAAYZIsdjICS0+BDyRUPnrSAZfPrwtuMaEDEn0/ijLNQmAVsACAAAAAAGPnYVvo2ulO9z4LGd/69NAklfIcZqZvFX2KK0s+FcTUAAzkxAH0AAAAFZAAgAAAAAEWY7dEUOJBgjOoWVht1wLehsWAzB3rSOBtLgTuM2HC8BXMAIAAAAAAAoswiHRROurjwUW8u8D5EUT+67yvrgpB/j6PzBDAfVwVsACAAAAAA6NhRTYFL/Sz4tao7vpPjLNgAJ0FX6P/IyMW65qT6YsMAAzkyAH0AAAAFZAAgAAAAAPZaapeAUUFPA7JTCMOWHJa9lnPFh0/gXfAPjA1ezm4ZBXMAIAAAAACmJvLY2nivw7/b3DOKH/X7bBXjJwoowqb1GtEFO3OYgAVsACAAAAAA/JcUoyKacCB1NfmH8vYqC1f7rd13KShrQqV2r9QBP44AAzkzAH0AAAAFZAAgAAAAAK00u6jadxCZAiA+fTsPVDsnW5p5LCr4+kZZZOTDuZlfBXMAIAAAAAAote4zTEYMDgaaQbAdN8Dzv93ljPLdGjJzvnRn3KXgtQVsACAAAAAAxXd9Mh6R3mnJy8m7UfqMKi6oD5DlZpkaOz6bEjMOdiwAAzk0AH0AAAAFZAAgAAAAAFbgabdyymiEVYYwtJSWa7lfl/oYuj/SukzJeDOR6wPVBXMAIAAAAADAFGFjS1vPbN6mQEhkDYTD6V2V23Ys9gUEUMGNvMPkaAVsACAAAAAAL/D5Sze/ZoEanZLK0IeEkhgVkxEjMWVCfmJaD3a8uNIAAzk1AH0AAAAFZAAgAAAAABNMR6UBv2E627CqLtQ/eDYx7OEwQ7JrR4mSHFa1N8tLBXMAIAAAAAAxH4gucI4UmNVB7625C6hFSVCuIpJO3lusJlPuL8H5EQVsACAAAAAAVLHNg0OUVqZ7WGOP53BkTap9FOw9dr1P4J8HxqFqU04AAzk2AH0AAAAFZAAgAAAAAG8cd6WBneNunlqrQ2EmNf35W7OGObGq9WL4ePX+LUDmBXMAIAAAAAAjJ2+sX87NSis9hBsgb1QprVRnO7Bf+GObCGoUqyPE4wVsACAAAAAAs9c9SM49/pWmyUQKslpt3RTMBNSRppfNO0JBvUqHPg0AAzk3AH0AAAAFZAAgAAAAAFWOUGkUpy8yf6gB3dio/aOfRKh7XuhvsUj48iESFJrGBXMAIAAAAAAY7sCDMcrUXvNuL6dO0m11WyijzXZvPIcOKob6IpC4PQVsACAAAAAAJOP+EHz6awDb1qK2bZQ3kTV7wsj5Daj/IGAWh4g7omAAAzk4AH0AAAAFZAAgAAAAAGUrIdKxOihwNmo6B+aG+Ag1qa0+iqdksHOjQj+Oy9bZBXMAIAAAAABwa5dbI2KmzBDNBTQBEkjZv4sPaeRkRNejcjdVymRFKQVsACAAAAAA4ml/nm0gJNTcJ4vuD+T2Qfq2fQZlibJp/j6MOGDrbHMAAzk5AH0AAAAFZAAgAAAAAOx89xV/hRk64/CkM9N2EMK6aldII0c8smdcsZ46NbP8BXMAIAAAAADBF6tfQ+7q9kTuLyuyrSnDgmrdmrXkdhl980i1KHuGHgVsACAAAAAACUqiFqHZdGbwAA+hN0YUE5zFg+H+dabIB4dj5/75W/YAAzEwMAB9AAAABWQAIAAAAADJDdC9aEFl4Y8J/awHbnXGHjfP+VXQilPHJg7ewaJI7AVzACAAAAAAE+tqRl6EcBMXvbr4GDiNIYObTsYpa1n6BJk9EjIJVicFbAAgAAAAAJVc+HYYqa0m1Hq6OiRX8c0iRnJYOt6AJAJoG0sG3GMSAAMxMDEAfQAAAAVkACAAAAAA3F9rjEKhpoHuTULVGgfUsGGwJs3bISrXkFP1v6KoQLgFcwAgAAAAAIBf0tXw96Z/Ds0XSIHX/zk3MzUR/7WZR/J6FpxRWChtBWwAIAAAAABWrjGlvKYuTS2s8L9rYy8Hf0juFGJfwQmxVIjkTmFIGQADMTAyAH0AAAAFZAAgAAAAAOYIYoWkX7dGuyKfi3XssUlc7u/gWzqrR9KMkikKVdmSBXMAIAAAAABVF2OYjRTGi9Tw8XCAwZWLpX35Yl271TlNWp6N/nROhAVsACAAAAAA0nWwYzXQ1+EkDvnGq+SMlq20z+j32Su+i/A95SggPb4AAzEwMwB9AAAABWQAIAAAAACMtPm12YtdEAvqu6Eji1yuRXnu1RJP6h0l7pH3lSH4MwVzACAAAAAAENyCFfyUAh1veQBGx+cxiB7Sasrj41jzCGflZkB5cRMFbAAgAAAAAKdI2LMqISr/T5vuJPg6ZRBm5fVi2aQCc4ra3A4+AjbDAAMxMDQAfQAAAAVkACAAAAAAvlI4lDcs6GB1cnm/Tzo014CXWqidCdyE5t2lknWQd4QFcwAgAAAAAD60SpNc4O2KT7J0llKdSpcX1/Xxs97N715a1HsTFkmBBWwAIAAAAABuuRkJWAH1CynggBt1/5sPh9PoGiqTlS24D/OE2uHXLQADMTA1AH0AAAAFZAAgAAAAAKl8zcHJRDjSjJeV/WvMxulW1zrTFtaeBy/aKKhadc6UBXMAIAAAAADBdWQl5SBIvtZZLIHszePwkO14W1mQ0izUk2Ov21cPNAVsACAAAAAAHErCYycpqiIcCZHdmPL1hi+ovLQk4TAvENpfLdTRamQAAzEwNgB9AAAABWQAIAAAAABb6LXDWqCp1beQgQjj8I3sRTtFhlrmiBi+h/+ikmrvugVzACAAAAAA9stpgTecT7uTyaGNs3K9Bp0A7R0QaIAOfscyMXHBPX8FbAAgAAAAAHUt+McyXrJ1H8SwnHNVO181Ki8vDAM1f7XI26mg95ZDAAMxMDcAfQAAAAVkACAAAAAA97NTT+81PhDhgptNtp4epzA0tP4iNb9j1AWkiiiKGM8FcwAgAAAAAKPbHg7ise16vxmdPCzksA/2Mn/qST0L9Xe8vnQugVkcBWwAIAAAAABB0EMXfvju4JU/mUH/OvxWbPEl9NJkcEp4iCbkXI41fAADMTA4AH0AAAAFZAAgAAAAAMqpayM2XotEFmm0gwQd9rIzApy0X+7HfOhNk6VU7F5lBXMAIAAAAACJR9+q5T9qFHXFNgGbZnPubG8rkO6cwWhzITQTmd6VgwVsACAAAAAAOZLQ6o7e4mVfDzbpQioa4d3RoTvqwgnbmc5Qh2wsZuoAAzEwOQB9AAAABWQAIAAAAADQnslvt6Hm2kJPmqsTVYQHE/wWeZ4bE1XSkt7TKy0r1gVzACAAAAAA8URTA4ZMrhHPvlp53TH6FDCzS+0+61qHm5XK6UiOrKEFbAAgAAAAAHQbgTCdZcbdA0avaTmZXUKnIS7Nwf1tNrcXDCw+PdBRAAMxMTAAfQAAAAVkACAAAAAAhujlgFPFczsdCGXtQ/002Ck8YWQHHzvWvUHrkbjv4rwFcwAgAAAAALbV0lLGcSGfE7mDM3n/fgEvi+ifjl7WZ5b3aqjDNvx9BWwAIAAAAACbceTZy8E3QA1pHmPN5kTlOx3EO8kJM5PUjTVftw1VpgADMTExAH0AAAAFZAAgAAAAABm/6pF96j26Jm7z5KkY1y33zcAEXLx2n0DwC03bs/ixBXMAIAAAAAD01OMvTZI/mqMgxIhA5nLs068mW+GKl3OW3ilf2D8+LgVsACAAAAAAaLvJDrqBESTNZSdcXsd+8GXPl8ZkUsGpeYuyYVv/kygAAzExMgB9AAAABWQAIAAAAACfw9/te4GkHZAapC9sDMHHHZgmlTrccyJDPFciOMSOcwVzACAAAAAAIIC1ZpHObvmMwUfqDRPl4C1aeuHwujM1G/yJbvybMNAFbAAgAAAAAAs9x1SnVpMfNv5Bm1aXGwHmbbI9keWa9HRD35XuCBK5AAMxMTMAfQAAAAVkACAAAAAAkxHJRbnShpPOylLoDdNShfILeA1hChKFQY9qQyZ5VmsFcwAgAAAAAKidrY+rC3hTY+YWu2a7fuMH2RD/XaiTIBW1hrxNCQOJBWwAIAAAAACW0kkqMIzIFMn7g+R0MI8l15fr3k/w/mHtY5n6SYTEwAADMTE0AH0AAAAFZAAgAAAAAByuYl8dBvfaZ0LO/81JW4hYypeNmvLMaxsIdvqMPrWoBXMAIAAAAABNddwobOUJzm9HOUD8BMZJqkNCUCqstHZkC76FIdNg9AVsACAAAAAAQQOkIQtkyNavqCnhQbNg3HfqrJdsAGaoxSJePJl1qXsAAzExNQB9AAAABWQAIAAAAABxMy7X5hf7AXGDz3Y/POu1ZpkMlNcSvSP92NOO/Gs7wAVzACAAAAAAHJshWo2T5wU2zvqCyJzcJQKQaHFHpCpMc9oWBXkpUPoFbAAgAAAAAGeiJKzlUXAvL0gOlW+Hz1mSa2HsV4RGmyLmCHlzbAkoAAMxMTYAfQAAAAVkACAAAAAAlqbslixl7Zw3bRlibZbe/WmKw23k8uKeIzPKYEtbIy0FcwAgAAAAAHEKwpUxkxOfef5HYvulXPmdbzTivwdwrSYIHDeNRcpcBWwAIAAAAADuPckac21Hrg/h0kt5ShJwVEZ9rx6SOHd2+HDjqxEWTQADMTE3AH0AAAAFZAAgAAAAAMXrXx0saZ+5gORmwM2FLuZG6iuO2YS+1IGPoAtDKoKBBXMAIAAAAADIQsxCr8CfFKaBcx8kIeSywnGh7JHjKRJ9vJd9x79y7wVsACAAAAAAcvBV+SykDYhmRFyVYwFYB9oBKBSHr55Jdz2cXeowsUQAAzExOAB9AAAABWQAIAAAAAAm83FA9yDUpwkbKTihe7m53u+DivS9BU2b4vQMtCVQ2AVzACAAAAAAz3m1UB/AbZPa4QSKFDnUgHaT78+6iGOFAtouiBorEgEFbAAgAAAAAIgbpyYtJj5513Z5XYqviH/HXG/5+mqR52iBbfqMmDtZAAMxMTkAfQAAAAVkACAAAAAAJRzYK0PUwr9RPG2/7yID0WgcTJPB2Xjccp5LAPDYunkFcwAgAAAAAIIh24h3DrltAzNFhF+MEmPrZtzr1PhCofhChZqfCW+jBWwAIAAAAAAzRNXtL5o9VXMk5D5ylI0odPDJDSZZry1wfN+TedH70gADMTIwAH0AAAAFZAAgAAAAAHSaHWs/dnmI9sc7nB50VB2Bzs0kHapMHCQdyVEYY30TBXMAIAAAAACkV22lhEjWv/9/DubfHBAcwJggKI5mIbSK5L2nyqloqQVsACAAAAAAS19m7DccQxgryOsBJ3GsCs37yfQqNi1G+S6fCXpEhn4AAzEyMQB9AAAABWQAIAAAAAAC/I4TQRtCl12YZmdGz17X4GqSQgfwCPgRBwdHmdwu+QVzACAAAAAAx8f3z2ut/RAZhleari4vCEE+tNIn4ikjoUwzitfQ588FbAAgAAAAAJci0w1ZB8W2spJQ+kMpod6HSCtSR2jrabOH+B0fj3A4AAMxMjIAfQAAAAVkACAAAAAADGB5yU2XT0fse/MPWgvBvZikVxrl5pf3S5K1hceKWooFcwAgAAAAAIxTmlLHMjNaVDEfJbXvRez0SEPWFREBJCT6qTHsrljoBWwAIAAAAAAlswzAl81+0DteibwHD+CG5mZJrfHXa9NnEFRtXybzzwADMTIzAH0AAAAFZAAgAAAAABmO7QD9vxWMmFjIHz13lyOeV6vHT6mYCsWxF7hb/yOjBXMAIAAAAACT9lmgkiqzuWG24afuzYiCeK9gmJqacmxAruIukd0xEAVsACAAAAAAZa0/FI/GkZR7CtX18Xg9Tn9zfxkD0UoaSt+pIO5t1t4AAzEyNAB9AAAABWQAIAAAAAAfPUoy7QyZKhIIURso+mkP9qr1izbjETqF5s22GwjCjAVzACAAAAAAvLMsIDQ/go4VUxeh50UHmsvMvfx51cwyONnRD2odvC0FbAAgAAAAAKMb+1CodEalAFnDrEL1Ndt8ztamZ+9134m9Kp3GQgd+AAMxMjUAfQAAAAVkACAAAAAAE3ZqUar0Bq2zWbARE0bAv98jBlK9UJ73/xcwdMWWlSkFcwAgAAAAAK4M+MmC+9sFiFsumMyJZQKxWmmJiuG9H7IzKw083xxkBWwAIAAAAAAqkAONzhvMhkyL1D/6h7QQxEkdhC3p2WjXH+VGq5qCqQADMTI2AH0AAAAFZAAgAAAAAMo8FJiOq63cAmyk2O7eI7GcbQh/1j4RrMTqly3rexftBXMAIAAAAADjVmpd0WiRGTw/gAqEgGolt2EI7Csv14vKdmYoMD0aAgVsACAAAAAA07XQBzBUQMNw7F2/YxJjZNuPVpHTTgbLd1oGk77+bygAAzEyNwB9AAAABWQAIAAAAACu5IGaIx7A3Jvly/kzlCsSA4s3iJwuIl8jEdRH0k93NwVzACAAAAAA9NRUyxYE+t0Xyosyt6vIfMFW/vBoYg6sR+jBNs4JAxIFbAAgAAAAAAzyZ91dx+0oMlOVAjRGiMrPySikY/U9eMEB4WJb3uWtAAMxMjgAfQAAAAVkACAAAAAALkRy0GJInXYLA+cgjs6Myb0a+Gu9hgXhHvhLNoGWfckFcwAgAAAAANbALyt9zCSvwnLaWCd2/y2eoB7qkWTvv1Ldu8r40JPuBWwAIAAAAAD4Fl5bV5sz4isIE9bX+lmAp+aAKaZgVYVZeVfrItkCZAADMTI5AH0AAAAFZAAgAAAAAGoUK/DSWhT8LZhszSUqDbTrp8cSA7rdqmADKL+MILtTBXMAIAAAAABHnEE9bVa6lvhfhEMkkV2kzSSxH/sMW/FIJuw3CzWs6wVsACAAAAAAanavcBdqZxgRGKvEK95wTmeL1K1CeDSXZsXUAs81uOgAAzEzMAB9AAAABWQAIAAAAAC922ZDQE3h2fQKibGMZ9hV0WNlmrPYYSdtaSyYxsWYqgVzACAAAAAAagMovciKK6WVjIc2cCj8nK5O/gVOFFVeVAJpRp89tmQFbAAgAAAAAKcTFfPQzaFiAtSFhqbN02sCE1BKWJSrRfGN5L6oZwzkAAMxMzEAfQAAAAVkACAAAAAAtK+JqX3K/z2txjAU15DgX4y90DS2YLfIJFolCOkJJJwFcwAgAAAAAMnR5V7gfX7MNqqUdL5AkWlkhyFXaBRVNej+Rcn8lrQkBWwAIAAAAAA2cDNRXZuiC241TGRvdFyctJnrNcdbZOP9zHio81tkngADMTMyAH0AAAAFZAAgAAAAAAeGrIMK/bac6kPczxbvRYqKMkcpeI2FjdMpD91FDWIvBXMAIAAAAAAix62z1LeS8yvSXCl5gHSIomjyx76fF3S1lp9k900hygVsACAAAAAAiYwzf2m71aWFD5ajcXyW2JX2EzQOkBroTGMg29nLPYIAAzEzMwB9AAAABWQAIAAAAACphf298InM0Us4HT8o1W1MGw0D/02vd7Jh+U0h7qaFaQVzACAAAAAAFXtk7YpqsOJxsqGWSIL+YcBE96G3Zz9D31gPqDW94y8FbAAgAAAAAAOrS1KVA94rjB1jZ1pPocpCeBG+B14RzWoHqVDpp7JbAAMxMzQAfQAAAAVkACAAAAAATLDS2cuDVM3yDMuWNgk2iGKBTzPpfJMbvxVOSY39ZfcFcwAgAAAAAPT5wRi2cLHIUflXzm6EQB/m7xdThP80ir1VV/JBBqvxBWwAIAAAAAB9lEtZS0aXCFbCtSbhnis27S5IPcfWGygHW8AHn3QqzwADMTM1AH0AAAAFZAAgAAAAAJNjExiZVX7jfFGfYpQu16qxLN0YPqVU/5CQ/Y67YSinBXMAIAAAAABMpm2+6KrkRUlXzQoMPHrQmIO6dkQz66tYdfTeA3dKqQVsACAAAAAAFXobHiMLvNZuEPr8jtewCX2J93EZG3JNeyVg92fue6YAAzEzNgB9AAAABWQAIAAAAABlFkYtLCx901X6QVVMkSn6Z7k30UF4xHaA0OZJJ9bdyQVzACAAAAAATez+F9GHcGzTp7jjv4feboUNb8JCkIp4EqcPFisnq7MFbAAgAAAAACE7JvOpBgMoZ7kRd4QbxIhxukPTUxXpzhjnBHiR7XoRAAMxMzcAfQAAAAVkACAAAAAA8NJKN0IxZnruhswGQkiruv8Ih0EMwDcSZx/Xasup9dkFcwAgAAAAAKaJZRxzA+Igeydvuk6cSwUHXcrmT4PjhuPu//FslpdnBWwAIAAAAAD53Rok1Vq/PMAnXmarqoHJ0PEyYUBmVESa9hIpCv/G9QADMTM4AH0AAAAFZAAgAAAAABHxHdEClz7hbSSgE58+dWLlSMJnoPz+jFxp4bB1GmLQBXMAIAAAAAD3nSvT6aGD+A110J/NwEfp0nPutlmuB5B+wA3CC3noGAVsACAAAAAA3Apjd+TapONB7k5wBVwTWgn8t+Sq2oyyU5/+as109RcAAzEzOQB9AAAABWQAIAAAAAC/o8qW/ifk3KuJ01VFkyNLgQafxB5/bGs2G5VyyVafOwVzACAAAAAA1bMqAFGDHSl6BYNLbxApvkAv2K1/oafywiX0MDz1dGUFbAAgAAAAAHJXLlId3edFoniLD/9K2A5973MeP2Ro31flDyqm3l5QAAMxNDAAfQAAAAVkACAAAAAAY2V8I1bz3a1AxTtmED6UhdhA09huFkuuEX8R+d/WDPUFcwAgAAAAAPTVoNRiI76tcRKqd+JBBVyy4+YcKST42p0QX2BtmQ2VBWwAIAAAAACcxt9hg14WqPNiDv1MkqVljM2e2KJEv53lA17LhV6ZigADMTQxAH0AAAAFZAAgAAAAAO2kSsW0WGN9AOtK4xK2SHrGhWiaAbMEKT4iZkRpaDN/BXMAIAAAAABKGzQcPM8LT2dwOggxoWjv/1imYWabbG/G4kBw8OWaxAVsACAAAAAAC9hLK1dScQTAqg+YAG3ObdPzg2Xet57HmOFpGmyUR9UAAzE0MgB9AAAABWQAIAAAAAAiCwzNEEaH/mDam68IdDftnhthyUFdb+ZCNSBQ91WlHQVzACAAAAAA7tHyHcxCzmbJeFYZyPm4mEgkTGKOvwY4MX82OvH0Jn8FbAAgAAAAAAb5IAbZ1hXCNegQ+S+C9i/Z8y6sS8KeU04V6hXa2ml6AAMxNDMAfQAAAAVkACAAAAAAGuCHVNJSuoVkpPOnS5s89GuA+BLi2IPBUr2Bg1sWEPIFcwAgAAAAAEl1gncS5/xO7bQ/KQSstRV3rOT2SW6nV92ZANeG2SR6BWwAIAAAAAA9LOcKmhek8F2wAh8yvT/vjp2gaouuO+Hmv10lwAeWPAADMTQ0AH0AAAAFZAAgAAAAAMfxz7gEaoCdPvXrubDhCZUS0ARLZc1svgbXgMDlVBPgBXMAIAAAAAB6a5dDA3fuT5Vz2KvAcbUEFX/+B7Nw2p1QqbPoQ5TTuAVsACAAAAAAcf/y75UOuI62A6vWH7bYr/5Jz+nirZVYK/81trN6XOQAAzE0NQB9AAAABWQAIAAAAACnYsqF/VzmjIImC9+dqrHO1TM6lJ6fRwM0mM6Wf6paOwVzACAAAAAA5tgZzch8uDCR1ky3SllVaKVpxAlbrhvlNDTazZZRZOAFbAAgAAAAALeGiLJS4z2zhgVpxzyPdRYyACP9QzQBOob34YrIZumCAAMxNDYAfQAAAAVkACAAAAAAEC0sIVmadtW4YMuRXH7RpAhXclsd+3bmqGXCMeaT014FcwAgAAAAABPpXh0uzpsJJB+IRUNajmMB9WGwswfpw5T9xk3Xj6ANBWwAIAAAAAAmf+NYh9TZ/QRu3w/GQz66n7DtfbJijN3G7KzeL8lstAADMTQ3AH0AAAAFZAAgAAAAABaIB3n49Xm9cOafSrQsE0WCcYp8rMIO/qVwIlMF5YLRBXMAIAAAAAC9EyWJV3xOu9bzgdJ/yX+ko7qLf1u3AxNMataW2C9EzQVsACAAAAAAvVbDkLxXx2DcMLifIQ3K0IIJcLcAG9DUrNfI6aoUjNcAAzE0OAB9AAAABWQAIAAAAAA5rZItA/cocRnngYqcJ3nBXQ+l688aKz3EQyLbYYunPAVzACAAAAAAwKyA+L7TgxztPClLrIMk2JXR+w7c04N3ZOqPgjvrIvsFbAAgAAAAACzvZ33h6aWEe8hmo+1f6OXJ72FY5hvWaUuha64ZV3KFAAMxNDkAfQAAAAVkACAAAAAA3htn7oHJ0YYpIrs+Mzyh85Ys67HwAdv5LQl1mCdoMWkFcwAgAAAAAEHjCtNNLenHuSIYux6ezAHsXDaj2DlTF67ToDhDDe6HBWwAIAAAAAD+P4H0sk9jOd+7vOANt2/1Ectb+4ZRGPE8GkHWNXW3MgADMTUwAH0AAAAFZAAgAAAAAEnt18Km/nqggfIJWxzTr9r3hnXNaueG6XO9A5G11LnGBXMAIAAAAAD7QxzGMN/ard5TfFLecE6uusMmXG2+RBsBR+/NCQHUwAVsACAAAAAAQEZ1ZZ8GC8rdbg7s87OM5Gr9qkTXS9+P5DuAZxj5Gl4AAzE1MQB9AAAABWQAIAAAAAAVAKK/GoY8AACu/hyMpO4hdLq6JnEyWNzkyci9sbaD/wVzACAAAAAA2HmeqpMlvvBpV2zQTYIRmsc4MFlfHRwLof0ycJgMg/MFbAAgAAAAACdltCeWi5E/q1Li1eXLChpM2D9QQSGLBZ82NklQSc0oAAMxNTIAfQAAAAVkACAAAAAAhHyq1GQC/GiMwpYjcsfkNxolJ10ARKjIjfkW1Wipzi0FcwAgAAAAAD/uaGWxTDq87F8XZ6CrFI+RNa8yMqfSZdqK00Kj833BBWwAIAAAAAD6aEdOO0CsQGagioOCvANPCEHSpJ8BSixlPBq5ERhB7AADMTUzAH0AAAAFZAAgAAAAABAJJxHoZD+MQBWqm9UM9Dd3z5ZohIZGWRaRVRsMptKQBXMAIAAAAADrE/ca+gqj/SH4oao4wE4qn2ovoTydzcMbDbrfnUs3zAVsACAAAAAAeNCIQN6hVnGJinytQRFGlQ2ocoprXNqpia+BSxzl+uwAAzE1NAB9AAAABWQAIAAAAAAv01wz7VG9mTepjXQi6Zma+7b/OVBaKVkWNbgDLr1mFgVzACAAAAAA0I5sxz8r6wkCp5Tgvr+iL4p6MxSOq5d3e1kZG+0b7NkFbAAgAAAAAIA32v6oGkAOS96HexGouNTex+tLahtx9QF2dgGClk6WAAMxNTUAfQAAAAVkACAAAAAAWXecRwxSon68xaa9THXnRDw5ZfzARKnvvjTjtbae6T0FcwAgAAAAAPh0UfUMEo7eILCMv2tiJQe1bF9qtXq7GJtC6H5Va4fIBWwAIAAAAADqFr1ThRrTXNgIOrJWScO9mk86Ufi95IDu5gi4vP+HWQADMTU2AH0AAAAFZAAgAAAAAEY5WL8/LpX36iAB1wlQrMO/xHVjoO9BePVzbUlBYo+bBXMAIAAAAABoKcpadDXUARedDvTmzUzWPe1jTuvD0z9oIcZmKuiSXwVsACAAAAAAJuJbwuaMrAFoI+jU/IYr+k4RzAqITrOjAd3HWCpJHqEAAzE1NwB9AAAABWQAIAAAAADnJnWqsfx0xqNnqfFGCxIplVu8mXjaHTViJT9+y2RuTgVzACAAAAAAWAaSCwIXDwdYxWf2NZTly/iKVfG/KDjHUcA1BokN5sMFbAAgAAAAAJVxavipE0H4/JQvhagdytXBZ8qGooeXpkbPQ1RfYMVHAAMxNTgAfQAAAAVkACAAAAAAsPG7LaIpJvcwqcbtfFUpIjj+vpNj70Zjaw3eV9T+QYsFcwAgAAAAAJQ71zi0NlCyY8ZQs3IasJ4gB1PmWx57HpnlCf3+hmhqBWwAIAAAAACD58TO6d+71GaOoS+r73rAxliAO9GMs4Uc8JbOTmC0OwADMTU5AH0AAAAFZAAgAAAAAAGiSqKaQDakMi1W87rFAhkogfRAevnwQ41onWNUJKtuBXMAIAAAAAASgiDpXfGh7E47KkOD8MAcX8+BnDShlnU5JAGdnPdqOAVsACAAAAAAI+2TTQIgbFq4Yr3lkzGwhG/tqChP7hRAx2W0fNaH6jcAAzE2MAB9AAAABWQAIAAAAAB7L4EnhjKA5xJD3ORhH2wOA1BvpnQ+7IjRYi+jjVEaJAVzACAAAAAAuhBIm0nL3FJnVJId+7CKDASEo+l2E89Z9/5aWSITK4AFbAAgAAAAALtSICOzQDfV9d+gZuYxpEj6cCeHnKTT+2G3ceP2H65kAAMxNjEAfQAAAAVkACAAAAAAaROn1NaDZFOGEWw724dsXBAm6bgmL5i0cki6QZQNrOoFcwAgAAAAANVT8R6UvhrAlyqYlxtmnvkR4uYK/hlvyQmBu/LP6/3ZBWwAIAAAAAD+aHNMP/X+jcRHyUtrCNkk1KfMtoD3GTmShS8pWGLt+AADMTYyAH0AAAAFZAAgAAAAADqSR5e0/Th59LrauDA7OnGD1Xr3H3NokfVxzDWOFaN7BXMAIAAAAACt30faNwTWRbvmykDpiDYUOCwA6QDbBBYBFWS7rdOB4AVsACAAAAAAF7SvnjjRk5v2flFOKaBAEDvjXaL1cpjsQLtK2fv9zdQAAzE2MwB9AAAABWQAIAAAAADmtb1ZgpZjSeodPG/hIVlsnS8hoRRwRbrTVx89VwL62AVzACAAAAAAi38e1g6sEyVfSDkzZbaZXGxKI/zKNbMasOl2LYoWrq8FbAAgAAAAAALACk0KcCDN/Kv8WuazY8ORtUGkOZ5Dsm0ys1oOppp/AAMxNjQAfQAAAAVkACAAAAAAf/f7AWVgBxoKjr7YsEQ4w/fqSvuQWV2HMiA3rQ7ur0sFcwAgAAAAADkkeJozP6FFhUdRIN74H4UhIHue+eVbOs1NvbdWYFQrBWwAIAAAAAB55FlHAkmTzAYj/TWrGkRJw2EhrVWUnZXDoMYjyfB/ZwADMTY1AH0AAAAFZAAgAAAAAI2WEOymtuFpdKi4ctanPLnlQud+yMKKb8p/nfKmIy56BXMAIAAAAADVKrJmhjr1rfF3p+T+tl7UFd1B7+BfJRk0e7a4im7ozgVsACAAAAAA5E7Ti3PnFiBQoCcb/DN7V1uM3Xd6VKiexPKntssFL7kAAzE2NgB9AAAABWQAIAAAAAAuHU9Qd79hjyvKOujGanSGDIQlxzsql8JytTZhEnPw+AVzACAAAAAAjF2gV/4+sOHVgDd/oR5wDi9zL7NGpGD+NsEpGXy/a4QFbAAgAAAAAJzMoyojYV6Ed/LpVN5zge93Odv3U7JgP7wxeRaJZGTdAAMxNjcAfQAAAAVkACAAAAAA7dQDkt3iyWYCT94d7yqUtPPwp4qkC0ddu+HFdHgVKEkFcwAgAAAAANuYvtvZBTEq4Rm9+5eb7VuFopowkrAuv86PGP8Q8/QvBWwAIAAAAACeqXoAOQOE4j0zRMlkVd8plaW0RX1npsFvB38Xmzv7sAADMTY4AH0AAAAFZAAgAAAAAAwnZSDhL4tNGYxlHPhKYB8s28dY5ScSwiKZm3UhT8U3BXMAIAAAAABDoY6dhivufTURQExyC9Gx3ocpl09bgbbQLChj3qVGbgVsACAAAAAAF+1nS7O0v85s3CCy+9HkdeoEfm2C6ZiNbPMMnSfsMHUAAzE2OQB9AAAABWQAIAAAAAC2VuRdaC4ZJmLdNOvD6R2tnvkyARteqXouJmI46V306QVzACAAAAAAMn1Z6B35wFTX9mEYAPM+IiJ5hauEwfD0CyIvBrxHg7IFbAAgAAAAAOG6DvDZkT9B/xZWmjao2AevN7MMbs3Oh9YJeSd/hZ+hAAMxNzAAfQAAAAVkACAAAAAAVerb7qVNy457rNOHOgDSKyWl5ojun7iWrv1uHPXrIZQFcwAgAAAAAIDcYS9j5z+gx0xdJj09L7876r/vjvKTi/d3bXDE3PhyBWwAIAAAAADuhVLqb1Bkrx8aNymS+bx2cL8GvLFNH4SAi690DUgnWQADMTcxAH0AAAAFZAAgAAAAAH/E44yLxKCJjuSmU9A8SEhbmkDOx1PqqtYcZtgOzJdrBXMAIAAAAABgLh9v2HjBbogrRoQ82LS6KjZQnzjxyJH4PH+F3jupSAVsACAAAAAAIlO46ehXp4TqpDV0t6op++KO+uWBFh8iFORZjmx2IjkAAzE3MgB9AAAABWQAIAAAAAAlNUdDL+f/SSQ5074mrq0JNh7CTXwTbbhsQyDwWeDVMwVzACAAAAAANIH2IlSNG0kUw4qz0budjcWn8mNR9cJlYUqPYdonucAFbAAgAAAAAJMrOUOyiu5Y3sV76zwEFct8L7+i8WGlQI2+8z2W2kzaAAMxNzMAfQAAAAVkACAAAAAASZ+CvUDtlk/R4HAQ3a+PHrKeY/8ifAfh0oXYFqliu80FcwAgAAAAAJelpzPgM65OZFt/mvGGpwibclQ49wH+1gbUGzd9OindBWwAIAAAAAD9qeDchteEpVXWcycmD9kl9449C1dOw0r60TBm5jK+cQADMTc0AH0AAAAFZAAgAAAAAN9fkoUVbvFV2vMNMAkak4gYfEnzwKI3eDM3pnDK5q3lBXMAIAAAAACnDkgVNVNUlbQ9RhR6Aot2nVy+U4km6+GHPkLr631jEAVsACAAAAAANzg/BnkvkmvOr8nS4omF+q9EG/4oisB+ul4YHi938hwAAzE3NQB9AAAABWQAIAAAAAASyK3b1nmNCMptVEGOjwoxYLLS9fYWm/Zxilqea0jpEQVzACAAAAAADDHsGrbqlKGEpxlvfyqOJKQJjwJrzsrB7k3HG0AUJbkFbAAgAAAAAKwx3S4XfDZh4+LuI9jf7XgUh5qiefNv87JD4qvVRfPSAAMxNzYAfQAAAAVkACAAAAAAlSP9iK31GlcG9MKGbLmq+VXMslURr+As736rrVNXcsUFcwAgAAAAAAvbj0zfq9zzi8XReheKFbCB+h9IsOLgXPPpI5vrEJNZBWwAIAAAAABXvoZhaQE7ogWjeBjceVkp03N20cKYP3TA8vuNsgpfAgADMTc3AH0AAAAFZAAgAAAAAOJNORH8Bev97gVU7y6bznOxJ+E6Qoykur1QP76hG1/7BXMAIAAAAAC+C1PtOOrSZgzBAGhr+dPe/kR0JUw9GTwLVNr61xC1aAVsACAAAAAAeA/L8MQIXkamaObtMPLpoDoi5FypA5WAPtMeMrgi0eQAAzE3OAB9AAAABWQAIAAAAAAKcHzLUomavInN6upPkyWhAqYQACP/vdVCIYpiy6U6HgVzACAAAAAATsR4KItY6R2+U7Gg6sJdaEcf58gjd1OulyWovIqfxKcFbAAgAAAAAFbm10ko67ahboAejQdAV0U2uA5OhZYdb8XUFJ8OL46LAAMxNzkAfQAAAAVkACAAAAAAqTOLiMpCdR59tLZzzIPqJvbCNvz2XQL9ust0qYaehtcFcwAgAAAAAArefox/3k5xGOeiw2m6NUdzuGxmPwcu5IFcj+jMwHgHBWwAIAAAAADLZGFJ7MQd5JXMgMXjqZO5LDLxcFClcXPlnRMWRn+1oAADMTgwAH0AAAAFZAAgAAAAAIPSqSeVzSRgNVNmrPYHmUMgykCY27NbdDUNhE5kx/SgBXMAIAAAAAAhX90nNfxyXmZe/+btZ7q6xMX4PFyj0paM1ccJ/5IUUQVsACAAAAAA419oHmD2W0SYoOMwhrhrp8jf68fg9hTkaRdCuVd3CN0AAzE4MQB9AAAABWQAIAAAAACLn5DxiqAosHGXIAY96FwFKjeqrzXWf3VJIQMwx1fl4gVzACAAAAAAindvU27nveutopdvuHmzdENBbeGFtI3Qcsr07jxmvm8FbAAgAAAAAPvl9pBStQvP4OGkN5v0MghUY6djm9n7XdKKfrW0l1sMAAMxODIAfQAAAAVkACAAAAAA7i2S6rHRSPBwZEn59yxaS7HiYBOmObIkeyCcFU42kf8FcwAgAAAAAGb3RSEyBmgarkTvyLWtOLJcPwCKbCRkESG4RZjVmY4iBWwAIAAAAADB2/wo5CSHR4ANtifY6ZRXNTO5+O8qP82DfAiAeanpZwADMTgzAH0AAAAFZAAgAAAAAFz+M+H/Z94mdPW5oP51B4HWptp1rxcMWAjnlHvWJDWrBXMAIAAAAACBFEOQyL7ZHu4Cq33QvXkmKuH5ibG/Md3RaED9CtG5HwVsACAAAAAAfggtJTprQ/yZzj7y5z9KvXsdeXMWP0yUXMMJqpOwI88AAzE4NAB9AAAABWQAIAAAAAAE7c2x3Z3aM1XGfLNk/XQ9jCazNRbGhVm7H8c2NjS5ywVzACAAAAAARJ9h8fdcwA19velF3L/Wcvi2rCzewlKZ2nA0p8bT9uwFbAAgAAAAAJtWe6b4wK2Hae2dZm/OEpYQnvoZjz4Sz5IgJC2wInecAAMxODUAfQAAAAVkACAAAAAAVoRt9B9dNVvIMGN+ea5TzRzQC+lqSZ8dd/170zU5o9cFcwAgAAAAAEwM95XZin5mv2yhCI8+ugtKuvRVmNgzzIQN0yi1+9aIBWwAIAAAAAAMGBq72n00rox3uqhxSB98mkenTGCdbbUF1gXrgottzgADMTg2AH0AAAAFZAAgAAAAAKRDkjyWv/etlYT4GyoXrmBED2FgZHnhc+l9Wsl06cH2BXMAIAAAAABohlpm3K850Vndf3NmNE0hHqDlNbSR8/IvMidQ3LnIZAVsACAAAAAAW42nGHa6q2MCAaaPVwaIDfr8QLyQwjKq23onZJYsqVsAAzE4NwB9AAAABWQAIAAAAAC3DFh5oklLCNLY90bgWm68dFXz65JpAZSp1K99MBTPAQVzACAAAAAAQgZecmxEUZVHoptEQClDwAf8smI3WynQ/i+JBP0g+kQFbAAgAAAAAEUSQGVnAPISD6voD0DiBUqyWKgt2rta0tjmoe+LNt6IAAMxODgAfQAAAAVkACAAAAAAQ5WKvWSB503qeNlOI2Tpjd5blheNr6OBO8pfJfPNstcFcwAgAAAAAKwHgQLSDJ5NwLBQbY5OnblQIsVDpGV7q3RCbFLD1U4/BWwAIAAAAACQ5nED99LnpbqXZuUOUjnO2HTphEAFBjLD4OZeDEYybgADMTg5AH0AAAAFZAAgAAAAAGfhFY3RGRm5ZgWRQef1tXxHBq5Y6fXaLAR4yJhrTBplBXMAIAAAAACKEF0ApLoB6lP2UqTFsTQYNc9OdDrs/vziPGzttGVLKQVsACAAAAAArOO6FyfNRyBi0sPT5iye7M8d16MTLcwRfodZq4uCYKEAAzE5MAB9AAAABWQAIAAAAAAIM73gPcgzgotYHLeMa2zAU4mFsr7CbILUZWfnuKSwagVzACAAAAAAJCSu98uV8xv88f2BIOWzt6p+6EjQStMBdkGPUkgN79cFbAAgAAAAAMGqPGMPxXbmYbVfSa/japvUljht1zZT33TY7ZjAiuPfAAMxOTEAfQAAAAVkACAAAAAAkWmHCUsiMy1pwZTHxVPBzPTrWFBUDqHNrVqcyyt7nO8FcwAgAAAAAMv2CebFRG/br7USELR98sIdgE9OQCRBGV5JZCO+uPMgBWwAIAAAAABt7qSmn3gxJu7aswsbUiwvO+G6lXj/Xhx+J/zQyZxzLAADMTkyAH0AAAAFZAAgAAAAAGInUYv0lP/rK7McM8taEHXRefk8Q2AunrvWqdfSV7UaBXMAIAAAAACE+WPxJ3gan7iRTbIxXXx+bKVcaf8kP4JD8DcwU0aL7wVsACAAAAAAUC4eTprX4DUZn2X+UXYU6QjtiXk+u57yoOPBbPQUmDkAAzE5MwB9AAAABWQAIAAAAACmHlg2ud3cplXlTsNTpvNnY6Qm1Fce0m899COamoDjaQVzACAAAAAArtJQeJIlepBWRU2aYar7+YGYVQ7dfDc1oxgTmA8r9q0FbAAgAAAAAOk45vg5VqZHAFCO3i0Z52SZi5RADf8NXwf68T5yad/DAAMxOTQAfQAAAAVkACAAAAAApzcWSAbZWV/Rq+ylRNqqlJqNVR4fhXrz4633/MQOQgcFcwAgAAAAAN/jz/bsEleiuCl+li83EWlG6UMHA8CyaOMRKCkXkSCPBWwAIAAAAAC3Sd+Qg+uFDKpGZHbrQgokXHQ1az1aFl4YK343OB6hcQAAEmNtAAAAAAAAAAAAABBwYXlsb2FkSWQAAAAAABBmaXJzdE9wZXJhdG9yAAEAAAASc3AAAQAAAAAAAAAQdGYAAQAAABNtbgD/////Y46NN8CHrb4J7f/fE214AP////9jjo03wIetvgnt/18A", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.yml new file mode 100644 index 000000000..daf89dd91 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Delete.yml @@ -0,0 +1,899 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDecimalNoPrecision": { $gt: {$numberDecimal: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.json new file mode 100644 index 000000000..4ab3b63ea --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.json @@ -0,0 +1,1906 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Decimal. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + }, + "update": { + "$set": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.yml new file mode 100644 index 000000000..e470f42db --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-FindOneAndUpdate.yml @@ -0,0 +1,1672 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimalNoPrecision": {$numberDecimal: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimalNoPrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.json new file mode 100644 index 000000000..5a2adf690 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.json @@ -0,0 +1,1893 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Decimal. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.yml new file mode 100644 index 000000000..0007eeff6 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-InsertFind.yml @@ -0,0 +1,1668 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: find + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RGTjNVEsNJb+DG7DpPOam8rQWD5HZAMpRyiTQaw7tk8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RlQWwhU+uVv0a+9IB5cUkEfvHBvOw3B1Sx6WfPWMqes=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubb81XTC7U+4tcNzf1oYvOY6gR5hC2Izqx54f4GuJ0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6M4Q5NMQ9TqNnjzGOxIkiUIY8TEL0I3XD1QnhefQUqU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BtInzk9t2FFMCEY6AQ7zN8jwrrZEs2irSv6q0Q4NaIw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vxXfETu9cuBIpRBo3jUUU04mJIH/aAhLX8K6VI5Xv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXPCdS+q23zi1bkPnaVG2j0PsVtxdeSLJ//h6J1x8RU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KY3KkfBAsN2l80wbpj41G0gwBR5KmmFnZcagg7D3ENk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI8NFAxXCX4VOnY5X73K6KI/Yspd3aR94KV39MhJlAw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nFxH0UC3mATKA6Vboz+QX/hAjj19kF/SH6H5Cne7qC0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q8hYqIYaIi7nOdG/7qQZYnz8Bsacfi66M1nVku4SH08=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4saA92R4arp4anvD9xFtze+sNcQqTEhPHyl1h70A8NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DbIziOBRRyeQS6RtBR09E37LV+CTKrEjGoRMLSpG6eE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Fv80Plp/7w2gnVqrwawLd6qhJ10G4NCDm3re67cNq4Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T/T2oiQCBBES4YN7EodzPRdabZSFlYIClHBym+bQUZE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQgHD3l46Ujqtbnj1VbbeM29C9wJzOhz+yZ/7XdSrxk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ltlFKzWvyZvHxDFOYDd/XXJ6kUiJj0ln2HTCEz2o4Z4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "flW8A7bltC1u8bzx0WJtxosGJdOVsJFfbx33jxnpFGg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SXO+92QbMKwUSG2t27ciunV1c3VvFkUuDmSczpRe008=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+KioGs1GM+xRBzFE67ePTWj04KMSE5/Y6qUF7nJ5kvU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L3xNVbh6YH+RzqABN+5Jgb7T234Efpn766DmUvxIxgg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hPF+60mBYPjh21dEmPlBhKgyc9S2qLtTkypYvnqP2Fc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EletRsETy2HcjaPIm2c8CkT7ch/P3pJJDC8hasepcSU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "r5bMXUaNKqLPxZ+TG9HYTG4aSDgcpim27rN8rQFkM0w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Q7Erdr8+/S0wUEDDIqlS5XjBVWvhZY65K0uUDb6+Ns=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xEcnhXy35hbXNVBPOOt3TUHbxvKfQ48KjA9b6/rbMqQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "T8bEpiQNgsEudXvyKE9SZlSvbpV/LUaslsdqgSFltyo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hIoiaF2YjnxDbODfhFEB+JGZ5nf8suD3Shck5bwQ3N0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qnA6qzejeRJ0rsZaZ0zOvKAaXyxt5lpscKQNYFZNl4k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "anAKCL2DN/le2VaP0n2ucYSEH/DaaEH/8Sa4OqTZsRA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JCZlBJaFm618oWYSnT9Jr1MtwFVw4BZjOzO+5yWgR90=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yxyk4n9762WzcDVGnTn4jCqUnSMIVCrLDIjCX1QVj34=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fDI6fdKvDJwim5/CQwWZEzcrXE3LHgy7FTtffcC7tXE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Vex+gcz5T+WkzsVZQrkqUR2ryyZbnaOGuWpYvjN0zCw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8TLEXz+Gbbp6llHpZXVjLsdlYY9f6hrKpHVpyfDe0RY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fTyt5BrunypS65TfOzFW2E2qdIuT4SLeDeGlbQoJCs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8fKGrkqN0/KuSjyXgDBmRauDKrSa//JBKRWHEB9xBf4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s4codmG7uN4ss6P357jL21lazEe90M9GOK5WrOknSV0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RkSpua8XF+NUdxVDU90EbLUTTyZFX3tt3atBTroFaRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LnTCuCDyAHK5B9KXzjtwGmWB+qergQk2OCjnIx9MI2A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cBFh0virAX4pVXf/udIGI2951i0+0aZAdJcBVGtYnT4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "G54X6myQXWZ5fw/G31en3QbdgfXzL9+hFTtJpnWMqDI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EdsiiuezcsFJFnYIyGjCOhnqMj1BOwTB5EFxN+ERUkg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dVH9MXLtk0WTwGQ3xmrhOqfropMUkDW3o6paNPGl3NU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sB3HqXKWY3pKbuEH8BTbfNIGfbY+7/ZbOc3XC+JRNNI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WHyDk62Xhqbo4/iie2aLIM4x2uuAjv6102dJSHI58oM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pNUFuHpeNRDUZ/NrtII2c6sNc9eGR1lIUlIyXKERA+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UPa+pdCqnN0bfAptdzldQOSd01gidrDKy8KhWrpSKAI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l+7dOAlo+HUffMqFYXL6pgUFeTbwOM9CjKQLxEoLtc4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SRnDXV/rN6C8xwMutv9E1luv3DOUio3VkgPr8Cpm7Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QcH6gl+gX7xZ7OWhUNQMbndJy0Piz49pDo6RsnLkVSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "t+uL4DnfsI/Zll/KXWW1cOKX3Hu8WIkm3pt9efCVSAQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "myutHDctku/+Uug/nD8gRbYvmx/IovtoAAC2/fz2oHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6C+cjD0e0nSCP6cPqQYbNG7SlOd6Mfvi8hyfm7Ng+D8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zg01JSoOj9oBKT0S1ldJucXzY5AKgreS+h2xJreWTOs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7qQ80/FjodHl1m1py/Oii0/9C/xWbLdhaRXQ+kkCP10=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YwWMNH07vL6c5Nhg+MRnVByhzUunu8y0VLM9z/XvR5U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Dle8bU98+fudAbc14SToZFkwvV3tcYVsjDug0NWljpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "J+eKL1vPJmlzltvhI6Li5Fz/TJmi3Ng+ehRTcs46API=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB3XzfFygLwC3WHkj0up+VbEd25KKoce1vOpG/5bwK4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vnVnmOnL+z2pqwE+A6cVKS0Iwy4F4/2IiElJca9bUQM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+lG5r/Fpqry3BtFuvY67+RntmHAMDoLVOSGc6ZoXPb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L5MXQertqc6uj7ADe8aWKbd1sYHPCE7P1VYVg9Zc3VI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "imKONuZgopt0bhM3GMX2WVPwQYMTobuUUEdhcLfHs4c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eOkU1J1uVbiVFWBerbXsSIVcF2nqiicTkFy4x7kFHB8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gI0uDhXeoH/UatDQKEf4qo8FHzWZDhb/wuWTqbq/ID4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cOkd5Aa3btYhtojE/smsF/PJnULqQ4NNqTkU6KXTFmo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AWNJMs1MTe294oFipp8Y6P0CjpkZ4qCZoClQF3XcHq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6gJtlzXOFhGYrVbTuRMmvMlDTwXdNtR9aGBlHZPwIMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LEmwVGA/xsEG7UrcOoYLFu6KCXgijzFznenknuDacm8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mIRFPTXRrGaPtp/Ydij2jgkRe4uoUvAKxW2d8b9zYL0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+Uv2u48WALOO0L311z+eryjYQzKJVMfdHMZPhOAFmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "INXXp0wDyVCq+NtfIrrC2ciETmyW/dWB/48/u4yLEZ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "se7DGo8XrlrQDLEcco1tZrQt9kDe+0RTyl2bw/quG4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vr0m2+Zk9lbN6UgWCyn8xJWJOokU3IDYab5U5q1+CgQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XI+eJ8Gy2JktG1gICgoj1qpsfy1tKmH0kglWbaQH6DA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A+UCuNnuAUqnQzspA6TVqUPRmtZmpSex5HFw7THRxs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaH2Ehfljd19uo0Fvb3iwkdaiWEVQd2YPoitgEPkhSM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S/iZBJGcc8+qZxyMtab65MMBoSglybwk3x58Nb86gnY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w14ZE5qqY5YgkS4Zcs9YNbrQbY1XfGOOHNn9bOYnFVQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0MhGd/jEF1vjkKGp+ZMn9SjLK54jkp9W4Hg+Sp/oxaI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92QZ73e/NRTYgCm4aifaKth6aAsKnLLccBc0zx/qUTY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WOjzemCgFJOiGIp81RSVh/tFlzSTj9eFWcBnsiv2Ycs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DrsP9CmfKPjw5yLL8bnSeAxfNzAwlb+Z8OqCiKgBY7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lMogqg8veBv6mri3/drMe9afJiKMvevkmGcw9BedfLo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TxqwNcY8Tg2MPpNdkPBwvfpuTttSYRHU26DGECKYQ9o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "l0u1b4b4vYACWIwfnB7PZac4oDEgjQZCzHruNPTgAIY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iVSGQ+cCfhbWIrY/v/WBORK92elu9gfRKyGhr6r/k00=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yK1forG50diEXte8ECzjfpHeYsPyuQ/dgxbxn/nzY5k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gIfTLCD3VwnOwkC0zPXWTqaITxX6ZplA69PO2a6zolc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O/Zxlgh3WqpzJ7+Sd8XWMVID4/GXJUUWaSqfgDUi3b0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZQ6yv368zwahUqSUYH/StL0Qgz/TwS1CzlMjVDvCciI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m2rPEYkjwyiKdonMrKlcF7hya4lFOAUwEePJ3SgrNx8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mq0yl5iVKlq71bT/dT/fXOWf2n90bTnXFnOdGDN0JOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6qDGMXipPLC2O6EAAMjO2F9xx4rdqZso4IkPpH2304U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jvQHRQQa2RIszE2LX2Hv2LbRhYawJ6qmtRt8HZzFQXg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ovJXQrkZlpeHRciKyE/WWNm5O389gRgzx1W+Dw596X4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "a4kgRNvYctGYqyQv9qScL/WkljTYVylJ9pE9KDULlxU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qV4Q48vPiCJMTjljotzYKI/zfExWpkKOSHGcAjGyDig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jtI7zbBF+QW/aYYTkn90zzyHLXLgmy7l1bzgMb2oqic=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q0KmJl9txPdn962UNvnfe6UFhdk9YaFZuTm33F+csso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ULNdEqeZJgtmNOhN/Y9INzsE9AnxWYwOMn+pIbRXIFs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "R4oz9+wkdjpKe5tE1jpG7IURAnfvS5fLP4LrD5cZfTE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qG5Z7VhwSu/HT/YFTgDzyAAzJKq51xPw2HeEV5btYC4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OM/1DmIIZ5Qyhtq8TGkHTBEMVKjAnKRZMRXYtTG8ctc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2R5vZbljLXnDFA99YfGuRB7pAdPJVKsT25zLNMC0fUk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OMbavF2EmdAz1fHkLV3ctFEUDfriKhoT2gidwHZ9z1o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MWT4Zrw3/vVvTYMa1Is5Pjr3wEwnBfnEAPPUAHKQhNU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tBkRPfG9yxfKocQx5pAJX0oEHKPL0Tgtr+0UYe09InE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lqxpnDR/H0YgH7RcfKoNoaaRhe1SIazIeMbQ1fu9y3Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "utT1UdR22PWOTrOkZauztX613lAplV4eh/ejTRb7ZSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S+Y2yFyKi/a6FXhih4yGo29X8I8OT6/zwEoX6NMKT4o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QSjVppg29x6oS5yBg8OFjrFt0tuTpWCuKxfIy0k8YnE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y3r6/Xsfvsl3HksXlVYkJgHUqpQGfICxg3x9f8Zw1qM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BSltHzEwDjFN4du9rDHAPvl22atlcTioEtt+gC5L1tk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0arGXjSN0006UnXbrWsGqhvBair569DeFDUME3Df3rA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s/DumaMad08S+PBUUcrS+v42K0z8HgcdiQtrFAEu2Qs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EzJ8Y8N0OQBTlnvrK82PdevDNZZO4E6CNgYVu8Cj6Ks=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VA4vr8jBPI5QdiPrULzzZjBMIUbG3V7Slg5zm0bFcKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YAOvEB2ZLtq9LQiFViBHWaxxWVVonC2rNYj9tN9s3L0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hgaHMo9aAGS+nBwvqnTjZO+YkiQPY1c1XcIYeaYKHyI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YvaoLt3ZpH0atB0tNzwMjpoxRYJXl0DqSjisMJiGVBE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EMmW6CptFsiLoPOi5/uAJQ2FmeLg6mCpuVLLrRWk7Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1jQsNMarSnarlYmXEuoFokeBMg/090qUD9wqo1Zn8Gs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hupXNKhRpJxpyDAAP1TgJ5JMZh9lhbMk6s7D7dMS3C8=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.json new file mode 100644 index 000000000..b840d3834 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.json @@ -0,0 +1,1910 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Decimal. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + }, + "update": { + "$set": { + "encryptedDecimalNoPrecision": { + "$numberDecimal": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalNoPrecision": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalNoPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.yml new file mode 100644 index 000000000..b1d1c4c00 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Decimal-Update.yml @@ -0,0 +1,1685 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + # Tests for Decimal (without precision) must only run against a replica set. Decimal (without precision) queries are expected to take a long time and may exceed the default mongos timeout. + topology: [ "replicaset" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalNoPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Decimal. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalNoPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalNoPrecision: { $numberDecimal: "1" } } + - name: updateOne + arguments: + filter: { encryptedDecimalNoPrecision: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimalNoPrecision": { $numberDecimal: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimalNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalNoPrecision": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "rbf3AeBEv4wWFAKknqDxRW5cLNkFvbIs6iJjc6LShQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0l86Ag5OszXpa78SlOUV3K9nff5iC1p0mRXtLg9M1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hn6yuxFHodeyu7ISlhYrbSf9pTiH4TDEvbYLWjTwFO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zdf4y2etKBuIpkEU1zMwoCkCsdisfXZCh8QPamm+drY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rOQ9oMdiK5xxGH+jPzOvwVqdGGnF3+HkJXxn81s6hp4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "61aKKsE3+BJHHWYvs3xSIBvlRmKswmaOo5rygQJguUg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KuDb/GIzqDM8wv7m7m8AECiWJbae5EKKtJRugZx7kR0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Q+t8t2TmNUiCIorVr9F3AlVnX+Mpt2ZYvN+s8UGict8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJRZIpKxUgHyL83kW8cvfjkxN3z6WoNnUg+SQw+LK+k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnUsYjip8SvW0+m9mR5WWTkpK+p6uwJ6yBUAlBnFKMk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PArHlz+yPRYDycAP/PgnI/AkP8Wgmfg++Vf4UG1Bf0E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wnIh53Q3jeK8jEBe1n8kJLa89/H0BxO26ZU8SRIAs9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4F8U59gzBLGhq58PEWQk2nch+R0Va7eTUoxMneReUIA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ihKagIW3uT1dm22ROr/g5QaCpxZVj2+Fs/YSdM2Noco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EJtUOOwjkrPUi9mavYAi+Gom9Y2DuFll7aDwo4mq0M0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dIkr8dbaVRQFskAVT6B286BbcBBt1pZPEOcTZqk4ZcI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aYVAcZYkH/Tieoa1XOjE/zCy5AJcVTHjS0NG2QB7muA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sBidL6y8TenseetpioIAAtn0lK/7C8MoW4JXpVYi3z8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0Dd2klU/t4R86c2WJcJDAd57k/N7OjvYSO5Vf8KH8sw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I3jZ92WEVmZmgaIkLbuWhBxl7EM6bEjiEttgBJunArA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aGHoQMlgJoGvArjfIbc3nnkoc8SWBxcrN7hSmjMRzos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bpiWPnF/KVBQr5F6MEwc5ZZayzIRvQOLDAm4ntwOi8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tI7QVKbE6avWgDD9h4QKyFlnTxFCwd2iLySKakxNR/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XGsge0CnoaXgE3rcpKm8AEeku5QVfokS3kcI+JKV1lk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JQxlryW2Q5WOwfrjAnaZxDvC83Dg6sjRVP5zegf2WiM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YFuHKJOfoqp1iGVxoFjx7bLYgVdsN4GuUFxEgO9HJ5s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z6vUdiCR18ylKomf08uxcQHeRtmyav7/Ecvzz4av3k4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SPGo1Ib5AiP/tSllL7Z5PAypvnKdwJLzt8imfIMSEJQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "m94Nh6PFFQFLIib9Cu5LAKavhXnagSHG6F5EF8lD96I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pfEkQI98mB+gm1+JbmVurPAODMFPJ4E8DnqfVyUWbSo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DNj3OVRLbr43s0vd+rgWghOL3FqeO/60npdojC8Ry/M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kAYIQrjHVu49W8FTxyxJeiLVRWWjC9fPcBn+Hx1F+Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "aCSO7UVOpoQvu/iridarxkxV1SVxU1i9HVSYXUAeXk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Gh6hTP/yj1IKlXQ+Q69KTfMlGZjEcXoRLGbQHNFo/1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/gDgIFQ4tAlJk3GN48IS5Qa5IPmErwGk8CHxAbp6gs0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PICyimwPjxpusyKxNssOOwUotAUbygpyEtORsVGXT8g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4lu+cBHyAUvuxC6JUNyHLzHsCogGSWFFnUCkDwfQdgI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pSndkmoNUJwXjgkbkgOrT5f9nSvuoMEZOkwAN9ElRaE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tyW+D4i26QihNM5MuBM+wnt5AdWGSJaJ4X5ydc9iWTU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9Syjr8RoxUgPKr+O5rsCu07AvcebA4P8IVKyS1NVLWc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "67tPfDYnK2tmrioI51fOBG0ygajcV0pLo5+Zm/rEW7U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "y0EiPRxYTuS1eVTIaPQUQBBxwkyxNckbePvKgChwd0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NWd+2veAaeXQgR3vCvzlI4R1WW67D5YsVLdoXfdb8qg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PY5RQqKQsL2GqBBSPNOEVpojNFRX/NijCghIpxD6CZk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lcvwTyEjFlssCJtdjRpdN6oY+C7bxZY+WA+QAqzj9zg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWE7XRNylvTwO/9Fv56dNqUaQWMmESNS/GNIwgBaEI0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ijwlrUeS8nRYqK1F8kiCYF0mNDolEZS+/lJO1Lg93C8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8KzV+qYGYuIjoNj8eEpnTuHrMYuhzphl80rS6wrODuU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wDyTLjSEFF895hSQsHvmoEQVS6KIkZOtq1c9dVogm9I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SGrtPuMYCjUrfKF0Pq/thdaQzmGBMUvlwN3ORIu9tHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KySHON3hIoUk4xWcwTqk6IL0kgjzjxgMBObVIkCGvk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hBIdS9j0XJPeT4ot73ngELkpUoSixvRBvdOL9z48jY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Tx6um0q9HjS5ZvlFhvukpI6ORnyrXMWVW1OoxvgqII0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zFKlyfX5H81+d4A4J3FKn4T5JfG+OWtR06ddyX4Mxas=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cGgCDuPV7MeMMYEDpgOupqyNP4BQ4H7rBnd2QygumgM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IPaUoy98v11EoglTpJ4kBlEawoZ8y7BPwzjLYBpkvHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Pfo4Am6tOWAyZNn8G9W5HWWGC3ZWmX0igI/RRB870Ro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fnTSjd7bC1Udoq6iM7UDnHAC/lsIXSHp/Gy332qw+/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fApBgVRrTDyEumkeWs5p3ag9KB48SbU4Si0dl7Ns9rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QxudfBItgoCnUj5NXVnSmWH3HK76YtKkMmzn4lyyUYY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sSOvwhKa29Wq94bZ5jGIiJQGbG1uBrKSBfOYBz/oZeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FdaMgwwJ0NKsqmPZLC5oE+/0D74Dfpvig3LaI5yW5Fs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "sRWBy12IERN43BSZIrnBfC9+zFBUdvjTlkqIH81NGt4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/4tIRpxKhoOwnXAiFn1Z7Xmric4USOIfKvTYQXk3QTc=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Mr/laWHUijZT5VT3x2a7crb7wgd/UXOGz8jr8BVqBpM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VDCpBYsJIxTfcI6Zgf7FTmKMxUffQv+Ys8zt5dlK76I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zYDslUwOUVNwTYkETfjceH/PU3bac9X3UuQyYJ19qK0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rAOmHSz18Jx107xpbv9fYcPOmh/KPAqge0PAtuhIRnc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BFOB1OGVUen7VsOuS0g8Ti7oDsTt2Yj/k/7ta8YAdGM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fckE5SPs0GU+akDkUEM6mm0EtcV3WDE/sQsnTtodlk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mi9+aNjuwIvaMpSHENvKzKRAmX9cYguo2mXLvOoftHQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K6TWn4VcWWkz/gkUkLmbtwkG7SNeABICmLDnoYJFlLU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z+2/cEtGU0Fq7QJFNGA/0y4aWAsw0ncG6X0LYRqwS3c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rrSIf+lgcNZFbbUkS9BmE045jRWBpcBJXHzfMVEFuzE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KlHL3Kyje1/LMIfgbCqw1SolxffJvvgsYBV5y77wxuA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hzJ1YBoETmYeCh352dBmG8d8Wse/bUcqojTWpWQlgsc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lSdcllDXx8MA+s0GULjDA1lQkcV0L8/aHtZ6dM2pZ2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "HGr7JLTTA7ksAnlmjSIwwdBVvgr3fv46/FTdiCPYpos=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "mMr25v1VwOEVZ8xaNUTHJCcsYqV+kwK6RzGYilxPtJ4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "129hJbziPJzNo0IoTU3bECdge0FtaPW8dm4dyNVNwYU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "doiLJ96qoo+v7NqIAZLq6BI5axV8Id8gT5vyJ1ZZ0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cW/Lcul3xYmfyvI/0x/+ybN78aQmBK1XIGs1EEU09N8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1aVIwzu9N5EJV9yEES+/g6hOTH7cA2NTcLIc59cu0wU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kw5tyl7Ew0r1wFyrN1mB9FiVW2hK2BxxxUuJDNWjyjQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ADAY2YBrm6RJBDY/eLLcfNxmSJku+mefz74gH66oyco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8gkqB1LojzPrstpFG7RHYmWxXpIlPDTqWnNsXH7XDRU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TESfVQMDQjfTZmHmUeYUE2XrokJ6CcrsKx/GmypGjOw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qFM+HFVQ539S0Ouynd1fBHoemFxtU9PRxE5+Dq7Ljy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jPiFgUZteSmOg4wf3bsEKCZzcnxmMoILsgp/GaZD+dM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YaWUgJhYgPNN7TkFK16H8SsQS226JguaVhOIQxZwQNQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x90/Qk3AgyaFsvWf2KUCu5XF3j76WFSjt/GrnG01060=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZGWybWL/xlEdMYRFCZDUoz10sywTf7U/7wufsb78lH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8l4ganN66jIcdxfHAdYLaym/mdzUUQ8TViw3MDRySPc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c8p5XEGTqxqvRGVlR+nkxw9uUdoqDqTB0jlYQ361qMA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZGFLlpQBcU3zIUg8MmgWwFKVz/SaA7eSYFrfe3Hb70=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "34529174M77rHr3Ftn9r8jU4a5ztYtyVhMn1wryZSkU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YkQ4pxFWzc49MS0vZM6S8mNo4wAwo21rePBeF3C+9mI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MhOf4mYY00KKVhptOcXf0bXB7WfuuM801MRJg4vXPgc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7pbbD8ihNIYIBJ3tAUPGzHpFPpIeCTAk5L88qCB0/9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C9Q5PoNJTQo6pmNzXEEXUEqH22//UUWY1gqILcIywec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AqGVk1QjDNDLYWGRBX/nv9QdGR2SEgXZEhF0EWBAiSE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/sGI3VCbJUKATULJmhTayPOeVW+5MjWSvVCqS77sRbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yOtbL0ih7gsuoxVtRrACMz+4N5uo7jIR7zzmtih2Beo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uA6dkb2Iyg9Su8UNDvZzkPx33kPZtWr/CCuEY+XgzUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1DoSFPdHIplqZk+DyWAmEPckWwXw/GdB25NLmzeEZhk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OfDVS0T3ZuIXI/LNbTp6C9UbPIWLKiMy6Wx+9tqNl+g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3PZjHXbmG6GtPz+iapKtQ3yY4PoFFgjIy+fV2xQv1YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kaoLN0BoBWsmqE7kKkJQejATmLShd8qffcAmlhsxsGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpiw9KgQdegGmp7IJnSGX2miujRLU0xzs0ITTqbPW7c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NuXFf7xGUefYjIUTuMxNUTCfVHrF8oL0AT7dPv5Plk4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8Tz53LxtfEBJ9eR+d2690kwNsqPV6XyKo2PlqZCbUrc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e6zsOmHSyV8tyQtSX6BSwui6wK9v1xG3giY/IILJQ2w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2fedFMCxa2DzmIpfbDKGXhQg0PPwbUv6vIWdwwlvhms=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yEJKMFnWXTC8tJUfzCInzQRByNEPjHxpw4L4m8No91Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YbFuWwOiFuQyOzIJXDbOkCWC2DyrG+248TBuVCa1pXU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "w7IkwGdrguwDrar5+w0Z3va5wXyZ4VXJkDMISyRjPGo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YmJUoILTRJPhyIyWyXJTsQ6KSZHHbEpwPVup6Ldm/Ko=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FvMjcwVZJmfh6FP/yBg2wgskK+KHD8YVUY6WtrE8xbg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4HCtD4HyYz0nci49IVAa10Z4NJD/FHnRMV4sRX6qro=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nC7BpXCmym+a0Is2kReM9cYN2M1Eh5rVo8fjms14Oiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1qtVWaeVo649ZZZtN8gXbwLgMWGLhz8beODbvru0I7Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ej+mC0QFyMNIiSjR939S+iGBm7dm+1xObu5IcF/OpbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UQ8LbUG3cMegbr9yKfKanAPQE1EfPkFciVDrNqZ5GHY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4iI3mXIDjnX+ralk1HhJY43mZx2uTJM7hsv9MQzTX7E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0WQCcs3rvsasgohERHHCaBM4Iy6yomS4qJ5To3/yYiw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qDCTVPoue1/DOAGNAlUstdA9Sid8MgEY4e5EzHcVHRk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9F9Mus0UnlzHb8E8ImxgXtz6SU98YXD0JqswOKw/Bzs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pctHpHKVBBcsahQ6TNh6/1V1ZrqOtKSAPtATV6BJqh0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vfR3C/4cPkVdxtNaqtF/v635ONbhTf5WbwJM6s4EXNE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ejP43xUBIex6szDcqExAFpx1IE/Ksi5ywJ84GKDFRrs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jbP4AWYd3S2f3ejmMG7dS5IbrFol48UUoT+ve3JLN6U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CiDifI7958sUjNqJUBQULeyF7x0Up3loPWvYKw9uAuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "e2dQFsiHqd2BFHNhlSxocjd+cPs4wkcUW/CnCz4KNuM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PJFckVmzBipqaEqsuP2mkjhJE4qhw36NhfQ9DcOHyEU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "S3MeuJhET/B8VcfZYDR9fvX0nscDj416jdDekhmK11s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CGVHZRXpuNtQviDB2Kj03Q8uvs4w3RwTgV847R7GwPw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yUGgmgyLrxbEpDVy89XN3c2cmFpZXWWmuJ/35zVZ+Jw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "inb6Q97mL1a9onfNTT8v9wsoi/fz7KXKq3p8j90AU9c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CCyYx/4npq9xGO1lsCo8ZJhFO9/tN7DB+/DTE778rYg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "LNnYw4fwbiAZu0kBdAHPEm/OFnreS+oArdB5O/l/I98=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P006SxmUS/RjiQJVYPdMFnNo3827GIEmSzagggkg05Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oyvwY+WsnYV6UHuPki1o0ILJ2jN4uyXf9yaUNtZJyBA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "36Lk3RHWh1wmtCWC/Yj6jNIo17U5y6SofAgQjzjVxD8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vOOo8FqeHnuO9mqOYjIb4vgwIwVyXZ5Y+bY5d9tGFUM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bJiDJjwQRNxqxlGjRm5lLziFhcfTDCnQ/qU1V85qcRg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2Qgrm1n0wUELAQnpkEiIHB856yv76q8jLbpiucetcm0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5ciPOYxTK0WDwwYyfs7yiVymwtYQXDELLxmM4JLl4/o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "31dC2WUSIOKQc4jwT6PikfeYTwi80mTlh7P31T5KNQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YluTV2Mu53EGCKLcWfHZb0BM/IPW2xJdG3vYlDMEsM4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dh/8lGo2Ek6KukSwutH6Q35iy8TgV0FN0SJqe0ZVHN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EVw6HpIs3BKen2qY2gz4y5dw1JpXilfh07msZfQqJpc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FYolLla9L8EZMROEdWetozroU40Dnmwwx2jIMrr7c1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "8M6k4QIutSIj6CM41vvkQtuFsaGrjoR9SZJVSLbfGKQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9LM0VoddDNHway442MqY+Z7vohB2UHau/cddshhzf40=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66i8Ytco4Yq/FMl6pIRZazz3CZlu8fO2OI6Pne0pvHU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2a/HgX+MjZxjXtSvHgF1yEpHMJBkl8Caee8XrJtn0WM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "frhBM662c4ZVG7mWP8K/HhRjd01lydW/cPcHnDjifqc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6k1T7Q1t668PBqv6fwpVnT1HWh7Am5LtbKvwPJKcpGU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UlJ5Edfusp8S/Pyhw6KTglIejmbr1HO0zUeHn/qFETA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jsxsB+1ECB3assUdoC333do9tYH+LglHmVSJHy4N8Hg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2nzIQxGYF7j3bGsIesECEOqhObKs/9ywknPHeJ3yges=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xJYKtuWrX90JrJVoYtnwP7Ce59XQGFYoalxpNfBXEH0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NLI5lriBTleGCELcHBtNnmnvwSRkHHaLOX4cKboMgTw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hUOQV0RmE5aJdJww1AR9rirJG4zOYPo+6cCkgn/BGvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "h4G2Of76AgxcUziBwCyH+ayMOpdBWzg4yFrTfehSC2c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VuamM75RzGfQpj2/Y1jSVuQLrhy6OAwlZxjuQLB/9Ss=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn9+hLq7hvw02xr9vrplOCDXKBTuFhfbX7d5v/l85Pg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fAiGqKyLZpGngBYFbtYUYt8LUrJ49vYafiboifTDjxs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BxRILymgfVJCczqjUIWXcfrfSgrrYkxTM5VTg0HkZLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CrFY/PzfPU2zsFkGLu/dI6mEeizZzCR+uYgjZBAHro0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "AEbrIuwvXLTtYgMjOqnGQ8y8axUn5Ukrn7UZRSyfQVw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ouWeVH3PEFg+dKWlXc6BmqirJOaVWjJbMzZbCsce4dA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+hd6xFB+EG+kVP7WH4uMd1CLaWMnt5xJRaY/Guuga9Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zmpGalfAOL3gmcUMJYcLYIRT/2VDO/1Dw4KdYZoNcng=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2PbHAoM/46J2UIZ/vyksKzmVVfxA7YUyIxWeL/N/vBk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7fD9x+zk5MVFesb59Klqiwwmve7P5ON/5COURXj5smE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tlrNQ4jaq051iaWonuv1sSrYhKkL1LtNZuHsvATha3s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fBodm28iClNpvlRyVq0dOdXQ08S7/N3aDwid+PdWvRo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "O+/nnRqT3Zv7yMMGug8GhKHaWy6u7BfRGtZoj0sdN1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5AZZ/RTMY4Photnm/cpXZr/HnFRi3eljacMsipkJLHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oFVyo/kgoMxBIk2VE52ySSimeyU+Gr0EfCwapXnTpKA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Z8v59DfcnviA0mzvnUk+URVO0UuqAWvtarEgJva/n1c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "P64GOntZ+zBJEHkigoh9FSxSO+rJTqR20z5aiGQ9an4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xMbSuDPfWuO/Dm7wuVl06GnzG9uzTlJJX9vFy7boGlY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kXPB19mRClxdH2UsHwlttS6lLU2uHvzuZgZz7kC45jU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NDVjVYXAw4k0w4tFzvs7QDq39aaU3HQor4I2XMKKnCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uKw/+ErVfpTO1dGUfd3T/eWfZW3nUxXCdBGdjvHtZ88=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "av0uxEzWkizYWm0QUM/MN1hLibnxPvCWJKwjOV4yVQY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ERwUC47dvgOBzIsEESMIioLYbFOxOe8PtJTnmDkKuHM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2gseKlG5Le12fS/vj4eaED4lturF16kAgJ1TpW3HxEE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7Cvg0Y3j/5i2F1TeXxlMmU7xwif5dCmwkZAOrVC5K2Y=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.json new file mode 100644 index 000000000..271f57b12 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.json @@ -0,0 +1,584 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DecimalPrecision. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.yml new file mode 100644 index 000000000..e6d153a08 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Aggregate.yml @@ -0,0 +1,317 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.json new file mode 100644 index 000000000..895444588 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.json @@ -0,0 +1,1650 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gte": { + "$numberDecimal": "0.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "1.0" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$lt": { + "$numberDecimal": "1.0" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$lte": { + "$numberDecimal": "1.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$lt": { + "$numberDecimal": "0.0" + } + } + } + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Find with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "200.0" + } + } + } + }, + "result": { + "errorContains": "must be less than the range max" + } + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0.0" + }, + "$lt": { + "$numberDecimal": "2.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gte": { + "$numberDecimal": "0.0" + }, + "$lte": { + "$numberDecimal": "200.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$in": [ + { + "$numberDecimal": "0.0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Insert out of range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "-1" + } + } + }, + "result": { + "errorContains": "value must be greater than or equal to the minimum value" + } + } + ] + }, + { + "description": "Insert min and max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 200, + "encryptedDecimalPrecision": { + "$numberDecimal": "200.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 200, + "encryptedDecimalPrecision": { + "$numberDecimal": "200.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gte": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "1.0" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$lt": { + "$numberDecimal": "1.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$lte": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$lt": { + "$numberDecimal": "0.0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Aggregate with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "200.0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be less than the range max" + } + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0.0" + }, + "$lt": { + "$numberDecimal": "2.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$gte": { + "$numberDecimal": "0.0" + }, + "$lte": { + "$numberDecimal": "200.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDecimalPrecision": { + "$in": [ + { + "$numberDecimal": "0.0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0.0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberInt": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gte": { + "$numberInt": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.yml new file mode 100644 index 000000000..cd1ced0b8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Correctness.yml @@ -0,0 +1,424 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1.0" } } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} + result: + errorContains: must be less than the range max + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDecimalPrecision: { $numberDecimal: "200.0" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gte: { $numberDecimal: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lte: { $numberDecimal: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $lt: { $numberDecimal: "0.0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "200.0" } }} } + result: + errorContains: must be less than the range max + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0.0" }, $lt: { $numberDecimal: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $numberDecimal: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $numberDecimal: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $gte: {$numberDecimal: "0.0"}, $lte: {$numberDecimal: "200.0"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDecimalPrecision: { $in: [ {$numberDecimal: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDecimalPrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.json new file mode 100644 index 000000000..7b3d5d822 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.json @@ -0,0 +1,476 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DecimalPrecision. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.yml new file mode 100644 index 000000000..af78be2dc --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Delete.yml @@ -0,0 +1,220 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDecimalPrecision": { $gt: {$numberDecimal: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.json new file mode 100644 index 000000000..af371f7b3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.json @@ -0,0 +1,588 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DecimalPrecision. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + }, + "update": { + "$set": { + "encryptedDecimalPrecision": { + "$numberDecimal": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.yml new file mode 100644 index 000000000..ac1de049f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-FindOneAndUpdate.yml @@ -0,0 +1,315 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDecimalPrecision: { $gt: {$numberDecimal: "0"}} } + update: { "$set": { "encryptedDecimalPrecision": {$numberDecimal: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDecimalPrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": { + "$numberInt": "0" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.json new file mode 100644 index 000000000..bbe81f87a --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.json @@ -0,0 +1,571 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DecimalPrecision. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "DRYKAAADcGF5bG9hZACiCQAABGcAjgkAAAMwAH0AAAAFZAAgAAAAAHdJ2Vnb4MMzqVYVssjSdDy8XU4GVzMTfGifGETgQ2mYBXMAIAAAAAD7cFfKJGIXo6PjyeX2ria02CckW7dWFDoY/3FyBdm1NQVsACAAAAAAhEPSNv4M023A3hzNFuy83+hIKuZ2mKRY954N++aEOBUAAzEAfQAAAAVkACAAAAAAlmvfDrZoydUet4eCVMq7z6a58Ea+1HLJOWxN5lNcrWEFcwAgAAAAAEBo5AWZyC41b9ayjWNQSL4iYEAIwR/JG+ssN8bdoK9RBWwAIAAAAACEndE0SLxFSElOrNnqeX0EPmgDio3udZjVREy4JLS3sQADMgB9AAAABWQAIAAAAABbiLaoxAA6rinMJw1hC8ZUiq6UU1AQaPFn/py/Y06WuQVzACAAAAAAhtDasFkvYE7SCNu1je/hxdE9TJtAvvH3NtdEbKzNbCUFbAAgAAAAAIGepU1RSCF8sWODHEpKglsoqw3VBBH4a/URGxgGzbq2AAMzAH0AAAAFZAAgAAAAALORWwSr+tYNxcil2KIGSbNhTHvcPbdj+rLVQNx21S/KBXMAIAAAAAD6diZBkPEJ1cQy06LAxdbNK8Nlxbb44fH4Wk3Y3260nQVsACAAAAAA1eYAZBFHlDiaDAljWi8blGQ2nvvZa5AO5doeo0SFZsgAAzQAfQAAAAVkACAAAAAAG5XMK96PjClNlUvg82j4pMY1YxsznZfj4uNweD394FoFcwAgAAAAAKHgQLdGJHkrfFg9nB93Ac+3VgBw6aU44MTkKIQ91dZoBWwAIAAAAAAPxXmi+SDJ+40A0KdwfRczexlZQrHjIA+D3oUB0EY9tAADNQB9AAAABWQAIAAAAAA6M++b9I0YFemmWBAWAE3glu2Ah3Ta1FBxAQEIWS0toAVzACAAAAAANXYTqPf1Y6X3Ns6YQIX0C3FKCyWUo+Kk+fNcQvc0WSoFbAAgAAAAAA+uJUw1ICYgyeygSRe206VTWVtUnhdci3iHbyP5YtEVAAM2AH0AAAAFZAAgAAAAAKl8bV1riH/uyJ+X0HHd3+18k2cJl2dQFXCdoagutFcaBXMAIAAAAABm8F2Ew9f0VOABdcF+lP0Bi+zWvEUPniWgrxPq/Sx3uwVsACAAAAAAJfFErjZ6BPhsw5LjJLqNtKDLJ4zV0eIZppQpd9b0wZoAAzcAfQAAAAVkACAAAAAAsYZD8JEP6kYsPncFnNZwJxhu4YtUTKPNcjHtv67H+rYFcwAgAAAAAI4LqZcRkvbs/2F62Flu0pixNcor4WmBD0DHGaf039wLBWwAIAAAAAD4wUR3xd9lKltcqqo8LYvdMQWzCRobkV/ppKB/yn5dUgADOAB9AAAABWQAIAAAAAC0vdAi+dmoIXvZ5LqUqvyKV9/tHqSI2SWiSJO5pTnA2wVzACAAAAAAS2qvf9fvfVUH5WtsVxjxmskpGjYTQV34LwvQQw1y9wIFbAAgAAAAAE0+FKuK7HxbypvCeEJzMTcjOWE0ScYOlTBMUNlIv55hAAM5AH0AAAAFZAAgAAAAAH31lb/srBcrOXkzddCwAnclsR5/3QijEVgECs2JjOWBBXMAIAAAAABg7+prDT73YcCvLE5QbuIrqGcjLc5pQD2Miq0d29yrxgVsACAAAAAAetRiPwDSFWBzpWSWkOKWM6fKStRJ8SyObnpc79ux8p0AAzEwAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzExAH0AAAAFZAAgAAAAAFdthRhe2Q8CvxGIhjTJZv0Lk97GkHciTPxZ/mckLoNaBXMAIAAAAAAqOxsAr23LOVB0DIHbPf9UDJJRFXY2YoKbjhRqw5psbQVsACAAAAAA0G2GD8ZQjDBntjLpW4rqwKRS6HiUjL03g1N6chANozcAAzEyAH0AAAAFZAAgAAAAAMWymwwbvIeMqmnKWWifUqoCxOsdpnonM2qdLPyjqJO/BXMAIAAAAAB6IDmmpUhBD2zpRj8/y/kmOSXcjuIU14sNh6GKSsg2uwVsACAAAAAAWMFPNOk3EMSQDS9JGPSMIQP0oNGVugxXKKUrIPPlhHgAAzEzAH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzE0AH0AAAAFZAAgAAAAAJaRYmo8zqI2BEUzdSwp4tVRpPmVWsfydkYN3UHh6TMuBXMAIAAAAAAeD6mDnQeLlbC9i0sVgE8+RH6y+e94OJQ0tJ0PvblVSgVsACAAAAAAWp4jvretbDEsqEMzP/WLTnwOiJwCtfrCiB6m8k+yEMoAAzE1AH0AAAAFZAAgAAAAAAZZ538coNPwyRjhEwr5P8Xw32oWOJF+R+nfCGgy2qO3BXMAIAAAAACOPLnJlKwGNPDBReRKnHfteq0wFb3ezhrc7BVXs8RUHwVsACAAAAAA+lGesNk3+SyB/60rSvdQ2aN2vfJPR7llJVhufGTNhHkAAzE2AH0AAAAFZAAgAAAAAFH9l9GGA1I52atJV5jNUf1lx8jBjoEoVoME97v5GFJiBXMAIAAAAAC1qH3Kd78Dr9NGbw7y9D/XYBwv5h1LLO8la5OU7g8UkQVsACAAAAAArZ6atJCYrVfHB8dSNPOFf6nnDADBMJcIEj8ljPvxHp8AAzE3AH0AAAAFZAAgAAAAADtbVEI2tdkrowEMdkacD2w0Y3T3Ofi7PH6HmA6sP0c/BXMAIAAAAADuBSROnZHA+NgUPH8d0LnWFiDsM2bY8bzjC1+elSsIygVsACAAAAAAR0G2m+uANoWknkr/NerFcG+fECVxNIs0cqbY1t/U/0MAAzE4AH0AAAAFZAAgAAAAAAh3WpeMVlikPFYj9hLj+fmIqVt6omCSF75W3TPExyWpBXMAIAAAAAAsQkRmwqeVj2gGE03orb6PtrIzDt6dDU3hgSQi8E2wKgVsACAAAAAA3GHaRE2RAcaBRd8VzmYzWeBD2Gmy91eTK1k8YdWObZcAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHBuAAIAAAAQdGYAAQAAABNtbgAAAAAAAAAAAAAAAAAAAD4wE214ANAHAAAAAAAAAAAAAAAAPjAA", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.yml new file mode 100644 index 000000000..ca04f412a --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-InsertFind.yml @@ -0,0 +1,307 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: find + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.json new file mode 100644 index 000000000..987bdf1aa --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.json @@ -0,0 +1,588 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DecimalPrecision. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDecimalPrecision": { + "$numberDecimal": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDecimalPrecision": { + "$numberDecimal": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedDecimalPrecision": { + "$gt": { + "$numberDecimal": "0" + } + } + }, + "update": { + "$set": { + "encryptedDecimalPrecision": { + "$numberDecimal": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalPrecision": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDecimalPrecision", + "bsonType": "decimal", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDecimal": "0.0" + }, + "max": { + "$numberDecimal": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDecimalPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.yml new file mode 100644 index 000000000..b8ca8df78 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DecimalPrecision-Update.yml @@ -0,0 +1,324 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDecimalPrecision', 'bsonType': 'decimal', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDecimal': '0.0'}, 'max': {'$numberDecimal': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DecimalPrecision. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDecimalPrecision: { $numberDecimal: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDecimalPrecision: { $numberDecimal: "1" } } + - name: updateOne + arguments: + filter: { encryptedDecimalPrecision: { $gt: { $numberDecimal: "0" } } } + update: { "$set": { "encryptedDecimalPrecision": { $numberDecimal: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDecimalPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDecimalPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDecimalPrecision": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDecimalPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.json new file mode 100644 index 000000000..c2a119cb7 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.json @@ -0,0 +1,381 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range applies defaults for trimFactor and sparsity", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + } + ] + }, + { + "_id": { + "$numberInt": "1" + }, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.yml new file mode 100644 index 000000000..91fb9eb77 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Defaults.yml @@ -0,0 +1,157 @@ +# Test "range" field with defaults for `trimFactor` and `sparsity`. +# Test requires libmongocrypt with changes in MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + topology: [ "replicaset", "sharded", "load-balanced" ] # Exclude "standalone". QE collections are not supported on standalone. +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + # Exclude `trimFactor` and `sparsity` + "contention": { "$numberLong": "0" }, + "min": { "$numberInt": "0" }, + "max": { "$numberInt": "200" } + } + } + ] +} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range applies defaults for trimFactor and sparsity" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: &encryptionInformation + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: *encryptionInformation + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: *encryptionInformation + command_name: find + outcome: + collection: + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + # Expected contents of `__safeContent__` require MONGOCRYPT-698 to apply expected `trimFactor`. + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + } + ] + } + - + { + "_id": { + "$numberInt": "1" + }, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.json new file mode 100644 index 000000000..daa7f4e97 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.json @@ -0,0 +1,1132 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Double. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.yml new file mode 100644 index 000000000..23453decf --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Aggregate.yml @@ -0,0 +1,901 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.json new file mode 100644 index 000000000..edb336743 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.json @@ -0,0 +1,1160 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gte": { + "$numberDouble": "0.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "1.0" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$lt": { + "$numberDouble": "1.0" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$lte": { + "$numberDouble": "1.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0.0" + }, + "$lt": { + "$numberDouble": "2.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$in": [ + { + "$numberDouble": "0.0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gte": { + "$numberDouble": "0.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "1.0" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$lt": { + "$numberDouble": "1.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$lte": { + "$numberDouble": "1.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0.0" + }, + "$lt": { + "$numberDouble": "2.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoubleNoPrecision": { + "$in": [ + { + "$numberDouble": "0.0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberInt": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gte": { + "$numberInt": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.yml new file mode 100644 index 000000000..54a116e5c --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Correctness.yml @@ -0,0 +1,292 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } + result: [*doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } + result: [*doc0] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $gte: { $numberDouble: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $lt: { $numberDouble: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $lte: { $numberDouble: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $numberDouble: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoubleNoPrecision: { $in: [ {$numberDouble: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDoubleNoPrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.json new file mode 100644 index 000000000..4a9c1f27b --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.json @@ -0,0 +1,732 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Double. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.yml new file mode 100644 index 000000000..ff71e8f1d --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Delete.yml @@ -0,0 +1,512 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDoubleNoPrecision": { $gt: {$numberDouble: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.json new file mode 100644 index 000000000..d7860de83 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.json @@ -0,0 +1,1136 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Double. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0" + } + } + }, + "update": { + "$set": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.yml new file mode 100644 index 000000000..48f018e46 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-FindOneAndUpdate.yml @@ -0,0 +1,899 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDoubleNoPrecision": {$numberDouble: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDoubleNoPrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.json new file mode 100644 index 000000000..934af381f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.json @@ -0,0 +1,1123 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Double. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.yml new file mode 100644 index 000000000..9216cf7b4 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-InsertFind.yml @@ -0,0 +1,895 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: find + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "2FIZh/9N+NeJEQwxYIX5ikQT85xJzulBNReXk8PnG/s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I93Md7QNPGmEEGYU1+VVCqBPBEvXdqHPtTJtMOn06Yk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "GecBFQ1PemlECWZWCl7f74vmsL6eB6mzQ9n6tK6FYfs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QpjhZl+O1ORifgtCZuWAdcP6OKL7IZ2cA46v8FJcV28=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FWXI/yZ1M+2fIboeMCDMlp+I2NwPQDtoM/wWselOPYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uk26nvN/LdRLaBphiBgIZzT0sSpoO1z0RdDWRm/xrSA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hiiYSH1KZovAULc7rlmEU74wCjzDR+mm6ZnsgvFQjMw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hRzvMvWPX0sJme+wck67lwbKDFaWOa+Eyef+JSdc1s4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PSx5D+zqC9c295dguX4+EobT4IEzfffdfjzC8DWpB5Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QzfXQCVTjPQv2h21v95HYPq8uCsVJ2tPnjv79gAaM9M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XcGDO/dlTcEMLqwcm55UmOqK+KpBmbzZO1LIzX7GPaQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Lf+o4E7YB5ynzUPC6KTyW0lj6Cg9oLIu1Sdd1ODHctA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wAuVn02LAVo5Y+TUocvkoenFYWzpu38k0NmGZOsAjS4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yJGDtveLbbo/0HtCtiTSsvVI/0agg/U1bFaQ0yhK12o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KsEy0zgYcmkM+O/fWF9z3aJGIk22XCk+Aw96HB6JU68=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "p+AnMI5ZxdJMSIEJmXXya+FeH5yubmOdViwUO89j0Rc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/jLix56jzeywBtNuGw55lCXyebQoSIhbful0hOKxKDY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fvDvSPomtJsl1S3+8/tzFCE8scHIdJY5hB9CdTEsoFo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "oV5hOJzPXxfTuRdKIlF4uYEoMDuqH+G7/3qgndDr0PM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3ALwcvLj3VOfgD6OqXAO13h1ZkOv46R6+Oy6SUKh53I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gxaB9FJj0IM+InhvAjwWaex3UIZ9SAnDiUd5WHSY/l0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "66NPvDygJzKJqddfNuDuNOpvGajjFRtvhkwfUkiYmXw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1dWcQIocRAcO9XnXYqbhl83jc0RgjQpsrWd8dC27trg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "npos0Uf1DT3ztSCjPVY9EImlRnTHB1KLrvmVSqBQ/8E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "TEI9qBx/tK1l1H0v1scMG8Srmtwo5VxWHADPBSlWrXk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3wUN2ypQKoj+5ASkeIK9ycxhahVxyTmGopigoUAlyYs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o/oksSnUS+nIq6ozWTbB5bJh+NoaPj8deAA23uxiWCk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KExYPruhA31e8xuSwvfUfDcyY/H2Va6taUd0k4yFgLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "/x+dNfxdd/lkx8Z8VZVfoYl7LPoaZ/iKEzZXBrAtIJc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DE4cmjFLPqZlmRomO0qQiruUBtzoCe8ZdNRcfNH92pU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M6EKNcLPw/iojAChgYUSieaBYWcbsjKtB94SaHOr8vk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+qP49lDPeyhaduTvXJgtJEqHNEYANVu9Bg3Bxz7Td9w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ruMrC2VIS+VKbJwCFb3bfkaLTju9nE+yPONV9s0M0Vo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EbjDlSB5JKnDKff4d8hOmaOwJ7B9Q6NQFisLj+DPC+0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C/yYOTB94edyqAbiQNu8/H7FoG3yRRjHDkMykz4+Mv0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CBxqrejG+qQQq2YTd6iP/06kiu2CxxzBFaZK3Ofb1CM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2ZOQ/fpho+AbDENWBZaln7wRoepIRdhyT648dr8O5cU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "EghIgEPz01+myPgj8oid+PgncvobvC7vjvG3THEEQ0M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "92CysZYNF8riwAMhdrIPKxfODw9p07cKQy/Snn8XmVY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VO0LeTBQmsEf7sCHzTnZwUPNTqRZ49R8V5E9XnZ/5N4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "exs8BQMJq7U6ZXYgIizT7XN+X/hOmmn4YEuzev9zgSI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qHpS4k1I+gPniNp4CA8TY8lLN36vBYmgbKMFpbYMEqg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+7lWKCKAWFw6gPZdHE6E8KIfI14/fSvtWUmllb5WLi0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YiH/US0q6679hWblFDDKNqUjCgggoU8sUCssTIF1QbU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YgwkKElEubNfvXL9hJxzqQUQtHiXN/OCGxNL1MUZZlM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hZFST4INZTTuhvJlGJeMwlUAK270UCOTCDeBAnN4a7g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "24I1Zw35AuGnK3CqJhbCwYb0IPuu5sCRrM5iyeITOLc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vgD12JB4Q1S/kGPSQ1KOgp386KnG1GbM/5+60oRGcGw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+wNE+OL+CB9d4AUJdVxd56jUJCAXmmk9fapuB2TAc4g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uhQh1B2Pe4RkNw/kPEcgaLenuikKoRf1iyfZhpXdodc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eu8gjAUIp8ybO204AgeOq5v1neI1yljqy5v3I6lo1lM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7QG6oVbASBAjrnCPxzzUNnuFSFNlKhbuBafkF8pr7Is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "PUS1xb2oHSDTdYltutoSSxBiJ1NjxH3l2kA4P1CZLEs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XPMh/JDC/O93gJJCwwgJDb8ssWZvRvezNmKmyn3nIfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jWz+KGwMk/GOvFAK2rOxF3OjxeZAWfmUQ1HGJ7icw4A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o7XbW68pc6flYigf3LW4WAGUWxpeqxaQLkHUhUR9RZ8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nqR+g60+5U0okbqJadSqGgnC+j1JcP8rwMcfzOs2ACI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Hz43qVK95tSfbYFtaE/8fE97XMk1RiO8XpWjwZHB80o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "noZUWlZ8M6KXU5rkifyo8/duw5IL7/fXbJvT7bNmW9k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WONVHCuPSanXDRQQ/3tmyJ0Vq+Lu/4hRaMUf0g0kSuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UEaj6vQRoIghE8Movd8AGXhtwIOXlP4cBsECIUvE5Y8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "D3n2YcO8+PB4C8brDo7kxKjF9Y844rVkdRMLTgsQkrw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "C+YA0G9KjxZVaWwOMuh/dcnHnHAlYnbFrRl0IEpmsY0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rUnmbmQanxrbFPYYrwyQ53x66OSt27yAvF+s48ezKDc=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.json new file mode 100644 index 000000000..ec95e0334 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.json @@ -0,0 +1,1140 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Double. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$numberDouble": "0" + } + } + }, + "update": { + "$set": { + "encryptedDoubleNoPrecision": { + "$numberDouble": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoubleNoPrecision": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoubleNoPrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoubleNoPrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.yml new file mode 100644 index 000000000..8a340ee6e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Double-Update.yml @@ -0,0 +1,912 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoubleNoPrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Double. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoubleNoPrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoubleNoPrecision: { $numberDouble: "1" } } + - name: updateOne + arguments: + filter: { encryptedDoubleNoPrecision: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDoubleNoPrecision": { $numberDouble: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoubleNoPrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDoubleNoPrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoubleNoPrecision": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "6YrBn2ofIw1b5ooakrLOwF41BWrps8OO0H9WH4/rtlE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "n+XAuFnP8Dov9TnhGFxNx0K/MnVM9WbJ7RouEu0ndO0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yRXojuVdn5GQtD97qYlaCL6cOLmZ7Cvcb3wFjkLUIdM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DuIkdRPITRs55I4SZmgomAHCIsDQmXRhW8+MOznkzSk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SsBk+Et1lTbU+QRPx+xyJ/jMkmfG+QCvQEpip2YYrzA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "crCIzOd8KhHvvUlX7M1v9bhvU4pLdTc+X2SuqoKU5Ek=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "YOWdCw4UrqnxkAaVjqmC4sKQDMVMHEpFGnlxpxdaU6E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "M3SShp81Ff8tQ632qKbv9MUcN6wjDaBReI0VXNu6Xh4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gzHlSPxpM0hT75kQvWFzGlOxKvDoiKQZOr19V6l2zXI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "s3JnppOGYw9SL2Q1kMAZs948v2F5PrpXjGei/HioDWs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cG6+3Gk/zEH68P/uuuwiAUVCuyJwa1LeV+t29FlPPAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dupdvR3AyJtM+g9NDKiaLVOtGca387JQp8w+V03m7Ig=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JqEQc5svj2jTvZ6LLA5ivE+kTb/0aRemSEmxk4G7Zrg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "szcXXXKnob+p3SoM4yED2R920LeJ7cVsclPMFTe4CeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "o1QoGVXmuBdHwHm7aCtGMlMVKrjFdYvJXpoq6uhIAZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Jfm5wPlqqLCJRGQIqRq2NGmpn7s0Vrih2H3YAOoI2YU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zMHLb8ARbsYo8Ld05bqnGFf1Usha6EGb8QKwdSAyps0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yQdtq9lh5pugL7/i0Bj/PuZUUBUIzf+7wj1rl5y736w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wGWVZdO7qIuyDg/BqDgqjgoQ02h5YYgwXQB1oCin2NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "by9HMLj6NTEpgztZ5HSN6GxImkXPcaFINYDzgZY33X8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tWo0vbasi7bXmn/MsOx13VC1IsWtpx/nYp0uj4iMzdA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tQQpndUYd5O87lOtrGjH3wl9VsOK0ray7RMasL90sBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cQjXEDCMsOpKLLf+vlTgIHA+cbSJdzqhbSX9Wvh95aA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7yMpU48IxK9SzP2cx3VnTownGEwFmeFofuuFT97SuuY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kSOx1kz0CmBgzKQHZlo65ZUY1DIv9A99JRm+Us2y6Ew=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ubQpdPBe6/xvtr+AcXdfYLSvYCR4ot0tivehkCsupb4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xal+iCJ6FTefRQToyoNksc9NCZShyn04NDGi4IYrcoM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d7jU4iOK50xHxlkSifcxlZFCM46TSgQzoYivxG3HNLY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tJvl2nsBLBVzL3pp6sKWCL4UXeh3q/roYBJjSb74ve0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OIUCaKRvIx9t1w6Hxlz1IcQTdPNCfdRNwnnTm10W+X0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A9tvzsiElotOUVIB4CqfQp9mAwqvTM35YkmAR170aHA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lI8gpK7hpb7c9x4RQugsxMnQay5LZJmwslZdvMx/dcE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dNCzh40U0XvdKnSDi3HRQOWQftEsDVqc4uUvsVFGoq8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "IP+iwEBWBwVVZIdpaMu8k5+soFCz+TZkYn3drKZ9grE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pnqyh6e0y5svHkJDShlN9CHV0WvMBE4QbtJpQw5ZCXc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "elEl42tbVDoRTLjAhZUFEtXiut4b3PVhg/1ZLZSQdtE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vHuu2FxwclMHqyE6JBYbTYgbEkB0dqb/JuaxsvfwsmY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xTf7NCe3Gf8QpE78HR5OknlLTKfs9J+RN9UZpH6fnso=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XiWSasRnJAulGR6+LCVD3mwRObXylqYWR9jvpywq12c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MZMxEQ5ikx0PG1YFIExv0UnTZogsvgeOEZTpzvBDn4w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yZMyMZBDrWbAhvnic7vvIYhmO9m5H2iuv0c8KNZrBzY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xxM14hTPY5j0vvcK2C7YAEjzdsfUTFHozHC0hEo1bxI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+01rqR1xVwkpGXcstbk1ItJqFVjH6Q8MGxEN3Cm9Y1A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xOpLV0Z2VTRJ3iWtnWZcsyjXubTIkYWo31cO+HV1o1k=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BWUOLqgLBqc5NwxVlSV5H3KFQPXbCp7mdo+jF+8cJqY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "fuQb1S6xZDGlrEbK+kI23aL53PP1PVNwqICnZNt9Yzg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfscnoibFttahLdPVC4Ee+47ewGFKpDSU7M6HX19bKE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rpSW2awybNVeKtat91VFxqbINoTfNhPfQAu+d73Xtf8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "9M/CP9ccOIIj2LLFmE0GFDO0Ban2wsNalEXfM6+h+1s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WrEMG49l1ye4MhXs5ZS9tz8P6h+hDvthIg/2wW9ne1Q=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ImNhbfeyfH8qIEeA5ic0s3dAQBdzzTBS+CPsNih9vZ0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dWP33YDSn04UKJN2ogh2Rui0iW/0q2y18OCDRVcfyoo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "lYv0isAtfGh6H9tdp3cp2eHU7q2J+uk7QrgcxtK3w7Y=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "VGMoamB/+7zTOYcY/pqJc96xlv2PdW4hwsIAEIslTDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yNeBWMF7BnD9wVwz2PgJsvWr77QiVvvWUvJF0+fqBug=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SfpvObJ+tJBXSvqeN7vlOfmhYign635lciYAJIjUtY8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dsen4NqjzVGjpjufiTMs3+gqeD09EbnuogPgxrJECwg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "pxCWVM3sn19NsFEpgHbgLa+PmYlhN3mMiP0Wk8kJhYw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q11KNvJszjYIB9n9HcC+N4uz11a3eRj1L3BH9scKMDQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "A1PmkgcEToWh1JiVWE6mI5jUu7poxWWuCUt/cgRUUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "qJo3Hu4PJeanL7XEaWXO/n3YsodhZyd+MJOOmB9Kpd8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "BkBKLO8URFscfRY9Bav/1+L9mLohDgNr/MkZtGiraIs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "rZq5WA3Hx3xthOyHAJXK//f8pE2qbz7YKu3TIMp9GFY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X07a/Lm80p5xd4RFs1dNmw+90tmPDPdGiAKVZkxd4zY=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoubleNoPrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "HI88j1zrIsFoijIXKybr9mYubNV5uVeODyLHFH4Ueco=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wXVD/HSbBljko0jJcaxJ1nrzs2+pchLQqYR3vywS8SU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "KhscCh+tt/pp8lxtKZQSPPUU94RvJYPKG/sjtzIa4Ws=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RISnuNrTTVNW5HnwCgQJ301pFw8DOcYrAMQIwVwjOkI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Ra5zukLh2boua0Bh74qA+mtIoixGXlsNsxiJqHtqdTI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "eqr0v+NNWXWszi9ni8qH58Q6gw5x737tJvH3lPaNHO4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "d42QupriWIwGrFAquXNFi0ehEuidIbHLFZtg1Sm2nN8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "2azRVxaaTIJKcgY2FU012gcyP8Y05cRDpfUaMnCBaQU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "3nlgkM4K/AAcHesRYYdEu24UGetHodVnVfHzw4yxZBM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "hqy91FNmAAac2zUaPO6eWFkx0/37rOWGrwXN+fzL0tU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "akX+fmscSDSF9pB5MPj56iaJPtohr0hfXNk/OPWsGv8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1ZvUb10Q7cN4cNLktd5yNjqgtawsYnkbeVBZV6WuY/I=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "otCwtuKiY4hCyXvYzXvo10OcnzZppebo38KsAlq49QM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Mty8EscckeT/dhMfrPFyDbLnmMOcYRUQ3mLK4KTu6V8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "tnvgLLkJINO7csREYu4dEVe1ICrBeu7OP+HdfoX3M2E=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kOefsHgEVhkJ17UuP7Dxogy6sAQbzf1SFPKCj6XRlrQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F+JQ79xavpaHdJzdhvwyHbzdZJLNHAymc/+67La3gao=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "NCZ9zp5rDRceENuSgAfTLEyKg0YgmXAhK0B8WSj7+Pw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wL1CJ7cYR5slx8mHq++uMdjDfkt9037lQTUztEMF56M=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "txefkzTMITZE+XvvRFZ7QcgwDT/7m8jNmxRk4QBaoZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jFunW3v1tSYMyZtQQD28eEy9qqDp4Kqo7gMN29N4bfQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QMO915KUiS3X3R1bU1YoafVM2s0NeHo3EjgTA9PnGwY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "nwdKJEXdilzvb7494vbuDJ+y6SrfJahza1dYIsHIWVI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vpWMX+T/VXXajFo0UbuYjtp0AEzBU0Y+lP+ih2EQ7mg=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1lmzG0J1DhKDRhhq5y5Buygu4G8eV2X0t7kUY90EohM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SiKqpXqO0trwhFvBWK274hMklpCgMhNs/JY84yyn/NE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7cPGPYCKPTay+ZR9Gx6oOueduOgaFrSuAXmNDpDHXdI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4THEYvAkjs2Fh7FIe5LC45P4i4N0L7ob67UOVbhp6Nk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "B+UGsChLLZR7iqnt8yq91OgmTgwiUKTJhFxY4NT0O6c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X1uYwBCsCg1H+PnKdwtBqXlt0zKEURi8bOM940GcPfk=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xYOgT5l7shlNXCwHlguovmDkcEnF8dXyYlTyYrgZ8GE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "vFMTZqV8bh1+gcKzTkXweMddJlgdUnwX0DWzUUaMok4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4HI0y9FrtleZxZ7M6INdNhLelrQ2Rv/+ykWCBl+tMC8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpJ0bBE474OUkn1vUiLWumIBtYmwc7J5+LQU/nyeLQc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jQTPeXZvdxY/DjtPfYfKUArIDsf0E9MVFy2O26sv1ec=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "QLLto0ExR2ZYMGqlyaMZc/hXFFTlwmgtKbiVq/xJIeI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yBJNviU1nchbGbhx6InXCVRXa90sEepz1EwbYuKXu2U=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jpEf0vHxrPu9gTJutNXSi2g/2Mc4WXFEN7yHonZEb7A=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "E09kLFckMYwNuhggMxmPtwndyvIAx+Vl+b2CV6FP75s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "N+ue6/cLPb5NssmJCCeo18LlbKPz6r2z20AsnTKRvOo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "yVQNZP8hhsvNGyDph2QP2qTNdXZTiIEVineKg+Qf33o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cSC9uI+9c5S8X+0G7amVyug1p0ZlgBsbEDYYyezBevQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1NpZGjoQzuQtekj80Rifxe9HbE08W07dfwxaFHaVn84=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "5Ghuq/8l11Ug9Uf/RTwf9On3OxOwIXUcb9soiy4J7/w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0LWKaEty6ywxLFhDaAqulqfMnYc+tgPfH4apyEeKg80=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "OwSthmCBtt6NIAoAh7aCbj82Yr/+9t8U7WuBQhFT3AQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "iYiyg6/1isqbMdvFPIGucu3cNM4NAZNtJhHpGZ4eM+c=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "waBgs8jWuGJPIF5zCRh6OmIyfK5GCBQgTMfmKSR2wyY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "1Jdtbe2BKJXPU2G9ywOrlODZ/cNYEQlKzAW3aMe1Hy4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xaLEnNUS/2ySerBpb9dN/D31t+wYcKekwTfkwtni0Mc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bIVBrOhOvr6cL55Tr24+B+CC9MiG7U6K54aAr2IXXuw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6Cdq5wroGu2TEFnekuT7LhOpd/K/+PcipIljcHU9QL4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "K5l64vI4S/pLviLW6Pl0U3iQkI3ge0xg4RAHcEsyKJo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "bzhuvZ0Ls22yIOX+Hz51eAHlSuDbWR/e0u4EhfdpHbc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Qv+fr6uD4o0bZRp69QJCFL6zvn3G82c7L+N1IFzj7H0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "XAmISMbD3aEyQT+BQEphCKFNa0F0GDKFuhM9cGceKoQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4VLCokntMfm1AogpUnYGvhV7nllWSo3mS3hVESMy+hA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "xiXNLj/CipEH63Vb5cidi8q9X47EF4f3HtJSOH7mfM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "4XlCYfYBjI9XA5zOSgTiEBYcZsdwyXL+f5XtH2xUIOc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "k6DfQy7ZYJIkEly2B5hjOZznL4NcgMkllZjJLb7yq7w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ZzM6gwWesa3lxbZVZthpPFs2s3GV0RZREE2zOMhBRBo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "US+jeMeeOd7J0wR0efJtq2/18lcO8YFvhT4O3DeaonQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b6iSxiI1FM9SzxuG1bHqGA1i4+3GOi0/SPW00XB4L7o=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kn3LsxAVkzIZKK9I6fi0Cctr0yjXOYgaQWMCoj4hLpM=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.json new file mode 100644 index 000000000..e8a50ebec --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.json @@ -0,0 +1,580 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DoublePrecision. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.yml new file mode 100644 index 000000000..87475991a --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Aggregate.yml @@ -0,0 +1,313 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.json new file mode 100644 index 000000000..87d0e3dd8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.json @@ -0,0 +1,1650 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gte": { + "$numberDouble": "0.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "1.0" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$lt": { + "$numberDouble": "1.0" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$lte": { + "$numberDouble": "1.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$lt": { + "$numberDouble": "0.0" + } + } + } + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Find with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "200.0" + } + } + } + }, + "result": { + "errorContains": "must be less than the range max" + } + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0.0" + }, + "$lt": { + "$numberDouble": "2.0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gte": { + "$numberDouble": "0.0" + }, + "$lte": { + "$numberDouble": "200.0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$in": [ + { + "$numberDouble": "0.0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Insert out of range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "-1" + } + } + }, + "result": { + "errorContains": "value must be greater than or equal to the minimum value" + } + } + ] + }, + { + "description": "Insert min and max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 200, + "encryptedDoublePrecision": { + "$numberDouble": "200.0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 200, + "encryptedDoublePrecision": { + "$numberDouble": "200.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gte": { + "$numberDouble": "0.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "1.0" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$lt": { + "$numberDouble": "1.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$lte": { + "$numberDouble": "1.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$lt": { + "$numberDouble": "0.0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Aggregate with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "200.0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be less than the range max" + } + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0.0" + }, + "$lt": { + "$numberDouble": "2.0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$gte": { + "$numberDouble": "0.0" + }, + "$lte": { + "$numberDouble": "200.0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1.0" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedDoublePrecision": { + "$in": [ + { + "$numberDouble": "0.0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0.0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberInt": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Int", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gte": { + "$numberInt": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.yml new file mode 100644 index 000000000..9c7a8d22f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Correctness.yml @@ -0,0 +1,424 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0.0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1.0" } } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} + result: + errorContains: must be less than the range max + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $numberDouble: "0.0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedDoublePrecision: { $numberDouble: "1.0" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedDoublePrecision: { $numberDouble: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedDoublePrecision: { $numberDouble: "200.0" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gte: { $numberDouble: "0.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "1.0" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "1.0" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lte: { $numberDouble: "1.0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $lt: { $numberDouble: "0.0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "200.0" } }} } + result: + errorContains: must be less than the range max + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gt: { $numberDouble: "0.0" }, $lt: { $numberDouble: "2.0"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $numberDouble: "0.0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $numberDouble: "1.0" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $gte: {$numberDouble: "0.0"}, $lte: {$numberDouble: "200.0"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedDoublePrecision: { $in: [ {$numberDouble: "0.0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedDoublePrecision: { $numberInt: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Int" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.json new file mode 100644 index 000000000..8a0fecf78 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.json @@ -0,0 +1,474 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DoublePrecision. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.yml new file mode 100644 index 000000000..09952c2f3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Delete.yml @@ -0,0 +1,218 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedDoublePrecision": { $gt: {$numberDouble: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.json new file mode 100644 index 000000000..ac77931d6 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.json @@ -0,0 +1,584 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DoublePrecision. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0" + } + } + }, + "update": { + "$set": { + "encryptedDoublePrecision": { + "$numberDouble": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.yml new file mode 100644 index 000000000..e276b54e4 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-FindOneAndUpdate.yml @@ -0,0 +1,311 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedDoublePrecision: { $gt: {$numberDouble: "0"}} } + update: { "$set": { "encryptedDoublePrecision": {$numberDouble: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedDoublePrecision": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.json new file mode 100644 index 000000000..5dcc09dca --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.json @@ -0,0 +1,571 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DoublePrecision. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.yml new file mode 100644 index 000000000..ed5225a65 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-InsertFind.yml @@ -0,0 +1,307 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: find + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "mVZb+Ra0EYjQ4Zrh9X//E2T8MRj7NMqm5GUJXhRrBEI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "MgwakFvPyBlwqFTbhWUF79URJQWFoJTGotlEVSPPUsQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "DyBERpMSD5lEM5Nhpcn4WGgxgn/mkUVJp+PYSLX5jsE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "I43iazc0xj1WVbYB/V+uTL/tughN1bBlxh1iypBnNsA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wjOBa/ATMuOywFmuPgC0GF/oeLqu0Z7eK5udzkTPbis=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "gRQVwiR+m+0Vg8ZDXqrQQcVnTyobwCXNaA4BCJVXtMc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "WUZ6huwx0ZbLb0R00uiC9FOJzsUocUN8qE5+YRenkvQ=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "7s79aKEuPgQcS/YPOOVcYNZvHIo7FFsWtFCrnDKXefA=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.json new file mode 100644 index 000000000..483e3d52e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.json @@ -0,0 +1,588 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range DoublePrecision. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedDoublePrecision": { + "$numberDouble": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedDoublePrecision": { + "$numberDouble": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedDoublePrecision": { + "$gt": { + "$numberDouble": "0" + } + } + }, + "update": { + "$set": { + "encryptedDoublePrecision": { + "$numberDouble": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoublePrecision": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedDoublePrecision", + "bsonType": "double", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberDouble": "0.0" + }, + "max": { + "$numberDouble": "200.0" + }, + "precision": { + "$numberInt": "2" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedDoublePrecision": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.yml new file mode 100644 index 000000000..7ed6f16bf --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-DoublePrecision-Update.yml @@ -0,0 +1,326 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedDoublePrecision', 'bsonType': 'double', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberDouble': '0.0'}, 'max': {'$numberDouble': '200.0'}, 'precision': {'$numberInt': '2'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range DoublePrecision. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedDoublePrecision: { $numberDouble: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedDoublePrecision: { $numberDouble: "1" } } + - name: updateOne + arguments: + filter: { encryptedDoublePrecision: { $gt: { $numberDouble: "0" } } } + update: { "$set": { "encryptedDoublePrecision": { $numberDouble: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedDoublePrecision": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedDoublePrecision": { + "$gt": { + "$binary": { + "base64": "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", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedDoublePrecision": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "Dri0CXmL78L2DOgk9w0DwxHOMGMzih7m6l59vgy+WWo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "b7d8mRzD1kI1tdc7uNL+YAUonJ6pODLsRLkArfEKSkM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "Xg8C1/A0KJaXOw4i+26Rv03/CydaaunOzXh0CIT+gn8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "UoKUDw2wJYToUCcFaIs03YQSTksYR0MIOTJllwODqKc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "c/5cwAT0C5jber2xlJnWD3a5tVDy0nRtr5HG02hoFOY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wSUrRXavAGaajNeqC5mEUH1K67oYl5Wy9RNIzKjwLAM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6vrp4wWDtHEgHWR99I70WVDzevg1Fk/Pw5U8gUDa0OU=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedDoublePrecision": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "V6knyt7Zq2CG3++l75UtBx2m32iGAPjHiAe439Bf02w=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "0OKSXELxPP85SBVwDGf3LtMEQCJ8TTkFUl/+6jlkdb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uEw0lpQtBppR3vqV9j9+NQRSBF1BzZukb8c9IhyWvxc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zVhZ7Q59O087ji49oMJvBIgeir2oqvUpnh4p53GcTow=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "dowrzKs+qJhRMZyKDbhjXbuX43FbmUKOaw9I8YlOZDw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ep5B6cska6THLIF7Mn3tn3RvV9EiwLSt0eZM/CLRUDc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "URNp/YmmDh5wIZUfAzzgPyJeMNiVx9PMsz52DZRujGY=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "wlM4IAQhhKQEzoVqS8b1Ddd50GB95OFb9LnzOwyjCP4=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.json new file mode 100644 index 000000000..6cd837c78 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.json @@ -0,0 +1,484 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Int. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.yml new file mode 100644 index 000000000..f677bdedf --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Aggregate.yml @@ -0,0 +1,229 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedInt": { $gt: {$numberInt: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.json new file mode 100644 index 000000000..9dc4e4e50 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.json @@ -0,0 +1,1644 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gte": { + "$numberInt": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "1" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$lt": { + "$numberInt": "1" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$lte": { + "$numberInt": "1" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$lt": { + "$numberInt": "0" + } + } + } + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Find with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "200" + } + } + } + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + }, + "$lt": { + "$numberInt": "2" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$numberInt": "0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$numberInt": "1" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gte": { + "$numberInt": "0" + }, + "$lte": { + "$numberInt": "200" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$in": [ + { + "$numberInt": "0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + } + ] + }, + { + "description": "Insert out of range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "-1" + } + } + }, + "result": { + "errorContains": "value must be greater than or equal to the minimum value" + } + } + ] + }, + { + "description": "Insert min and max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 200, + "encryptedInt": { + "$numberInt": "200" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 200, + "encryptedInt": { + "$numberInt": "200" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gte": { + "$numberInt": "0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$numberInt": "1" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$lt": { + "$numberInt": "1" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$lte": { + "$numberInt": "1" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$lt": { + "$numberInt": "0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Aggregate with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$numberInt": "200" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + }, + "$lt": { + "$numberInt": "2" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$numberInt": "0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$numberInt": "1" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$gte": { + "$numberInt": "0" + }, + "$lte": { + "$numberInt": "200" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + }, + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedInt": { + "$in": [ + { + "$numberInt": "0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberDouble": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gte": { + "$numberDouble": "0" + } + } + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.yml new file mode 100644 index 000000000..9cb1cb368 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Correctness.yml @@ -0,0 +1,423 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberInt: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "1" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lt: { $numberInt: "1" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lte: { $numberInt: "1" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $lt: { $numberInt: "0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "200" } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $numberInt: "0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedInt: { $numberInt: "1" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedInt: { $numberInt: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedInt: { $numberInt: "200" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gte: { $numberInt: "0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "1" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lt: { $numberInt: "1" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lte: { $numberInt: "1" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $lt: { $numberInt: "0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "200" } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gt: { $numberInt: "0" }, $lt: { $numberInt: "2"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $numberInt: "0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $numberInt: "1" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $gte: {$numberInt: "0"}, $lte: {$numberInt: "200"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedInt: { $in: [ {$numberInt: "0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.json new file mode 100644 index 000000000..b251db915 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.json @@ -0,0 +1,420 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Int. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.yml new file mode 100644 index 000000000..158358d88 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Delete.yml @@ -0,0 +1,176 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedInt": { $gt: {$numberInt: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.json new file mode 100644 index 000000000..6e09b5ea2 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.json @@ -0,0 +1,488 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Int. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + }, + "update": { + "$set": { + "encryptedInt": { + "$numberInt": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedInt": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.yml new file mode 100644 index 000000000..6b7fe8f02 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-FindOneAndUpdate.yml @@ -0,0 +1,227 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedInt: { $gt: {$numberInt: "0"}} } + update: { "$set": { "encryptedInt": {$numberInt: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedInt": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.json new file mode 100644 index 000000000..cbab7e769 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.json @@ -0,0 +1,475 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Int. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.yml new file mode 100644 index 000000000..ecfc72634 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-InsertFind.yml @@ -0,0 +1,223 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: find + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.json new file mode 100644 index 000000000..cb6b22394 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.json @@ -0,0 +1,492 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Int. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": { + "$numberInt": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedInt": { + "$gt": { + "$numberInt": "0" + } + } + }, + "update": { + "$set": { + "encryptedInt": { + "$numberInt": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedInt": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.yml new file mode 100644 index 000000000..6b81bf563 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Int-Update.yml @@ -0,0 +1,242 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Int. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedInt: { $numberInt: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedInt: { $numberInt: "1" } } + - name: updateOne + arguments: + filter: { encryptedInt: { $gt: { $numberInt: "0" } } } + update: { "$set": { "encryptedInt": { $numberInt: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedInt": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedInt": { + "$gt": { + "$binary": { + "base64": "DW0FAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAAQbW4AAAAAABBteADIAAAAAA==", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedInt": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedInt": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.json new file mode 100644 index 000000000..5c4bf1010 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.json @@ -0,0 +1,484 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Long. Aggregate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "aggregate": "default", + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + } + ], + "cursor": {}, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "aggregate" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.yml new file mode 100644 index 000000000..04b315bad --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Aggregate.yml @@ -0,0 +1,229 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Aggregate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: aggregate + arguments: + pipeline: [{ $match: { "encryptedLong": { $gt: {$numberLong: "0" }} } }] + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + aggregate: *collection_name + pipeline: [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + } + ] + cursor: {} + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: aggregate + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.json new file mode 100644 index 000000000..d81e0933f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.json @@ -0,0 +1,1644 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Find with $gt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gte": { + "$numberLong": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "1" + } + } + } + }, + "result": [] + } + ] + }, + { + "description": "Find with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$lt": { + "$numberLong": "1" + } + } + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + } + ] + }, + { + "description": "Find with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$lte": { + "$numberLong": "1" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$lt": { + "$numberLong": "0" + } + } + } + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Find with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "200" + } + } + } + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Find with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + }, + "$lt": { + "$numberLong": "2" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$numberLong": "0" + } + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$numberLong": "1" + } + } + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gte": { + "$numberLong": "0" + }, + "$lte": { + "$numberLong": "200" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Find with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$in": [ + { + "$numberLong": "0" + } + ] + } + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + } + ] + }, + { + "description": "Insert out of range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "-1" + } + } + }, + "result": { + "errorContains": "value must be greater than or equal to the minimum value" + } + } + ] + }, + { + "description": "Insert min and max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 200, + "encryptedLong": { + "$numberLong": "200" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + } + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 200, + "encryptedLong": { + "$numberLong": "200" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gte": { + "$numberLong": "0" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $gt with no results", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$numberLong": "1" + } + } + } + } + ] + }, + "result": [] + } + ] + }, + { + "description": "Aggregate with $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$lt": { + "$numberLong": "1" + } + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lte", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$lte": { + "$numberLong": "1" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $lt below min", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$lt": { + "$numberLong": "0" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be greater than the range minimum" + } + } + ] + }, + { + "description": "Aggregate with $gt above max", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$numberLong": "200" + } + } + } + } + ] + }, + "result": { + "errorContains": "must be less than the range maximum" + } + } + ] + }, + { + "description": "Aggregate with $gt and $lt", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + }, + "$lt": { + "$numberLong": "2" + } + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with equality", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$numberLong": "0" + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$numberLong": "1" + } + } + } + ] + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with full range", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$gte": { + "$numberLong": "0" + }, + "$lte": { + "$numberLong": "200" + } + } + } + }, + { + "$sort": { + "_id": 1 + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + }, + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $in", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "encryptedLong": { + "$in": [ + { + "$numberLong": "0" + } + ] + } + } + } + ] + }, + "result": [ + { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + ] + } + ] + }, + { + "description": "Wrong type: Insert Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberDouble": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gte": { + "$numberDouble": "0" + } + } + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.yml new file mode 100644 index 000000000..97b7db2b7 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Correctness.yml @@ -0,0 +1,422 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Find with $gt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } }} + result: [*doc1] + + - description: "Find with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gte: { $numberLong: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "1" } }} + result: [] + + - description: "Find with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lt: { $numberLong: "1" } }} + result: [*doc0] + + - description: "Find with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lte: { $numberLong: "1" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $lt: { $numberLong: "0" } }} + result: + errorContains: must be greater than the range minimum + + - description: "Find with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "200" } }} + result: + errorContains: must be less than the range maximum + + - description: "Find with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} + result: [*doc1] + + - description: "Find with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $numberLong: "0" } } + result: [*doc0] + - name: find + arguments: + filter: { encryptedLong: { $numberLong: "1" } } + result: [*doc1] + + - description: "Find with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc1] + + - description: "Find with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: find + arguments: + filter: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } + result: [*doc0] + + - description: "Insert out of range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: { _id: 0, encryptedLong: { $numberLong: "-1" }} + result: + errorContains: value must be greater than or equal to the minimum value + + - description: "Insert min and max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: *doc0 + - name: insertOne + arguments: + document: &doc200 { _id: 200, encryptedLong: { $numberLong: "200" }} + - name: find + arguments: + filter: {} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: [*doc0, *doc200] + + - description: "Aggregate with $gte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gte: { $numberLong: "0" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $gt with no results" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "1" } }} } + result: [] + + - description: "Aggregate with $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lt: { $numberLong: "1" } }} } + result: [*doc0] + + - description: "Aggregate with $lte" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lte: { $numberLong: "1" } }} } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $lt below min" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $lt: { $numberLong: "0" } }} } + result: + errorContains: must be greater than the range minimum + + - description: "Aggregate with $gt above max" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "200" } }} } + result: + errorContains: must be less than the range maximum + + - description: "Aggregate with $gt and $lt" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gt: { $numberLong: "0" }, $lt: { $numberLong: "2"} }} } + result: [*doc1] + + - description: "Aggregate with equality" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $numberLong: "0" } } } + result: [*doc0] + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $numberLong: "1" } } } + result: [*doc1] + + - description: "Aggregate with full range" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $gte: {$numberLong: "0"}, $lte: {$numberLong: "200"} } } } + # sort so results from range queries are ordered. + - { $sort: { _id: 1 }} + result: [*doc0, *doc1] + + - description: "Aggregate with $in" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: *doc0 } + - name: insertOne + arguments: { document: *doc1 } + - name: aggregate + arguments: + pipeline: + - { $match: { encryptedLong: { $in: [ {$numberLong: "0"} ] } } } + result: [*doc0] + + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedLong: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: find + arguments: + filter: { encryptedLong: { $gte: { $numberDouble: "0" } }} + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.json new file mode 100644 index 000000000..faf0c401b --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.json @@ -0,0 +1,420 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Long. Delete.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "delete": "default", + "deletes": [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "delete" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.yml new file mode 100644 index 000000000..9bbbf71ae --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Delete.yml @@ -0,0 +1,176 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Delete." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: deleteOne + arguments: + filter: { "encryptedLong": { $gt: {$numberLong: "0" }} } + result: + deletedCount: 1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + delete: *collection_name + deletes: [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "limit": 1 + } + ] + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: delete + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.json new file mode 100644 index 000000000..b233b40b5 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.json @@ -0,0 +1,488 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Long. FindOneAndUpdate.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + }, + "update": { + "$set": { + "encryptedLong": { + "$numberLong": "2" + } + } + }, + "returnDocument": "Before" + }, + "result": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "findAndModify": "default", + "query": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "update": { + "$set": { + "encryptedLong": { + "$$type": "binData" + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "findAndModify" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.yml new file mode 100644 index 000000000..8f74e442f --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-FindOneAndUpdate.yml @@ -0,0 +1,227 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. FindOneAndUpdate." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: findOneAndUpdate + arguments: + filter: { encryptedLong: { $gt: {$numberLong: "0"}} } + update: { "$set": { "encryptedLong": {$numberLong: "2"}}} + returnDocument: Before + result: *doc1 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + findAndModify: *collection_name + query: { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + } + update: { "$set": {"encryptedLong": { $$type: "binData" }} } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: findAndModify + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.json new file mode 100644 index 000000000..1b787d4cb --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.json @@ -0,0 +1,475 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Long. Insert and Find.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + } + }, + "result": [ + { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.yml new file mode 100644 index 000000000..595807d5a --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-InsertFind.yml @@ -0,0 +1,223 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Insert and Find." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: find + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } } } + result: [*doc1] + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "25j9sQXZCihCmHKvTHgaBsAVZFcGPn7JjHdrCGlwyyw=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FA74j21GUEJb1DJBOpR9nVnjaDZnd8yAQNuaW9Qi26g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kJv//KVkbrobIBf+QeWC5jxn20mx/P0R1N6aCSMgKM8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "zB+Whi9IUUGxfLEe+lGuIzLX4LFbIhaIAm5lRk65QTc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ybO1QU3CgvhO8JgRXH+HxKszWcpl5aGDYYVa75fHa1g=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "X3Y3eSAbbMg//JgiHHiFpYOpV61t8kkDexI+CQyitH4=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "SlNHXyqVFGDPrX/2ppwog6l4pwj3PKda2TkZbqgfSfA=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "McjV8xwTF3xI7863DYOBdyvIv6UpzThl6v9vBRk05bI=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.json new file mode 100644 index 000000000..07182bb5e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.json @@ -0,0 +1,492 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "FLE2 Range Long. Update.", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedLong": { + "$numberLong": "0" + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedLong": { + "$numberLong": "1" + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "encryptedLong": { + "$gt": { + "$numberLong": "0" + } + } + }, + "update": { + "$set": { + "encryptedLong": { + "$numberLong": "2" + } + } + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + } + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command_name": "update", + "command": { + "update": "default", + "ordered": true, + "updates": [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedLong": { + "$$type": "binData" + } + } + } + } + ], + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedLong", + "bsonType": "long", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberInt": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberLong": "0" + }, + "max": { + "$numberLong": "200" + } + } + } + ] + } + } + }, + "$db": "default" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 0, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + }, + { + "_id": 1, + "encryptedLong": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.yml new file mode 100644 index 000000000..ac5932355 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-Long-Update.yml @@ -0,0 +1,242 @@ + +# Requires libmongocrypt including MONGOCRYPT-702. +runOn: + - minServerVersion: "8.0.0" # Requires 8.0.0-rc14 for SERVER-91889. + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedLong', 'bsonType': 'long', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberInt': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberLong': '0'}, 'max': {'$numberLong': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "FLE2 Range Long. Update." + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 0, encryptedLong: { $numberLong: "0" } } + - name: insertOne + arguments: + document: &doc1 { _id: 1, encryptedLong: { $numberLong: "1" } } + - name: updateOne + arguments: + filter: { encryptedLong: { $gt: { $numberLong: "0" } } } + update: { "$set": { "encryptedLong": { $numberLong: "2" } }} + result: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectations: + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + - command_started_event: + command: + find: datakeys + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { "_id": 0, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { "_id": 1, "encryptedLong": { $$type: "binData" } } + ordered: true + encryptionInformation: + type: 1 + schema: + default.default: + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + command_name: insert + - command_started_event: + command_name: update + command: + + "update": "default" + "ordered": true + "updates": [ + { + "q": { + "encryptedLong": { + "$gt": { + "$binary": { + "base64": "DXUFAAADcGF5bG9hZAAZBQAABGcABQUAAAMwAH0AAAAFZAAgAAAAALGGQ/CRD+pGLD53BZzWcCcYbuGLVEyjzXIx7b+ux/q2BXMAIAAAAACOC6mXEZL27P9hethZbtKYsTXKK+FpgQ9Axxmn9N/cCwVsACAAAAAA+MFEd8XfZSpbXKqqPC2L3TEFswkaG5Ff6aSgf8p+XVIAAzEAfQAAAAVkACAAAAAAtL3QIvnZqCF72eS6lKr8ilff7R6kiNklokiTuaU5wNsFcwAgAAAAAEtqr3/X731VB+VrbFcY8ZrJKRo2E0Fd+C8L0EMNcvcCBWwAIAAAAABNPhSriux8W8qbwnhCczE3IzlhNEnGDpUwTFDZSL+eYQADMgB9AAAABWQAIAAAAAB99ZW/7KwXKzl5M3XQsAJ3JbEef90IoxFYBArNiYzlgQVzACAAAAAAYO/qaw0+92HAryxOUG7iK6hnIy3OaUA9jIqtHdvcq8YFbAAgAAAAAHrUYj8A0hVgc6VklpDiljOnykrUSfEsjm56XO/bsfKdAAMzAH0AAAAFZAAgAAAAAOK8brUuc2onBNDRtfYMR736dHj4dQqXod8JG7tAMTsDBXMAIAAAAAAW6SrGAL6Bx0s7ZlsYULFfOAiYIGhEWu6md3r+Rk40awVsACAAAAAAIHYXP8RLcCboUmHN3+OlnEw1DxaLSnbTB9PdF228fFAAAzQAfQAAAAVkACAAAAAAV22FGF7ZDwK/EYiGNMlm/QuT3saQdyJM/Fn+ZyQug1oFcwAgAAAAACo7GwCvbcs5UHQMgds9/1QMklEVdjZigpuOFGrDmmxtBWwAIAAAAADQbYYPxlCMMGe2MulbiurApFLoeJSMvTeDU3pyEA2jNwADNQB9AAAABWQAIAAAAADFspsMG7yHjKppyllon1KqAsTrHaZ6JzNqnSz8o6iTvwVzACAAAAAAeiA5pqVIQQ9s6UY/P8v5Jjkl3I7iFNeLDYehikrINrsFbAAgAAAAAFjBTzTpNxDEkA0vSRj0jCED9KDRlboMVyilKyDz5YR4AAM2AH0AAAAFZAAgAAAAAPcLmtq+V1e+MRlZ7NHq1+mrRVBQje5zj685ZvdsfKvSBXMAIAAAAABdHz/3w2k5km97QN9m7oLFYJaVJneNlMboIlz5yUASQAVsACAAAAAAWbp8JVJnx8fEVAJFa7WMfMa7wXeP5M3C8MX20J/i9n0AAzcAfQAAAAVkACAAAAAAYfLwnoxK6XAGQrJFy8+TIJoq38ldBaO75h4zA4ZX5tQFcwAgAAAAAC2wk8UcJH5X5XGnDBYmel6srpBkzBhHtt3Jw1u5TSJ1BWwAIAAAAAA9/YU9eI3D7QbXKIw/3/gzWJ6MZrCYhG0j1wNKgRQp5wADOAB9AAAABWQAIAAAAADGvyrtKkIcaV17ynZA7b2k5Pz6OhvxdWNkDvDWJIja8wVzACAAAAAAOLypVKNxf/wR1G8OZjUUsTQzDYeNNhhITxGMSp7euS4FbAAgAAAAAA9EsxoV1B2DcQ1NJRwuxXnvVR+vkD0wbbDYEI/zFEnDAAM5AH0AAAAFZAAgAAAAAEocREw1L0g+roFUchJI2Yd0M0ME2bnErNUYnpyJP1SqBXMAIAAAAAAcE2/JK/8MoSeOchIuAkKh1X3ImoA7p8ujAZIfvIDo6QVsACAAAAAA+W0+zgLr85/PD7P9a94wk6MgNgrizx/XU9aCxAkp1IwAABJjbQAAAAAAAAAAAAAQcGF5bG9hZElkAAAAAAAQZmlyc3RPcGVyYXRvcgABAAAAEnNwAAEAAAAAAAAAEHRmAAEAAAASbW4AAAAAAAAAAAASbXgAyAAAAAAAAAAA", + "subType": "06" + } + } + } + }, + "u": { + "$set": { + "encryptedLong": { $$type: "binData" } + } + } + } + ] + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + "$db": "default" + + + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - + { + "_id": 0, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "RjBYT2h3ZAoHxhf8DU6/dFbDkEBZp0IxREcsRTu2MXs=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "x7GR49EN0t3WXQDihkrbonK7qNIBYC87tpL/XEUyIYc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "JfYUqWF+OoGjiYkRI4L5iPlF+T1Eleul7Fki22jp4Qc=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "q1RyGfIgsaQHoZFRw+DD28V26rN5hweApPLwExncvT8=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "L2PFeKGvLS6C+DLudR6fGlBq3ERPvjWvRyNRIA2HVb0=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "CWxaNqL3iP1yCixDkcmf9bmW3E5VeN8TJkg1jJe528s=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "+vC6araOEo+fpW7PSIP40/EnzBCj1d2N10Jr3rrXJJM=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "6SV63Mf51Z6A6p2X3rCnJKCu6ku3Oeb45mBYbz+IoAo=", + "subType": "00" + } + } + ] + } + - + { + "_id": 1, + "encryptedLong": { $$type: "binData" }, + "__safeContent__": [ + { + "$binary": { + "base64": "hyDcE6QQjPrYJaIS/n7evEZFYcm31Tj89CpEYGF45cI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "F08nMDWDZc+DbWM7XCEJNNCEYyinRmrvGP7EWhmp4is=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "cXH4688amcDc8kZOJq4UP8cE3R58Zl7e+Qo/1jyspps=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "uURBxvTp3FBCVkd+LPqyuY7d6rMW6SGIJQEPY/wtkZI=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "jG3hax1L3RBp9t38vUt53FsBxgr/+Si/vVISpAylYpE=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "kwtIW8MhH9Ky5xNjBx8gFA/SHh2YVphie7g5FGBzals=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "FHflwFuEMu4xX0ZApHi+pdlBH+oevAtXckCUb5Wv0xU=", + "subType": "00" + } + }, + { + "$binary": { + "base64": "ty4cnzJdAlbQKnh7px3GEYjBnvO+jIOaKjoTRDtmh3M=", + "subType": "00" + } + } + ] + } \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.json new file mode 100644 index 000000000..621560450 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.json @@ -0,0 +1,163 @@ +{ + "runOn": [ + { + "minServerVersion": "8.0.0", + "topology": [ + "replicaset", + "sharded", + "load-balanced" + ], + "maxServerVersion": "8.99.99" + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "encrypted_fields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "range", + "contention": { + "$numberLong": "0" + }, + "trimFactor": { + "$numberLong": "1" + }, + "sparsity": { + "$numberLong": "1" + }, + "min": { + "$numberInt": "0" + }, + "max": { + "$numberInt": "200" + } + } + } + ] + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ], + "tests": [ + { + "description": "Wrong type: Insert Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberDouble": "0" + } + } + }, + "result": { + "errorContains": "cannot encrypt element" + } + } + ] + }, + { + "description": "Wrong type: Find Double", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": { + "$binary": { + "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 0, + "encryptedInt": { + "$numberInt": "0" + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "encryptedInt": { + "$gte": { + "$numberDouble": "0" + } + } + }, + "sort": { + "_id": 1 + } + }, + "result": { + "errorContains": "field type is not supported" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.yml new file mode 100644 index 000000000..432f86b42 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Rangev2-WrongType.yml @@ -0,0 +1,44 @@ +# Test correctness results. +# Does not include command monitoring expectations or outcome assertions to make tests more readable. + +# Requires libmongocrypt 1.8.0. +runOn: + - minServerVersion: "8.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. + # FLE 2 Encrypted collections are not supported on standalone. + topology: [ "replicaset", "sharded", "load-balanced" ] + maxServerVersion: "8.99.99" +database_name: &database_name "default" +collection_name: &collection_name "default" +data: [] +encrypted_fields: &encrypted_fields { 'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedInt', 'bsonType': 'int', 'queries': {'queryType': 'range', 'contention': {'$numberLong': '0'}, 'trimFactor': {'$numberLong': '1'}, 'sparsity': {'$numberLong': '1'}, 'min': {'$numberInt': '0'}, 'max': {'$numberInt': '200'}}}]} +key_vault_data: [ {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} ] +tests: + - description: "Wrong type: Insert Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberDouble: "0" }} } + result: + # Expect an error from mongocryptd. + errorContains: "cannot encrypt element" + + - description: "Wrong type: Find Double" + clientOptions: + autoEncryptOpts: + kmsProviders: + local: {'key': {'$binary': {'base64': 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: { document: { _id: 0, encryptedInt: { $numberInt: "0" }} } + - name: find + arguments: + filter: { encryptedInt: { $gte: { $numberDouble: "0" } }} + # sort so results from range queries are ordered. + sort: { _id: 1 } + result: + # expect an error from libmongocrypt. + errorContains: "field type is not supported" diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.json index 14104e2cd..cb260edc0 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.yml index ce5a4ed67..3bed02718 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-Update.yml @@ -1,7 +1,6 @@ # Requires libmongocrypt 1.8.0. runOn: - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.json b/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.json index 4adf6fc07..901c4dd84 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.json +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.json @@ -2,7 +2,6 @@ "runOn": [ { "minServerVersion": "7.0.0", - "serverless": "forbid", "topology": [ "replicaset", "sharded", diff --git a/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.yml b/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.yml index fdeb49010..3eb90ea93 100644 --- a/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.yml +++ b/src/test/spec/json/client-side-encryption/legacy/fle2v2-validatorAndPartialFieldExpression.yml @@ -2,7 +2,6 @@ runOn: # Require server version 6.0.0 to get behavior added in SERVER-64911. - minServerVersion: "7.0.0" - serverless: "forbid" # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Unskip once Serverless enables the QEv2 protocol. # FLE 2 Encrypted collections are not supported on standalone. topology: [ "replicaset", "sharded", "load-balanced" ] diff --git a/src/test/spec/json/client-side-encryption/legacy/gcpKMS.json b/src/test/spec/json/client-side-encryption/legacy/gcpKMS.json index c2c08b8a2..65f12ec13 100644 --- a/src/test/spec/json/client-side-encryption/legacy/gcpKMS.json +++ b/src/test/spec/json/client-side-encryption/legacy/gcpKMS.json @@ -78,6 +78,17 @@ "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } + }, + "encrypted_string_kmip_delegated": { + "encrypt": { + "keyId": [ + { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba6" + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } } }, "bsonType": "object" diff --git a/src/test/spec/json/client-side-encryption/legacy/gcpKMS.yml b/src/test/spec/json/client-side-encryption/legacy/gcpKMS.yml index 50b6a40be..1bf2121b6 100644 --- a/src/test/spec/json/client-side-encryption/legacy/gcpKMS.yml +++ b/src/test/spec/json/client-side-encryption/legacy/gcpKMS.yml @@ -4,7 +4,7 @@ database_name: &database_name "default" collection_name: &collection_name "default" data: [] -json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip_delegated': {'encrypt': {'keyId': [{'$uuid': '7411e9af-c688-4df7-8143-5e60ae96cba6'}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} key_vault_data: [{'_id': {'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'CiQAIgLj0WyktnB4dfYHo5SLZ41K4ASQrjJUaSzl5vvVH0G12G0SiQEAjlV8XPlbnHDEDFbdTO4QIe8ER2/172U1ouLazG0ysDtFFIlSvWX5ZnZUrRMmp/R2aJkzLXEt/zf8Mn4Lfm+itnjgo5R9K4pmPNvvPKNZX5C16lrPT+aA+rd+zXFSmlMg3i5jnxvTdLHhg3G7Q/Uv1ZIJskKt95bzLoe0tUVzRWMYXLIEcohnQg==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1601574333107'}}, 'updateDate': {'$date': {'$numberLong': '1601574333107'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'gcp', 'projectId': 'devprod-drivers', 'location': 'global', 'keyRing': 'key-ring-csfle', 'keyName': 'key-name-csfle'}, 'keyAltNames': ['altname', 'gcp_altname']}] tests: diff --git a/src/test/spec/json/client-side-encryption/legacy/getMore.json b/src/test/spec/json/client-side-encryption/legacy/getMore.json index ee99bf753..94e788ef6 100644 --- a/src/test/spec/json/client-side-encryption/legacy/getMore.json +++ b/src/test/spec/json/client-side-encryption/legacy/getMore.json @@ -216,7 +216,10 @@ "command_started_event": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "default", "batchSize": 2 diff --git a/src/test/spec/json/client-side-encryption/legacy/getMore.yml b/src/test/spec/json/client-side-encryption/legacy/getMore.yml index 4359ee891..dd246e9fa 100644 --- a/src/test/spec/json/client-side-encryption/legacy/getMore.yml +++ b/src/test/spec/json/client-side-encryption/legacy/getMore.yml @@ -48,7 +48,7 @@ tests: command_name: find - command_started_event: command: - getMore: { $$type: "long" } + getMore: { $$type: [ int, long ] } collection: *collection_name batchSize: 2 command_name: getMore diff --git a/src/test/spec/json/client-side-encryption/legacy/keyCache.json b/src/test/spec/json/client-side-encryption/legacy/keyCache.json new file mode 100644 index 000000000..912ce8002 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/keyCache.json @@ -0,0 +1,270 @@ +{ + "runOn": [ + { + "minServerVersion": "4.1.10" + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "json_schema": { + "properties": { + "encrypted_w_altname": { + "encrypt": { + "keyId": "/altname", + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + }, + "random": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string_equivalent": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" + }, + "key_vault_data": [ + { + "status": 1, + "_id": { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + }, + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyAltNames": [ + "altname", + "another_altname" + ] + } + ], + "tests": [ + { + "description": "Insert with deterministic encryption, then find it", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "aws": {} + }, + "keyExpirationMS": 1 + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + } + }, + { + "name": "wait", + "object": "testRunner", + "arguments": { + "ms": 50 + } + }, + { + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "result": [ + { + "_id": 1, + "encrypted_string": "string0" + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ], + "ordered": true + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "_id": 1 + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/keyCache.yml b/src/test/spec/json/client-side-encryption/legacy/keyCache.yml new file mode 100644 index 000000000..3af117ca8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/keyCache.yml @@ -0,0 +1,69 @@ +runOn: + - minServerVersion: "4.1.10" +database_name: &database_name "default" +collection_name: &collection_name "default" + +data: [] +json_schema: {'properties': {'encrypted_w_altname': {'encrypt': {'keyId': '/altname', 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'random': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string_equivalent': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +key_vault_data: [{'status': 1, '_id': {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'masterKey': {'provider': 'aws', 'key': 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', 'region': 'us-east-1'}, 'updateDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyMaterial': {'$binary': {'base64': 'AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyAltNames': ['altname', 'another_altname']}] + +tests: + - description: "Insert with deterministic encryption, then find it" + clientOptions: + autoEncryptOpts: + kmsProviders: + aws: {} # Credentials filled in from environment. + keyExpirationMS: 1 + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 1, encrypted_string: "string0" } + - name: wait + object: testRunner + arguments: + ms: 50 # Wait long enough to account for coarse time resolution on Windows (CDRIVER-4526). + - name: find + arguments: + filter: { _id: 1 } + result: [*doc0] + expectations: + # Auto encryption will request the collection info. + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + # Then key is fetched from the key vault. + - command_started_event: + command: + find: datakeys + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { _id: 1, encrypted_string: {'$binary': {'base64': 'AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==', 'subType': '06'}} } + ordered: true + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: { _id: 1 } + command_name: find + # The cache has expired and the key must be fetched again + - command_started_event: + command: + find: datakeys + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + $db: keyvault + readConcern: { level: "majority" } + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - *doc0_encrypted \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/kmipKMS.json b/src/test/spec/json/client-side-encryption/legacy/kmipKMS.json index 5749d21ab..349328b43 100644 --- a/src/test/spec/json/client-side-encryption/legacy/kmipKMS.json +++ b/src/test/spec/json/client-side-encryption/legacy/kmipKMS.json @@ -78,6 +78,17 @@ "bsonType": "string", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" } + }, + "encrypted_string_kmip_delegated": { + "encrypt": { + "keyId": [ + { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba6" + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } } }, "bsonType": "object" @@ -117,6 +128,38 @@ "altname", "kmip_altname" ] + }, + { + "_id": { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba6" + }, + "keyMaterial": { + "$binary": { + "base64": "5TLMFWlguBWe5GUESTvOVtkdBsCrynhnV72XRyZ66/nk+EP9/1oEp1t1sg0+vwCTqULHjBiUE6DRx2mYD/Eup1+u2Jgz9/+1sV1drXeOPALNPkSgiZiDbIb67zRi+wTABEcKcegJH+FhmSGxwUoQAiHCsCbcvia5P8tN1lt98YQ=", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1634220190041" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1634220190041" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": "11" + }, + "keyAltNames": [ + "delegated" + ] } ], "tests": [ @@ -218,6 +261,102 @@ ] } } + }, + { + "description": "Insert a document with auto encryption using KMIP delegated KMS provider", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "kmip": {} + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string_kmip_delegated": "string0" + } + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba6" + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault" + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encrypted_string_kmip_delegated": { + "$binary": { + "base64": "AXQR6a/GiE33gUNeYK6Wy6YCkB+8NVfAAjIbvLqyXIg6g1a8tXrym92DPoqmxpcdQyH0vQM3aFNMz7tZwQBimKs29ztZV/LWjM633HhO5ACl9A==", + "subType": "06" + } + } + } + ], + "ordered": true + }, + "command_name": "insert" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "encrypted_string_kmip_delegated": { + "$binary": { + "base64": "AXQR6a/GiE33gUNeYK6Wy6YCkB+8NVfAAjIbvLqyXIg6g1a8tXrym92DPoqmxpcdQyH0vQM3aFNMz7tZwQBimKs29ztZV/LWjM633HhO5ACl9A==", + "subType": "06" + } + } + } + ] + } + } } ] } diff --git a/src/test/spec/json/client-side-encryption/legacy/kmipKMS.yml b/src/test/spec/json/client-side-encryption/legacy/kmipKMS.yml index 874a92bf3..51fa42cc7 100644 --- a/src/test/spec/json/client-side-encryption/legacy/kmipKMS.yml +++ b/src/test/spec/json/client-side-encryption/legacy/kmipKMS.yml @@ -4,8 +4,8 @@ database_name: &database_name "default" collection_name: &collection_name "default" data: [] -json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} -key_vault_data: [{'_id': {'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'eUYDyB0HuWb+lQgUwO+6qJQyTTDTY2gp9FbemL7ZFo0pvr0x6rm6Ff9OVUTGH6HyMKipaeHdiIJU1dzsLwvqKvi7Beh+U4iaIWX/K0oEg1GOsJc0+Z/in8gNHbGUYLmycHViM3LES3kdt7FdFSUl5rEBHrM71yoNEXImz17QJWMGOuT4x6yoi2pvnaRJwfrI4DjpmnnTrDMac92jgZehbg==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1634220190041'}}, 'updateDate': {'$date': {'$numberLong': '1634220190041'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'kmip', 'keyId': '1'}, 'keyAltNames': ['altname', 'kmip_altname']}] +json_schema: {'properties': {'encrypted_string_aws': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_azure': {'encrypt': {'keyId': [{'$binary': {'base64': 'AZURE+AAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_gcp': {'encrypt': {'keyId': [{'$binary': {'base64': 'GCP+AAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_local': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip': {'encrypt': {'keyId': [{'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'encrypted_string_kmip_delegated': {'encrypt': {'keyId': [{'$uuid': '7411e9af-c688-4df7-8143-5e60ae96cba6'}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +key_vault_data: [{'_id': {'$binary': {'base64': 'dBHpr8aITfeBQ15grpbLpQ==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'eUYDyB0HuWb+lQgUwO+6qJQyTTDTY2gp9FbemL7ZFo0pvr0x6rm6Ff9OVUTGH6HyMKipaeHdiIJU1dzsLwvqKvi7Beh+U4iaIWX/K0oEg1GOsJc0+Z/in8gNHbGUYLmycHViM3LES3kdt7FdFSUl5rEBHrM71yoNEXImz17QJWMGOuT4x6yoi2pvnaRJwfrI4DjpmnnTrDMac92jgZehbg==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1634220190041'}}, 'updateDate': {'$date': {'$numberLong': '1634220190041'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'kmip', 'keyId': '1'}, 'keyAltNames': ['altname', 'kmip_altname']},{'_id': {'$uuid': '7411e9af-c688-4df7-8143-5e60ae96cba6'}, 'keyMaterial': {'$binary': {'base64': '5TLMFWlguBWe5GUESTvOVtkdBsCrynhnV72XRyZ66/nk+EP9/1oEp1t1sg0+vwCTqULHjBiUE6DRx2mYD/Eup1+u2Jgz9/+1sV1drXeOPALNPkSgiZiDbIb67zRi+wTABEcKcegJH+FhmSGxwUoQAiHCsCbcvia5P8tN1lt98YQ=', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1634220190041'}}, 'updateDate': {'$date': {'$numberLong': '1634220190041'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'kmip', 'delegated': True, 'keyId': '11'}, 'keyAltNames': ['delegated']}] tests: - description: "Insert a document with auto encryption using KMIP KMS provider" @@ -43,4 +43,41 @@ tests: collection: # Outcome is checked using a separate MongoClient without auto encryption. data: - - *doc0_encrypted \ No newline at end of file + - *doc0_encrypted + + - description: "Insert a document with auto encryption using KMIP delegated KMS provider" + clientOptions: + autoEncryptOpts: + kmsProviders: + kmip: {} + operations: + - name: insertOne + arguments: + document: &doc1 { _id: 1, encrypted_string_kmip_delegated: "string0" } + expectations: + # Auto encryption will request the collection info. + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + # Then key is fetched from the key vault. + - command_started_event: + command: + find: datakeys + filter: { $or: [ { _id: { $in: [ {'$uuid': '7411e9af-c688-4df7-8143-5e60ae96cba6'} ] } }, { keyAltNames: { $in: [] } } ] } + $db: keyvault + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc1_encrypted { _id: 1, encrypted_string_kmip_delegated: {'$binary': {'base64': 'AXQR6a/GiE33gUNeYK6Wy6YCkB+8NVfAAjIbvLqyXIg6g1a8tXrym92DPoqmxpcdQyH0vQM3aFNMz7tZwQBimKs29ztZV/LWjM633HhO5ACl9A==', 'subType': '06'}} } + ordered: true + command_name: insert + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - *doc1_encrypted \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/namedKMS.json b/src/test/spec/json/client-side-encryption/legacy/namedKMS.json new file mode 100644 index 000000000..c85944358 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/namedKMS.json @@ -0,0 +1,197 @@ +{ + "runOn": [ + { + "minServerVersion": "4.1.10" + } + ], + "database_name": "default", + "collection_name": "default", + "data": [], + "json_schema": { + "properties": { + "encrypted_string": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "local+name2+AAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" + }, + "key_vault_data": [ + { + "_id": { + "$binary": { + "base64": "local+name2+AAAAAAAAAA==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local:name2" + } + } + ], + "tests": [ + { + "description": "Automatically encrypt and decrypt with a named KMS provider", + "clientOptions": { + "autoEncryptOpts": { + "kmsProviders": { + "local:name2": { + "key": { + "$binary": { + "base64": "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", + "subType": "00" + } + } + } + } + } + }, + "operations": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "result": [ + { + "_id": 1, + "encrypted_string": "string0" + } + ] + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "command_name": "listCollections" + } + }, + { + "command_started_event": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "local+name2+AAAAAAAAAA==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "command_name": "find" + } + }, + { + "command_started_event": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AZaHGpfp2pntvgAAAAAAAAAC07sFvTQ0I4O2U49hpr4HezaK44Ivluzv5ntQBTYHDlAJMLyRMyB6Dl+UGHBgqhHe/Xw+pcT9XdiUoOJYAx9g+w==", + "subType": "06" + } + } + } + ], + "ordered": true + }, + "command_name": "insert" + } + }, + { + "command_started_event": { + "command": { + "find": "default", + "filter": { + "_id": 1 + } + }, + "command_name": "find" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AZaHGpfp2pntvgAAAAAAAAAC07sFvTQ0I4O2U49hpr4HezaK44Ivluzv5ntQBTYHDlAJMLyRMyB6Dl+UGHBgqhHe/Xw+pcT9XdiUoOJYAx9g+w==", + "subType": "06" + } + } + } + ] + } + } + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/legacy/namedKMS.yml b/src/test/spec/json/client-side-encryption/legacy/namedKMS.yml new file mode 100644 index 000000000..7bd456aba --- /dev/null +++ b/src/test/spec/json/client-side-encryption/legacy/namedKMS.yml @@ -0,0 +1,56 @@ +runOn: + - minServerVersion: "4.1.10" +database_name: &database_name "default" +collection_name: &collection_name "default" + +data: [] +json_schema: {'properties': {'encrypted_string': {'encrypt': {'keyId': [{'$binary': {'base64': 'local+name2+AAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} +key_vault_data: [{'_id': {'$binary': {'base64': 'local+name2+AAAAAAAAAA==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1552949630483'}}, 'updateDate': {'$date': {'$numberLong': '1552949630483'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local:name2'}}] + +tests: + - description: "Automatically encrypt and decrypt with a named KMS provider" + clientOptions: + autoEncryptOpts: + kmsProviders: + "local:name2": {'key': {'$binary': {'base64': 'local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk', 'subType': '00'}}} + operations: + - name: insertOne + arguments: + document: &doc0 { _id: 1, encrypted_string: "string0" } + - name: find + arguments: + filter: { _id: 1 } + result: [*doc0] + expectations: + # Auto encryption will request the collection info. + - command_started_event: + command: + listCollections: 1 + filter: + name: *collection_name + command_name: listCollections + # Then key is fetched from the key vault. + - command_started_event: + command: + find: datakeys + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'local+name2+AAAAAAAAAA==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + $db: keyvault + readConcern: { level: "majority" } + command_name: find + - command_started_event: + command: + insert: *collection_name + documents: + - &doc0_encrypted { _id: 1, encrypted_string: {'$binary': {'base64': 'AZaHGpfp2pntvgAAAAAAAAAC07sFvTQ0I4O2U49hpr4HezaK44Ivluzv5ntQBTYHDlAJMLyRMyB6Dl+UGHBgqhHe/Xw+pcT9XdiUoOJYAx9g+w==', 'subType': '06'}} } + ordered: true + command_name: insert + - command_started_event: + command: + find: *collection_name + filter: { _id: 1 } + command_name: find + outcome: + collection: + # Outcome is checked using a separate MongoClient without auto encryption. + data: + - *doc0_encrypted \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/legacy/timeoutMS.json b/src/test/spec/json/client-side-encryption/legacy/timeoutMS.json index 443aa0aa2..b667767cf 100644 --- a/src/test/spec/json/client-side-encryption/legacy/timeoutMS.json +++ b/src/test/spec/json/client-side-encryption/legacy/timeoutMS.json @@ -161,7 +161,7 @@ "failPoint": { "configureFailPoint": "failCommand", "mode": { - "times": 3 + "times": 2 }, "data": { "failCommands": [ @@ -169,7 +169,7 @@ "find" ], "blockConnection": true, - "blockTimeMS": 20 + "blockTimeMS": 30 } }, "clientOptions": { diff --git a/src/test/spec/json/client-side-encryption/legacy/timeoutMS.yml b/src/test/spec/json/client-side-encryption/legacy/timeoutMS.yml index 33321ad64..bb71d6765 100644 --- a/src/test/spec/json/client-side-encryption/legacy/timeoutMS.yml +++ b/src/test/spec/json/client-side-encryption/legacy/timeoutMS.yml @@ -38,8 +38,10 @@ tests: command_name: listCollections # Test that timeoutMS applies to the sum of all operations done for client-side encryption. This is done by blocking - # listCollections and find for 20ms each and running an insertOne with timeoutMS=50. There should be two - # listCollections commands and one "find" command, so the sum should take more than timeoutMS. + # listCollections and find for 30ms each and running an insertOne with timeoutMS=50. There should be one + # listCollections command and one "find" command, so the sum should take more than timeoutMS. A second listCollections + # event doesn't occur due to the internal MongoClient lacking configured auto encryption, plus libmongocrypt holds the + # collection schema in cache for a minute. # # This test does not include command monitoring expectations because the exact command sequence is dependent on the # amount of time taken by mongocryptd communication. In slow runs, mongocryptd communication can breach the timeout @@ -47,11 +49,11 @@ tests: - description: "remaining timeoutMS applied to find to get keyvault data" failPoint: configureFailPoint: failCommand - mode: { times: 3 } + mode: { times: 2 } data: failCommands: ["listCollections", "find"] blockConnection: true - blockTimeMS: 20 + blockTimeMS: 30 clientOptions: autoEncryptOpts: kmsProviders: diff --git a/src/test/spec/json/client-side-encryption/unified/createDataKey-kms_providers-invalid.yml b/src/test/spec/json/client-side-encryption/unified/createDataKey-kms_providers-invalid.yml index f692a0907..fee5a823f 100644 --- a/src/test/spec/json/client-side-encryption/unified/createDataKey-kms_providers-invalid.yml +++ b/src/test/spec/json/client-side-encryption/unified/createDataKey-kms_providers-invalid.yml @@ -58,7 +58,7 @@ tests: kmsProvider: aws opts: masterKey: - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: invalid expectError: isClientError: true diff --git a/src/test/spec/json/client-side-encryption/unified/createDataKey.json b/src/test/spec/json/client-side-encryption/unified/createDataKey.json index 110c726f9..f99fa3dbc 100644 --- a/src/test/spec/json/client-side-encryption/unified/createDataKey.json +++ b/src/test/spec/json/client-side-encryption/unified/createDataKey.json @@ -337,6 +337,70 @@ } ] }, + { + "description": "create datakey with KMIP delegated KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "kmip", + "opts": { + "masterKey": { + "delegated": true + } + } + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "kmip", + "keyId": { + "$$type": "string" + }, + "delegated": true + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, { "description": "create datakey with local KMS provider", "operations": [ diff --git a/src/test/spec/json/client-side-encryption/unified/createDataKey.yml b/src/test/spec/json/client-side-encryption/unified/createDataKey.yml index dd1463863..c9a73158c 100644 --- a/src/test/spec/json/client-side-encryption/unified/createDataKey.yml +++ b/src/test/spec/json/client-side-encryption/unified/createDataKey.yml @@ -44,7 +44,7 @@ tests: kmsProvider: aws opts: masterKey: &new_aws_masterkey - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-1 expectResult: { $$type: binData } expectEvents: @@ -150,6 +150,35 @@ tests: keyId: { $$type: string } writeConcern: { w: majority } + - description: create datakey with KMIP delegated KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: kmip + opts: + masterKey: &new_kmip_masterkey + delegated: true + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: kmip + keyId: { $$type: string } + delegated: true + writeConcern: { w: majority } + - description: create datakey with local KMS provider operations: - name: createDataKey diff --git a/src/test/spec/json/client-side-encryption/unified/deleteKey.yml b/src/test/spec/json/client-side-encryption/unified/deleteKey.yml index c598e9469..56acf115a 100644 --- a/src/test/spec/json/client-side-encryption/unified/deleteKey.yml +++ b/src/test/spec/json/client-side-encryption/unified/deleteKey.yml @@ -39,7 +39,7 @@ initialData: status: 1 masterKey: provider: aws - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-1 - &local_key_doc _id: &local_key_id { $binary: { base64: bG9jYWxrZXlsb2NhbGtleQ==, subType: "04" } } diff --git a/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.json b/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.json new file mode 100644 index 000000000..0817508f8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.json @@ -0,0 +1,322 @@ +{ + "description": "fle2v2-BypassQueryAnalysis", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "serverless": "forbid", + "csfle": true, + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + }, + "keyVaultNamespace": "keyvault.datakeys", + "bypassQueryAnalysis": true + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + }, + { + "client": { + "id": "client1" + } + }, + { + "database": { + "id": "unencryptedDB", + "client": "client1", + "databaseName": "default" + } + }, + { + "collection": { + "id": "unencryptedColl", + "database": "unencryptedDB", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + ], + "tests": [ + { + "description": "BypassQueryAnalysis decrypts", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedIndexed": { + "$binary": { + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", + "subType": "06" + } + } + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": "123" + } + ] + }, + { + "object": "unencryptedColl", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", + "subType": "00" + } + } + ] + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedIndexed": { + "$binary": { + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", + "subType": "06" + } + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "default", + "filter": { + "_id": 1 + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.yml b/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.yml new file mode 100644 index 000000000..2b4a5ec11 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/fle2v2-BypassQueryAnalysis.yml @@ -0,0 +1,130 @@ +description: fle2v2-BypassQueryAnalysis + +schemaVersion: "1.23" + +runOnRequirements: + - minServerVersion: "7.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Test has not run on Serverless. + # Serverless tests are planned for removal: DRIVERS-3115 + serverless: forbid + csfle: true + topologies: [ "replicaset", "sharded", "load-balanced" ] + +createEntities: + - client: + id: &client0 client0 + autoEncryptOpts: + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + keyVaultNamespace: keyvault.datakeys + bypassQueryAnalysis: true + observeEvents: [ commandStartedEvent ] + - database: + id: &encryptedDB encryptedDB + client: *client0 + databaseName: &encryptedDBName default + - collection: + id: &encryptedColl encryptedColl + database: *encryptedDB + collectionName: &encryptedCollName default + - client: + id: &client1 client1 + - database: + id: &unencryptedDB unencryptedDB + client: *client1 + databaseName: *encryptedDBName + - collection: + id: &unencryptedColl unencryptedColl + database: *unencryptedDB + collectionName: *encryptedCollName + +initialData: + - databaseName: &keyvaultDBName keyvault + collectionName: &datakeysCollName datakeys + documents: + - {'_id': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} + - databaseName: *encryptedDBName + collectionName: *encryptedCollName + documents: [] + createOptions: + encryptedFields: &encrypted_fields {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + +tests: + - description: "BypassQueryAnalysis decrypts" + operations: + - object: *encryptedColl + name: insertOne + arguments: + document: &doc0_encrypted { + "_id": 1, + "encryptedIndexed": { + "$binary": { + # Payload has an IndexKey of key1 and UserKey of key1. + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", + "subType": "06" + } + } + } + - object: *encryptedColl + name: find + arguments: + filter: { "_id": 1 } + expectResult: [{"_id": 1, "encryptedIndexed": "123" }] + - object: *unencryptedColl + name: find + arguments: + filter: {} + expectResult: + - {"_id": 1, "encryptedIndexed": { "$$type": "binData" }, "__safeContent__": [{ "$binary" : { "base64" : "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", "subType" : "00" } }] } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + listCollections: 1 + filter: + name: *encryptedCollName + commandName: listCollections + - commandStartedEvent: + command: + insert: *encryptedCollName + documents: + - *doc0_encrypted + ordered: true + encryptionInformation: + type: 1 + schema: + "default.default": + # libmongocrypt applies escCollection and ecocCollection to outgoing command. + escCollection: "enxcol_.default.esc" + ecocCollection: "enxcol_.default.ecoc" + <<: *encrypted_fields + commandName: insert + - commandStartedEvent: + command: + find: *encryptedCollName + filter: { "_id": 1 } + commandName: find + - commandStartedEvent: + command: + find: *datakeysCollName + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: *keyvaultDBName + readConcern: { level: "majority" } + commandName: find \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json b/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json new file mode 100644 index 000000000..b5f848c08 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json @@ -0,0 +1,256 @@ +{ + "description": "fle2v2-EncryptedFields-vs-EncryptedFieldsMap", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "serverless": "forbid", + "csfle": true, + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + }, + "keyVaultNamespace": "keyvault.datakeys", + "encryptedFieldsMap": { + "default.default": { + "fields": [] + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + ], + "tests": [ + { + "description": "encryptedFieldsMap is preferred over remote encryptedFields", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedUnindexed": "value123" + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "default", + "commandName": "insert", + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + ], + "ordered": true + } + } + }, + { + "commandStartedEvent": { + "databaseName": "default", + "commandName": "find", + "command": { + "find": "default", + "filter": { + "_id": 1 + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "commandName": "find", + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "default", + "databaseName": "default", + "documents": [ + { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml b/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml new file mode 100644 index 000000000..67cca9b43 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.yml @@ -0,0 +1,114 @@ +description: fle2v2-EncryptedFields-vs-EncryptedFieldsMap + +schemaVersion: "1.23" + +runOnRequirements: + - minServerVersion: "7.0.0" + # Skip QEv2 (also referred to as FLE2v2) tests on Serverless. Test has not run on Serverless. + # Serverless tests are planned for removal: DRIVERS-3115 + serverless: forbid + csfle: true + topologies: [ "replicaset", "sharded", "load-balanced" ] + +createEntities: + - client: + id: &client0 client0 + autoEncryptOpts: + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + keyVaultNamespace: keyvault.datakeys + encryptedFieldsMap: { + "default.default": { + "fields": [] + } + } + observeEvents: [ commandStartedEvent ] + - database: + id: &encryptedDB encryptedDB + client: *client0 + databaseName: &encryptedDBName default + - collection: + id: &encryptedColl encryptedColl + database: *encryptedDB + collectionName: &encryptedCollName default + +initialData: + - databaseName: &keyvaultDBName keyvault + collectionName: &datakeysCollName datakeys + documents: + - {'_id': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'keyMaterial': {'$binary': {'base64': 'HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1648914851981'}}, 'updateDate': {'$date': {'$numberLong': '1648914851981'}}, 'status': {'$numberInt': '0'}, 'masterKey': {'provider': 'local'}} + - databaseName: *encryptedDBName + collectionName: *encryptedCollName + documents: [] + createOptions: + encryptedFields: {'fields': [{'keyId': {'$binary': {'base64': 'EjRWeBI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedIndexed', 'bsonType': 'string', 'queries': {'queryType': 'equality', 'contention': {'$numberLong': '0'}}}, {'keyId': {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}}, 'path': 'encryptedUnindexed', 'bsonType': 'string'}]} + +tests: + - description: "encryptedFieldsMap is preferred over remote encryptedFields" + operations: + # EncryptedFieldsMap overrides remote encryptedFields. + # Automatic encryption does not occur on encryptedUnindexed. The value is validated on the server. + - object: *encryptedColl + name: insertOne + arguments: + document: &doc0 { + _id: 1, + encryptedUnindexed: { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + - object: *encryptedColl + name: find + arguments: + filter: { "_id": 1 } + expectResult: + - {"_id": 1, "encryptedUnindexed": "value123" } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *encryptedDBName + commandName: insert + command: + insert: *encryptedCollName + documents: + - *doc0 + ordered: true + - commandStartedEvent: + databaseName: *encryptedDBName + commandName: find + command: + find: *encryptedCollName + filter: { "_id": 1} + - commandStartedEvent: + databaseName: *keyvaultDBName + commandName: find + command: + find: *datakeysCollName + filter: { + "$or": [ + { + "_id": { + "$in": [ + {'$binary': {'base64': 'q83vqxI0mHYSNBI0VniQEg==', 'subType': '04'}} + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + } + $db: *keyvaultDBName + readConcern: { level: "majority" } + outcome: + - collectionName: *encryptedCollName + databaseName: *encryptedDBName + documents: + - *doc0 \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/unified/getKey.yml b/src/test/spec/json/client-side-encryption/unified/getKey.yml index 149922761..65022dc2a 100644 --- a/src/test/spec/json/client-side-encryption/unified/getKey.yml +++ b/src/test/spec/json/client-side-encryption/unified/getKey.yml @@ -39,7 +39,7 @@ initialData: status: 1 masterKey: provider: aws - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-1 - &local_key_doc _id: &local_key_id { $binary: { base64: bG9jYWxrZXlsb2NhbGtleQ==, subType: "04" } } diff --git a/src/test/spec/json/client-side-encryption/unified/getKeyByAltName.yml b/src/test/spec/json/client-side-encryption/unified/getKeyByAltName.yml index b0cada879..e9da7231e 100644 --- a/src/test/spec/json/client-side-encryption/unified/getKeyByAltName.yml +++ b/src/test/spec/json/client-side-encryption/unified/getKeyByAltName.yml @@ -39,7 +39,7 @@ initialData: status: 1 masterKey: provider: aws - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-1 - &local_key_doc _id: { $binary: { base64: bG9jYWxrZXlsb2NhbGtleQ==, subType: "04" } } diff --git a/src/test/spec/json/client-side-encryption/unified/keyCache.json b/src/test/spec/json/client-side-encryption/unified/keyCache.json new file mode 100644 index 000000000..a39701e28 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/keyCache.json @@ -0,0 +1,198 @@ +{ + "description": "keyCache-explicit", + "schemaVersion": "1.22", + "runOnRequirements": [ + { + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "OCTP9uKPPmvuqpHlqq83gPk4U6rUPxKVRRyVtrjFmVjdoa4Xzm1SzUbr7aIhNI42czkUBmrCtZKF31eaaJnxEBkqf0RFukA9Mo3NEHQWgAQ2cn9duOcRbaFUQo2z0/rB" + } + }, + "keyExpirationMS": 1 + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "keyvault" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "datakeys" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "a+YWzdygTAG62/cNUkqZiQ==", + "subType": "04" + } + }, + "keyAltNames": [], + "keyMaterial": { + "$binary": { + "base64": "iocBkhO3YBokiJ+FtxDTS71/qKXQ7tSWhWbcnFTXBcMjarsepvALeJ5li+SdUd9ePuatjidxAdMo7vh1V2ZESLMkQWdpPJ9PaJjA67gKQKbbbB4Ik5F2uKjULvrMBnFNVRMup4JNUwWFQJpqbfMveXnUVcD06+pUpAkml/f+DSXrV3e5rxciiNVtz03dAG8wJrsKsFXWj6vTjFhsfknyBA==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ] + } + ], + "tests": [ + { + "description": "decrypt, wait, and decrypt again", + "operations": [ + { + "name": "decrypt", + "object": "clientEncryption0", + "arguments": { + "value": { + "$binary": { + "base64": "AWvmFs3coEwButv3DVJKmYkCJ6lUzRX9R28WNlw5uyndb+8gurA+p8q14s7GZ04K2ZvghieRlAr5UwZbow3PMq27u5EIhDDczwBFcbdP1amllw==", + "subType": "06" + } + } + }, + "expectResult": "foobar" + }, + { + "name": "wait", + "object": "testRunner", + "arguments": { + "ms": 50 + } + }, + { + "name": "decrypt", + "object": "clientEncryption0", + "arguments": { + "value": { + "$binary": { + "base64": "AWvmFs3coEwButv3DVJKmYkCJ6lUzRX9R28WNlw5uyndb+8gurA+p8q14s7GZ04K2ZvghieRlAr5UwZbow3PMq27u5EIhDDczwBFcbdP1amllw==", + "subType": "06" + } + } + }, + "expectResult": "foobar" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "a+YWzdygTAG62/cNUkqZiQ==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "a+YWzdygTAG62/cNUkqZiQ==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/keyCache.yml b/src/test/spec/json/client-side-encryption/unified/keyCache.yml new file mode 100644 index 000000000..d6e747ba0 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/keyCache.yml @@ -0,0 +1,85 @@ +description: keyCache-explicit + +schemaVersion: "1.22" + +runOnRequirements: + - csfle: true + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - clientEncryption: + id: &clientEncryption0 clientEncryption0 + clientEncryptionOpts: + keyVaultClient: *client0 + keyVaultNamespace: keyvault.datakeys + kmsProviders: + "local" : { key: "OCTP9uKPPmvuqpHlqq83gPk4U6rUPxKVRRyVtrjFmVjdoa4Xzm1SzUbr7aIhNI42czkUBmrCtZKF31eaaJnxEBkqf0RFukA9Mo3NEHQWgAQ2cn9duOcRbaFUQo2z0/rB" } + keyExpirationMS: 1 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name keyvault + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name datakeys + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { + "_id": { + "$binary": { + "base64": "a+YWzdygTAG62/cNUkqZiQ==", + "subType": "04" + } + }, + "keyAltNames": [], + "keyMaterial": { + "$binary": { + "base64": "iocBkhO3YBokiJ+FtxDTS71/qKXQ7tSWhWbcnFTXBcMjarsepvALeJ5li+SdUd9ePuatjidxAdMo7vh1V2ZESLMkQWdpPJ9PaJjA67gKQKbbbB4Ik5F2uKjULvrMBnFNVRMup4JNUwWFQJpqbfMveXnUVcD06+pUpAkml/f+DSXrV3e5rxciiNVtz03dAG8wJrsKsFXWj6vTjFhsfknyBA==", + "subType": "00" + } + }, + "creationDate": {"$date": {"$numberLong": "1552949630483"}}, + "updateDate": {"$date": {"$numberLong": "1552949630483"}}, + "status": {"$numberInt": "0"}, + "masterKey": {"provider": "local"} + } + +tests: + - description: decrypt, wait, and decrypt again + operations: + - name: decrypt + object: *clientEncryption0 + arguments: + value: { "$binary" : { "base64" : "AWvmFs3coEwButv3DVJKmYkCJ6lUzRX9R28WNlw5uyndb+8gurA+p8q14s7GZ04K2ZvghieRlAr5UwZbow3PMq27u5EIhDDczwBFcbdP1amllw==", "subType" : "06" } } + expectResult: "foobar" + - name: wait + object: testRunner + arguments: + ms: 50 # Wait long enough to account for coarse time resolution on Windows (CDRIVER-4526). + - name: decrypt + object: *clientEncryption0 + arguments: + value: { "$binary" : { "base64" : "AWvmFs3coEwButv3DVJKmYkCJ6lUzRX9R28WNlw5uyndb+8gurA+p8q14s7GZ04K2ZvghieRlAr5UwZbow3PMq27u5EIhDDczwBFcbdP1amllw==", "subType" : "06" } } + expectResult: "foobar" + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: datakeys + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'a+YWzdygTAG62/cNUkqZiQ==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + $db: keyvault + readConcern: { level: "majority" } + - commandStartedEvent: + command: + find: datakeys + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'a+YWzdygTAG62/cNUkqZiQ==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + $db: keyvault + readConcern: { level: "majority" } diff --git a/src/test/spec/json/client-side-encryption/unified/localSchema.json b/src/test/spec/json/client-side-encryption/unified/localSchema.json new file mode 100644 index 000000000..aee323d94 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/localSchema.json @@ -0,0 +1,343 @@ +{ + "description": "localSchema", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "4.1.10", + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "schemaMap": { + "default.default": { + "properties": { + "encrypted_w_altname": { + "encrypt": { + "keyId": "/altname", + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + }, + "random": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string_equivalent": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" + } + }, + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + }, + "sessionToken": { + "$$placeholder": 1 + } + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "client": { + "id": "client1", + "autoEncryptOpts": { + "schemaMap": { + "default.default": { + "properties": { + "test": { + "bsonType": "string" + } + }, + "bsonType": "object", + "required": [ + "test" + ] + } + }, + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + }, + "sessionToken": { + "$$placeholder": 1 + } + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + }, + { + "database": { + "id": "encryptedDB2", + "client": "client1", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl2", + "database": "encryptedDB2", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "status": 1, + "_id": { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + }, + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyAltNames": [ + "altname", + "another_altname" + ] + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [] + } + ], + "tests": [ + { + "description": "A local schema should override", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encrypted_string": "string0" + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "commandName": "find", + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ], + "ordered": true + } + } + }, + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "default", + "filter": { + "_id": 1 + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "default", + "databaseName": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ] + } + ] + }, + { + "description": "A local schema with no encryption is an error", + "operations": [ + { + "object": "encryptedColl2", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + }, + "expectError": { + "isError": true, + "errorContains": "JSON schema keyword 'required' is only allowed with a remote schema" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/localSchema.yml b/src/test/spec/json/client-side-encryption/unified/localSchema.yml new file mode 100644 index 000000000..495b2774e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/localSchema.yml @@ -0,0 +1,103 @@ +description: localSchema + +schemaVersion: "1.23" + +runOnRequirements: + - minServerVersion: "4.1.10" + csfle: true + +createEntities: + - client: + id: &client0 client0 + autoEncryptOpts: + schemaMap: + "default.default": {'properties': {'encrypted_w_altname': {'encrypt': {'keyId': '/altname', 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}, 'random': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Random'}}, 'encrypted_string_equivalent': {'encrypt': {'keyId': [{'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}], 'bsonType': 'string', 'algorithm': 'AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic'}}}, 'bsonType': 'object'} + keyVaultNamespace: keyvault.datakeys + kmsProviders: + aws: { accessKeyId: { $$placeholder: 1 }, secretAccessKey: { $$placeholder: 1 }, sessionToken: { $$placeholder: 1 } } + observeEvents: [ commandStartedEvent ] + - client: + id: &client1 client1 + autoEncryptOpts: + schemaMap: + "default.default": {'properties': {'test': {'bsonType': 'string'}}, 'bsonType': 'object', 'required': ['test']} + keyVaultNamespace: keyvault.datakeys + kmsProviders: + aws: { accessKeyId: { $$placeholder: 1 }, secretAccessKey: { $$placeholder: 1 }, sessionToken: { $$placeholder: 1 } } + observeEvents: [ commandStartedEvent ] + - database: + id: &encryptedDB encryptedDB + client: *client0 + databaseName: &encryptedDBName default + - collection: + id: &encryptedColl encryptedColl + database: *encryptedDB + collectionName: &encryptedCollName default + # intentionally the same DB and collection name as encryptedDB/Coll + - database: + id: &encryptedDB2 encryptedDB2 + client: *client1 + databaseName: *encryptedDBName + - collection: + id: &encryptedColl2 encryptedColl2 + database: *encryptedDB2 + collectionName: *encryptedDBName + +initialData: + - databaseName: &keyvaultDBName keyvault + collectionName: &datakeysCollName datakeys + documents: + - {'status': 1, '_id': {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'masterKey': {'provider': 'aws', 'key': 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', 'region': 'us-east-1'}, 'updateDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyMaterial': {'$binary': {'base64': 'AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyAltNames': ['altname', 'another_altname']} + - databaseName: *encryptedDBName + collectionName: *encryptedCollName + documents: [] + +tests: + - description: "A local schema should override" + operations: + - object: *encryptedColl + name: insertOne + arguments: + document: &doc0 { _id: 1, encrypted_string: "string0" } + - object: *encryptedColl + name: find + arguments: + filter: { _id: 1 } + expectResult: [*doc0] + expectEvents: + # Then key is fetched from the key vault. + - client: *client0 + events: + - commandStartedEvent: + databaseName: *keyvaultDBName + commandName: find + command: + find: *datakeysCollName + filter: {"$or": [{"_id": {"$in": [ {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}} ] }}, {"keyAltNames": {"$in": []}}]} + readConcern: { level: "majority" } + - commandStartedEvent: + commandName: insert + command: + insert: *encryptedCollName + documents: + - &doc0_encrypted { _id: 1, encrypted_string: {'$binary': {'base64': 'AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==', 'subType': '06'}} } + ordered: true + - commandStartedEvent: + commandName: find + command: + find: *encryptedCollName + filter: { _id: 1 } + outcome: + - collectionName: *encryptedCollName + databaseName: *encryptedDBName + documents: + - *doc0_encrypted + - description: "A local schema with no encryption is an error" + operations: + - object: *encryptedColl2 + name: insertOne + arguments: + document: &doc0 { _id: 1, encrypted_string: "string0" } + expectError: + isError: true + errorContains: "JSON schema keyword 'required' is only allowed with a remote schema" diff --git a/src/test/spec/json/client-side-encryption/unified/maxWireVersion.json b/src/test/spec/json/client-side-encryption/unified/maxWireVersion.json new file mode 100644 index 000000000..d0af75ac9 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/maxWireVersion.json @@ -0,0 +1,101 @@ +{ + "description": "maxWireVersion", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99", + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "kmsProviders": { + "aws": {} + }, + "keyVaultNamespace": "keyvault.datakeys", + "extraOptions": { + "mongocryptdBypassSpawn": true + } + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "status": 1, + "_id": { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + }, + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyAltNames": [ + "altname", + "another_altname" + ] + } + ] + } + ], + "tests": [ + { + "description": "operation fails with maxWireVersion < 8", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "encrypted_string": "string0" + } + }, + "expectError": { + "errorContains": "Auto-encryption requires a minimum MongoDB version of 4.2" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/maxWireVersion.yml b/src/test/spec/json/client-side-encryption/unified/maxWireVersion.yml new file mode 100644 index 000000000..75a51dd4e --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/maxWireVersion.yml @@ -0,0 +1,41 @@ +description: maxWireVersion + +schemaVersion: "1.23" + +runOnRequirements: + - maxServerVersion: "4.0.99" + csfle: true + +createEntities: + - client: + id: &client0 client0 + autoEncryptOpts: + kmsProviders: + aws: {} + keyVaultNamespace: keyvault.datakeys + extraOptions: + mongocryptdBypassSpawn: true # mongocryptd probably won't be on the path. mongocryptd was introduced in server 4.2. + - database: + id: &database0 database0 + client: *client0 + databaseName: default + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: default + +initialData: + - databaseName: keyvault + collectionName: datakeys + documents: + - {'status': 1, '_id': {'$binary': {'base64': 'AAAAAAAAAAAAAAAAAAAAAA==', 'subType': '04'}}, 'masterKey': {'provider': 'aws', 'key': 'arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0', 'region': 'us-east-1'}, 'updateDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyMaterial': {'$binary': {'base64': 'AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO', 'subType': '00'}}, 'creationDate': {'$date': {'$numberLong': '1552949630483'}}, 'keyAltNames': ['altname', 'another_altname']} + +tests: + - description: "operation fails with maxWireVersion < 8" + operations: + - name: insertOne + object: *collection0 + arguments: + document: { encrypted_string: "string0" } + expectError: + errorContains: "Auto-encryption requires a minimum MongoDB version of 4.2" \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.json b/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.json new file mode 100644 index 000000000..4d75e4cf5 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.json @@ -0,0 +1,396 @@ +{ + "description": "namedKMS-createDataKey", + "schemaVersion": "1.18", + "runOnRequirements": [ + { + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws:name1": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + } + }, + "azure:name1": { + "tenantId": { + "$$placeholder": 1 + }, + "clientId": { + "$$placeholder": 1 + }, + "clientSecret": { + "$$placeholder": 1 + } + }, + "gcp:name1": { + "email": { + "$$placeholder": 1 + }, + "privateKey": { + "$$placeholder": 1 + } + }, + "kmip:name1": { + "endpoint": { + "$$placeholder": 1 + } + }, + "local:name1": { + "key": { + "$$placeholder": 1 + } + } + } + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "keyvault" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "datakeys" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [] + } + ], + "tests": [ + { + "description": "create data key with named AWS KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "aws:name1", + "opts": { + "masterKey": { + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + } + } + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "create datakey with named Azure KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "azure:name1", + "opts": { + "masterKey": { + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + } + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "create datakey with named GCP KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "gcp:name1", + "opts": { + "masterKey": { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + } + } + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "create datakey with named KMIP KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "kmip:name1" + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "kmip:name1", + "keyId": { + "$$type": "string" + } + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "create datakey with named local KMS provider", + "operations": [ + { + "name": "createDataKey", + "object": "clientEncryption0", + "arguments": { + "kmsProvider": "local:name1" + }, + "expectResult": { + "$$type": "binData" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "insert": "datakeys", + "documents": [ + { + "_id": { + "$$type": "binData" + }, + "keyMaterial": { + "$$type": "binData" + }, + "creationDate": { + "$$type": "date" + }, + "updateDate": { + "$$type": "date" + }, + "status": { + "$$exists": true + }, + "masterKey": { + "provider": "local:name1" + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.yml b/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.yml new file mode 100644 index 000000000..0e3a53b64 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-createDataKey.yml @@ -0,0 +1,175 @@ +description: namedKMS-createDataKey + +schemaVersion: "1.18" + +runOnRequirements: + - csfle: true + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - clientEncryption: + id: &clientEncryption0 clientEncryption0 + clientEncryptionOpts: + keyVaultClient: *client0 + keyVaultNamespace: keyvault.datakeys + kmsProviders: + "aws:name1": { accessKeyId: { $$placeholder: 1 }, secretAccessKey: { $$placeholder: 1 } } + "azure:name1": { tenantId: { $$placeholder: 1 }, clientId: { $$placeholder: 1 }, clientSecret: { $$placeholder: 1 } } + "gcp:name1": { email: { $$placeholder: 1 }, privateKey: { $$placeholder: 1 } } + "kmip:name1": { endpoint: { $$placeholder: 1 } } + "local:name1": { key: { $$placeholder: 1 } } + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name keyvault + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name datakeys + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + +tests: + - description: create data key with named AWS KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: "aws:name1" + opts: + masterKey: &new_aws_masterkey + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + region: us-east-1 + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: "aws:name1" + <<: *new_aws_masterkey + writeConcern: { w: majority } + + - description: create datakey with named Azure KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: "azure:name1" + opts: + masterKey: &new_azure_masterkey + keyVaultEndpoint: key-vault-csfle.vault.azure.net + keyName: key-name-csfle + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: "azure:name1" + <<: *new_azure_masterkey + writeConcern: { w: majority } + + - description: create datakey with named GCP KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: "gcp:name1" + opts: + masterKey: &new_gcp_masterkey + projectId: devprod-drivers + location: global + keyRing: key-ring-csfle + keyName: key-name-csfle + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: "gcp:name1" + <<: *new_gcp_masterkey + writeConcern: { w: majority } + + - description: create datakey with named KMIP KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: "kmip:name1" + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: "kmip:name1" + keyId: { $$type: string } + writeConcern: { w: majority } + + - description: create datakey with named local KMS provider + operations: + - name: createDataKey + object: *clientEncryption0 + arguments: + kmsProvider: "local:name1" + expectResult: { $$type: binData } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + insert: *collection0Name + documents: + - _id: { $$type: binData } + keyMaterial: { $$type: binData } + creationDate: { $$type: date } + updateDate: { $$type: date } + status: { $$exists: true } + masterKey: + provider: "local:name1" + writeConcern: { w: majority } diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.json b/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.json new file mode 100644 index 000000000..e28d7e8b3 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.json @@ -0,0 +1,130 @@ +{ + "description": "namedKMS-explicit", + "schemaVersion": "1.18", + "runOnRequirements": [ + { + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local:name2": { + "key": "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + } + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "keyvault" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "datakeys" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "local+name2+AAAAAAAAAA==", + "subType": "04" + } + }, + "keyAltNames": [ + "local:name2" + ], + "keyMaterial": { + "$binary": { + "base64": "DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local:name2" + } + } + ] + } + ], + "tests": [ + { + "description": "can explicitly encrypt with a named KMS provider", + "operations": [ + { + "name": "encrypt", + "object": "clientEncryption0", + "arguments": { + "value": "foobar", + "opts": { + "keyAltName": "local:name2", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + }, + "expectResult": { + "$binary": { + "base64": "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==", + "subType": "06" + } + } + } + ] + }, + { + "description": "can explicitly decrypt with a named KMS provider", + "operations": [ + { + "name": "decrypt", + "object": "clientEncryption0", + "arguments": { + "value": { + "$binary": { + "base64": "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==", + "subType": "06" + } + } + }, + "expectResult": "foobar" + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.yml b/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.yml new file mode 100644 index 000000000..aa9fb3bc8 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-explicit.yml @@ -0,0 +1,72 @@ +description: namedKMS-explicit + +schemaVersion: "1.18" + +runOnRequirements: + - csfle: true + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - clientEncryption: + id: &clientEncryption0 clientEncryption0 + clientEncryptionOpts: + keyVaultClient: *client0 + keyVaultNamespace: keyvault.datakeys + kmsProviders: + "local:name2" : { key: "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" } + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name keyvault + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name datakeys + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { + "_id": { + "$binary": { + "base64": "local+name2+AAAAAAAAAA==", + "subType": "04" + } + }, + "keyAltNames": ["local:name2"], + "keyMaterial": { + "$binary": { + "base64": "DX3iUuOlBsx6wBX9UZ3v/qXk1HNeBace2J+h/JwsDdF/vmSXLZ1l1VmZYIcpVFy6ODhdbzLjd4pNgg9wcm4etYig62KNkmtZ0/s1tAL5VsuW/s7/3PYnYGznZTFhLjIVcOH/RNoRj2eQb/sRTyivL85wePEpAU/JzuBj6qO9Y5txQgs1k0J3aNy10R9aQ8kC1NuSSpLAIXwE6DlNDDJXhw==", + "subType": "00" + } + }, + "creationDate": {"$date": {"$numberLong": "1552949630483"}}, + "updateDate": {"$date": {"$numberLong": "1552949630483"}}, + "status": {"$numberInt": "0"}, + "masterKey": {"provider": "local:name2"} + } + +tests: + - description: can explicitly encrypt with a named KMS provider + operations: + - name: encrypt + object: *clientEncryption0 + arguments: + value: "foobar" + opts: + keyAltName: "local:name2" + algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + expectResult: { "$binary" : { "base64" : "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==", "subType" : "06" } } + + - description: can explicitly decrypt with a named KMS provider + operations: + - name: decrypt + object: *clientEncryption0 + arguments: + value: { "$binary" : { "base64" : "AZaHGpfp2pntvgAAAAAAAAAC4yX2LTAuN253GAkEO2ZXp4GpCyM7yoVNJMQQl+6uzxMs03IprLC7DL2vr18x9LwOimjTS9YbMJhrnFkEPuNhbg==", "subType" : "06" } } + expectResult: "foobar" + \ No newline at end of file diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.json b/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.json new file mode 100644 index 000000000..b3b9bd247 --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.json @@ -0,0 +1,1385 @@ +{ + "description": "namedKMS-rewrapManyDataKey", + "schemaVersion": "1.18", + "runOnRequirements": [ + { + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws:name1": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + } + }, + "azure:name1": { + "tenantId": { + "$$placeholder": 1 + }, + "clientId": { + "$$placeholder": 1 + }, + "clientSecret": { + "$$placeholder": 1 + } + }, + "gcp:name1": { + "email": { + "$$placeholder": 1 + }, + "privateKey": { + "$$placeholder": 1 + } + }, + "kmip:name1": { + "endpoint": { + "$$placeholder": 1 + } + }, + "local:name1": { + "key": { + "$$placeholder": 1 + } + }, + "local:name2": { + "key": "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + }, + "aws:name2": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + } + } + } + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "keyvault" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "datakeys" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "YXdzYXdzYXdzYXdzYXdzYQ==", + "subType": "04" + } + }, + "keyAltNames": [ + "aws:name1_key" + ], + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gFXJqbF0Fy872MD7xl56D/2AAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDO7HPisPUlGzaio9vgIBEIB7/Qow46PMh/8JbEUbdXgTGhLfXPE+KIVW7T8s6YEMlGiRvMu7TV0QCIUJlSHPKZxzlJ2iwuz5yXeOag+EdY+eIQ0RKrsJ3b8UTisZYzGjfzZnxUKLzLoeXremtRCm3x47wCuHKd1dhh6FBbYt5TL2tDaj+vL2GBrKat2L", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + } + }, + { + "_id": { + "$binary": { + "base64": "YXp1cmVhenVyZWF6dXJlYQ==", + "subType": "04" + } + }, + "keyAltNames": [ + "azure:name1_key" + ], + "keyMaterial": { + "$binary": { + "base64": "pr01l7qDygUkFE/0peFwpnNlv3iIy8zrQK38Q9i12UCN2jwZHDmfyx8wokiIKMb9kAleeY+vnt3Cf1MKu9kcDmI+KxbNDd+V3ytAAGzOVLDJr77CiWjF9f8ntkXRHrAY9WwnVDANYkDwXlyU0Y2GQFTiW65jiQhUtYLYH63Tk48SsJuQvnWw1Q+PzY8ga+QeVec8wbcThwtm+r2IHsCFnc72Gv73qq7weISw+O4mN08z3wOp5FOS2ZM3MK7tBGmPdBcktW7F8ODGsOQ1FU53OrWUnyX2aTi2ftFFFMWVHqQo7EYuBZHru8RRODNKMyQk0BFfKovAeTAVRv9WH9QU7g==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + }, + { + "_id": { + "$binary": { + "base64": "Z2NwZ2NwZ2NwZ2NwZ2NwZw==", + "subType": "04" + } + }, + "keyAltNames": [ + "gcp:name1_key" + ], + "keyMaterial": { + "$binary": { + "base64": "CiQAIgLj0USbQtof/pYRLQO96yg/JEtZbD1UxKueaC37yzT5tTkSiQEAhClWB5ZCSgzHgxv8raWjNB4r7e8ePGdsmSuYTYmLC5oHHS/BdQisConzNKFaobEQZHamTCjyhy5NotKF8MWoo+dyfQApwI29+vAGyrUIQCXzKwRnNdNQ+lb3vJtS5bqvLTvSxKHpVca2kqyC9nhonV+u4qru5Q2bAqUgVFc8fL4pBuvlowZFTQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + } + }, + { + "_id": { + "$binary": { + "base64": "a21pcGttaXBrbWlwa21pcA==", + "subType": "04" + } + }, + "keyAltNames": [ + "kmip:name1_key" + ], + "keyMaterial": { + "$binary": { + "base64": "CklVctHzke4mcytd0TxGqvepkdkQN8NUF4+jV7aZQITAKdz6WjdDpq3lMt9nSzWGG2vAEfvRb3mFEVjV57qqGqxjq2751gmiMRHXz0btStbIK3mQ5xbY9kdye4tsixlCryEwQONr96gwlwKKI9Nubl9/8+uRF6tgYjje7Q7OjauEf1SrJwKcoQ3WwnjZmEqAug0kImCpJ/irhdqPzivRiA==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "kmip:name1", + "keyId": "1" + } + }, + { + "_id": { + "$binary": { + "base64": "bG9jYWxrZXlsb2NhbGtleQ==", + "subType": "04" + } + }, + "keyAltNames": [ + "local:name1_key" + ], + "keyMaterial": { + "$binary": { + "base64": "ABKBldDEoDW323yejOnIRk6YQmlD9d3eQthd16scKL75nz2LjNL9fgPDZWrFFOlqlhMCFaSrNJfGrFUjYk5JFDO7soG5Syb50k1niJoKg4ilsj0L4mpimFUtTpOr2nzZOeQtvAksEXc7gsFgq8gV7t/U3lsaXPY7I0t42DfSE8EGlPdxRjFdHnxh+OR8h7U9b8Qs5K5UuhgyeyxaBZ1Hgw==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "local:name1" + } + } + ] + } + ], + "tests": [ + { + "description": "rewrap to aws:name1", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "aws:name1_key" + } + }, + "opts": { + "provider": "aws:name1", + "masterKey": { + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + } + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "aws:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws:name1", + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap to azure:name1", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "azure:name1_key" + } + }, + "opts": { + "provider": "azure:name1", + "masterKey": { + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + } + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "azure:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "azure:name1", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap to gcp:name1", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "gcp:name1_key" + } + }, + "opts": { + "provider": "gcp:name1", + "masterKey": { + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + } + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "gcp:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "gcp:name1", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap to kmip:name1", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "kmip:name1_key" + } + }, + "opts": { + "provider": "kmip:name1" + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "kmip:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip:name1", + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip:name1", + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip:name1", + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip:name1", + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap to local:name1", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "local:name1_key" + } + }, + "opts": { + "provider": "local:name1" + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "local:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local:name1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local:name1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local:name1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local:name1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap from local:name1 to local:name2", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$eq": "local:name1_key" + } + }, + "opts": { + "provider": "local:name2" + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$eq": "local:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local:name2" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, + { + "description": "rewrap from aws:name1 to aws:name2", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$eq": "aws:name1_key" + } + }, + "opts": { + "provider": "aws:name2", + "masterKey": { + "key": "arn:aws:kms:us-east-1:857654397073:key/0f8468f0-f135-4226-aa0b-bd05c4c30df5", + "region": "us-east-1" + } + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$eq": "aws:name1_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws:name2", + "key": "arn:aws:kms:us-east-1:857654397073:key/0f8468f0-f135-4226-aa0b-bd05c4c30df5", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.yml b/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.yml new file mode 100644 index 000000000..b776ed4dd --- /dev/null +++ b/src/test/spec/json/client-side-encryption/unified/namedKMS-rewrapManyDataKey.yml @@ -0,0 +1,432 @@ +description: namedKMS-rewrapManyDataKey + +schemaVersion: "1.18" + +runOnRequirements: + - csfle: true + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - clientEncryption: + id: &clientEncryption0 clientEncryption0 + clientEncryptionOpts: + keyVaultClient: *client0 + keyVaultNamespace: keyvault.datakeys + kmsProviders: + "aws:name1": { accessKeyId: { $$placeholder: 1 }, secretAccessKey: { $$placeholder: 1 } } + "azure:name1": { tenantId: { $$placeholder: 1 }, clientId: { $$placeholder: 1 }, clientSecret: { $$placeholder: 1 } } + "gcp:name1": { email: { $$placeholder: 1 }, privateKey: { $$placeholder: 1 } } + "kmip:name1": { endpoint: { $$placeholder: 1 } } + "local:name1": { key: { $$placeholder: 1 } } + # Use a different local master key for `local:name2`. + "local:name2": { key: "local+name2+YUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" } + # Use a different AWS account to test wrapping between different AWS accounts. + "aws:name2": { accessKeyId: { $$placeholder: 1 }, secretAccessKey: { $$placeholder: 1 } } + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name keyvault + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name datakeys + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - _id: &aws:name1_key_id { $binary: { base64: YXdzYXdzYXdzYXdzYXdzYQ==, subType: "04" } } + keyAltNames: ["aws:name1_key"] + keyMaterial: { $binary: { base64: AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gFXJqbF0Fy872MD7xl56D/2AAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDO7HPisPUlGzaio9vgIBEIB7/Qow46PMh/8JbEUbdXgTGhLfXPE+KIVW7T8s6YEMlGiRvMu7TV0QCIUJlSHPKZxzlJ2iwuz5yXeOag+EdY+eIQ0RKrsJ3b8UTisZYzGjfzZnxUKLzLoeXremtRCm3x47wCuHKd1dhh6FBbYt5TL2tDaj+vL2GBrKat2L, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &aws:name1_masterkey + provider: aws:name1 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" + region: us-east-1 + - _id: &azure:name1_key_id { $binary: { base64: YXp1cmVhenVyZWF6dXJlYQ==, subType: "04" } } + keyAltNames: ["azure:name1_key"] + keyMaterial: { $binary: { base64: pr01l7qDygUkFE/0peFwpnNlv3iIy8zrQK38Q9i12UCN2jwZHDmfyx8wokiIKMb9kAleeY+vnt3Cf1MKu9kcDmI+KxbNDd+V3ytAAGzOVLDJr77CiWjF9f8ntkXRHrAY9WwnVDANYkDwXlyU0Y2GQFTiW65jiQhUtYLYH63Tk48SsJuQvnWw1Q+PzY8ga+QeVec8wbcThwtm+r2IHsCFnc72Gv73qq7weISw+O4mN08z3wOp5FOS2ZM3MK7tBGmPdBcktW7F8ODGsOQ1FU53OrWUnyX2aTi2ftFFFMWVHqQo7EYuBZHru8RRODNKMyQk0BFfKovAeTAVRv9WH9QU7g==, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &azure:name1_masterkey + provider: "azure:name1" + keyVaultEndpoint: key-vault-csfle.vault.azure.net + keyName: key-name-csfle + - _id: &gcp:name1_key_id { $binary: { base64: Z2NwZ2NwZ2NwZ2NwZ2NwZw==, subType: "04" } } + keyAltNames: ["gcp:name1_key"] + keyMaterial: { $binary: { base64: CiQAIgLj0USbQtof/pYRLQO96yg/JEtZbD1UxKueaC37yzT5tTkSiQEAhClWB5ZCSgzHgxv8raWjNB4r7e8ePGdsmSuYTYmLC5oHHS/BdQisConzNKFaobEQZHamTCjyhy5NotKF8MWoo+dyfQApwI29+vAGyrUIQCXzKwRnNdNQ+lb3vJtS5bqvLTvSxKHpVca2kqyC9nhonV+u4qru5Q2bAqUgVFc8fL4pBuvlowZFTQ==, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &gcp:name1_masterkey + provider: "gcp:name1" + projectId: devprod-drivers + location: global + keyRing: key-ring-csfle + keyName: key-name-csfle + - _id: &kmip:name1_key_id { $binary: { base64: a21pcGttaXBrbWlwa21pcA==, subType: "04" } } + keyAltNames: ["kmip:name1_key"] + keyMaterial: { $binary: { base64: CklVctHzke4mcytd0TxGqvepkdkQN8NUF4+jV7aZQITAKdz6WjdDpq3lMt9nSzWGG2vAEfvRb3mFEVjV57qqGqxjq2751gmiMRHXz0btStbIK3mQ5xbY9kdye4tsixlCryEwQONr96gwlwKKI9Nubl9/8+uRF6tgYjje7Q7OjauEf1SrJwKcoQ3WwnjZmEqAug0kImCpJ/irhdqPzivRiA==, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &kmip:name1_masterkey + provider: "kmip:name1" + keyId: "1" + - _id: &local:name1_key_id { $binary: { base64: bG9jYWxrZXlsb2NhbGtleQ==, subType: "04" } } + keyAltNames: ["local:name1_key"] + keyMaterial: { $binary: { base64: ABKBldDEoDW323yejOnIRk6YQmlD9d3eQthd16scKL75nz2LjNL9fgPDZWrFFOlqlhMCFaSrNJfGrFUjYk5JFDO7soG5Syb50k1niJoKg4ilsj0L4mpimFUtTpOr2nzZOeQtvAksEXc7gsFgq8gV7t/U3lsaXPY7I0t42DfSE8EGlPdxRjFdHnxh+OR8h7U9b8Qs5K5UuhgyeyxaBZ1Hgw==, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &local:name1_masterkey + provider: "local:name1" + +tests: + - description: "rewrap to aws:name1" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: "aws:name1_key" } } + opts: + provider: "aws:name1" + # Different key: 89fcc2c4-08b0-4bd9-9f25-e30687b580d0 -> 061334ae-07a8-4ceb-a813-8135540e837d. + masterKey: &new_aws_masterkey + key: "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d" + region: us-east-1 + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: "aws:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "aws:name1", <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "aws:name1", <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "aws:name1", <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "aws:name1", <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap to azure:name1" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: azure:name1_key } } + opts: + provider: "azure:name1" + masterKey: &new_azure_masterkey + keyVaultEndpoint: key-vault-csfle.vault.azure.net + keyName: key-name-csfle + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: azure:name1_key } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "azure:name1", <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "azure:name1", <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "azure:name1", <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "azure:name1", <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap to gcp:name1" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: gcp:name1_key } } + opts: + provider: "gcp:name1" + masterKey: &new_gcp_masterkey + projectId: devprod-drivers + location: global + keyRing: key-ring-csfle + keyName: key-name-csfle + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: "gcp:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "gcp:name1", <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "gcp:name1", <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "gcp:name1", <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "gcp:name1", <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap to kmip:name1" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: "kmip:name1_key" } } + opts: + provider: "kmip:name1" + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: "kmip:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "kmip:name1", keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "kmip:name1", keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "kmip:name1", keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "kmip:name1", keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap to local:name1" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: "local:name1_key" } } + opts: + provider: "local:name1" + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: "local:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "local:name1" }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "local:name1" }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "local:name1" }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "local:name1" }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap from local:name1 to local:name2" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $eq: "local:name1_key" } } + opts: + provider: "local:name2" + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $eq: "local:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "local:name2" }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap from aws:name1 to aws:name2" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $eq: "aws:name1_key" } } + opts: + provider: "aws:name2" + masterKey: &new_awsname2_masterkey + # aws:name1 account does not have permissions to access this key. + key: "arn:aws:kms:us-east-1:857654397073:key/0f8468f0-f135-4226-aa0b-bd05c4c30df5" + region: us-east-1 + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $eq: "aws:name1_key" } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: "aws:name2", <<: *new_awsname2_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } diff --git a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-decrypt_failure.yml b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-decrypt_failure.yml index 3a6e52188..d61912ecf 100644 --- a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-decrypt_failure.yml +++ b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-decrypt_failure.yml @@ -43,7 +43,7 @@ initialData: masterKey: provider: aws # "us-east-1" changed to "us-east-2" in both key and region. - key: arn:aws:kms:us-east-2:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-2:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-2 tests: diff --git a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-encrypt_failure.yml b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-encrypt_failure.yml index b947c8ce7..0ec3ebb56 100644 --- a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-encrypt_failure.yml +++ b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey-encrypt_failure.yml @@ -54,7 +54,7 @@ tests: provider: aws masterKey: # "us-east-1" changed to "us-east-2" in both key and region. - key: arn:aws:kms:us-east-2:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-2:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-2 expectError: isClientError: true diff --git a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.json b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.json index 6b3c9664a..8803491db 100644 --- a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.json +++ b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.json @@ -246,6 +246,36 @@ "masterKey": { "provider": "local" } + }, + { + "_id": { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba5" + }, + "keyAltNames": [ + "kmip_delegated_key" + ], + "keyMaterial": { + "$binary": { + "base64": "5TLMFWlguBWe5GUESTvOVtkdBsCrynhnV72XRyZ66/nk+EP9/1oEp1t1sg0+vwCTqULHjBiUE6DRx2mYD/Eup1+u2Jgz9/+1sV1drXeOPALNPkSgiZiDbIb67zRi+wTABEcKcegJH+FhmSGxwUoQAiHCsCbcvia5P8tN1lt98YQ=", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "kmip", + "keyId": "11", + "delegated": true + } } ] } @@ -317,8 +347,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 4, - "modifiedCount": 4, + "matchedCount": 5, + "modifiedCount": 5, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -440,6 +470,34 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d", + "region": "us-east-1" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { @@ -502,8 +560,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 4, - "modifiedCount": 4, + "matchedCount": 5, + "modifiedCount": 5, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -625,6 +683,34 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "azure", + "keyVaultEndpoint": "key-vault-csfle.vault.azure.net", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { @@ -689,8 +775,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 4, - "modifiedCount": 4, + "matchedCount": 5, + "modifiedCount": 5, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -818,6 +904,36 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "gcp", + "projectId": "devprod-drivers", + "location": "global", + "keyRing": "key-ring-csfle", + "keyName": "key-name-csfle" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { @@ -878,8 +994,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 4, - "modifiedCount": 4, + "matchedCount": 5, + "modifiedCount": 5, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -1004,6 +1120,35 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { @@ -1044,6 +1189,228 @@ } ] }, + { + "description": "rewrap with new KMIP delegated KMS provider", + "operations": [ + { + "name": "rewrapManyDataKey", + "object": "clientEncryption0", + "arguments": { + "filter": { + "keyAltNames": { + "$ne": "kmip_delegated_key" + } + }, + "opts": { + "provider": "kmip", + "masterKey": { + "delegated": true + } + } + }, + "expectResult": { + "bulkWriteResult": { + "insertedCount": 0, + "matchedCount": 5, + "modifiedCount": 5, + "deletedCount": 0, + "upsertedCount": 0, + "upsertedIds": {}, + "insertedIds": { + "$$unsetOrMatches": {} + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "find": "datakeys", + "filter": { + "keyAltNames": { + "$ne": "kmip_delegated_key" + } + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "command": { + "update": "datakeys", + "ordered": true, + "updates": [ + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "kmip", + "delegated": true, + "keyId": { + "$$type": "string" + } + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + ] + }, { "description": "rewrap with new local KMS provider", "operations": [ @@ -1063,8 +1430,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 4, - "modifiedCount": 4, + "matchedCount": 5, + "modifiedCount": 5, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -1180,6 +1547,32 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "provider": "local" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { @@ -1229,8 +1622,8 @@ "expectResult": { "bulkWriteResult": { "insertedCount": 0, - "matchedCount": 5, - "modifiedCount": 5, + "matchedCount": 6, + "modifiedCount": 6, "deletedCount": 0, "upsertedCount": 0, "upsertedIds": {}, @@ -1294,6 +1687,16 @@ "keyName": "key-name-csfle" } }, + { + "_id": { + "$uuid": "7411e9af-c688-4df7-8143-5e60ae96cba5" + }, + "masterKey": { + "provider": "kmip", + "keyId": "11", + "delegated": true + } + }, { "_id": { "$binary": { @@ -1447,6 +1850,32 @@ "$$unsetOrMatches": false } }, + { + "q": { + "_id": { + "$$type": "binData" + } + }, + "u": { + "$set": { + "masterKey": { + "$$type": "object" + }, + "keyMaterial": { + "$$type": "binData" + } + }, + "$currentDate": { + "updateDate": true + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, { "q": { "_id": { diff --git a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.yml b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.yml index cc20e1b17..1c78af7e7 100644 --- a/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.yml +++ b/src/test/spec/json/client-side-encryption/unified/rewrapManyDataKey.yml @@ -46,7 +46,7 @@ initialData: status: 1 masterKey: &aws_masterkey provider: aws - key: arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0 + key: "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0" region: us-east-1 - _id: &azure_key_id { $binary: { base64: YXp1cmVhenVyZWF6dXJlYQ==, subType: "04" } } keyAltNames: ["azure_key"] @@ -87,6 +87,16 @@ initialData: status: 1 masterKey: &local_masterkey provider: local + - _id: &kmip_delegated_key_id {$uuid: "7411e9af-c688-4df7-8143-5e60ae96cba5"} + keyAltNames: ["kmip_delegated_key"] + keyMaterial: { $binary: { base64: 5TLMFWlguBWe5GUESTvOVtkdBsCrynhnV72XRyZ66/nk+EP9/1oEp1t1sg0+vwCTqULHjBiUE6DRx2mYD/Eup1+u2Jgz9/+1sV1drXeOPALNPkSgiZiDbIb67zRi+wTABEcKcegJH+FhmSGxwUoQAiHCsCbcvia5P8tN1lt98YQ=, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: &kmip_delegated_masterkey + provider: kmip + keyId: "11" + delegated: true tests: - description: "no keys to rewrap due to no filter matches" @@ -120,13 +130,13 @@ tests: provider: aws # Different key: 89fcc2c4-08b0-4bd9-9f25-e30687b580d0 -> 061334ae-07a8-4ceb-a813-8135540e837d. masterKey: &new_aws_masterkey - key: arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d + key: "arn:aws:kms:us-east-1:579766882180:key/061334ae-07a8-4ceb-a813-8135540e837d" region: us-east-1 expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 4 - modifiedCount: 4 + matchedCount: 5 + modifiedCount: 5 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -162,6 +172,10 @@ tests: u: { $set: { masterKey: { provider: aws, <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: aws, <<: *new_aws_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - description: "rewrap with new Azure KMS provider" @@ -178,8 +192,8 @@ tests: expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 4 - modifiedCount: 4 + matchedCount: 5 + modifiedCount: 5 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -215,6 +229,10 @@ tests: u: { $set: { masterKey: { provider: azure, <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: azure, <<: *new_azure_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - description: "rewrap with new GCP KMS provider" @@ -233,8 +251,8 @@ tests: expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 4 - modifiedCount: 4 + matchedCount: 5 + modifiedCount: 5 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -270,6 +288,10 @@ tests: u: { $set: { masterKey: { provider: gcp, <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: gcp, <<: *new_gcp_masterkey }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - description: "rewrap with new KMIP KMS provider" @@ -283,8 +305,8 @@ tests: expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 4 - modifiedCount: 4 + matchedCount: 5 + modifiedCount: 5 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -320,6 +342,66 @@ tests: u: { $set: { masterKey: { provider: kmip, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + writeConcern: { w: majority } + + - description: "rewrap with new KMIP delegated KMS provider" + operations: + - name: rewrapManyDataKey + object: *clientEncryption0 + arguments: + filter: { keyAltNames: { $ne: kmip_delegated_key } } + opts: + provider: kmip + masterKey: + delegated: true + expectResult: + bulkWriteResult: + insertedCount: 0 + matchedCount: 5 + modifiedCount: 5 + deletedCount: 0 + upsertedCount: 0 + upsertedIds: {} + insertedIds: { $$unsetOrMatches: {} } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + databaseName: *database0Name + command: + find: *collection0Name + filter: { keyAltNames: { $ne: kmip_delegated_key } } + readConcern: { level: majority } + - commandStartedEvent: + databaseName: *database0Name + command: + update: *collection0Name + ordered: true + updates: + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, delegated: true, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, delegated: true, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, delegated: true, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, delegated: true, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: kmip, delegated: true, keyId: { $$type: string } }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - description: "rewrap with new local KMS provider" @@ -333,8 +415,8 @@ tests: expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 4 - modifiedCount: 4 + matchedCount: 5 + modifiedCount: 5 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -370,6 +452,10 @@ tests: u: { $set: { masterKey: { provider: local }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { provider: local }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - description: "rewrap with current KMS provider" @@ -381,8 +467,8 @@ tests: expectResult: bulkWriteResult: insertedCount: 0 - matchedCount: 5 - modifiedCount: 5 + matchedCount: 6 + modifiedCount: 6 deletedCount: 0 upsertedCount: 0 upsertedIds: {} @@ -397,6 +483,7 @@ tests: - { _id: *aws_key_id, masterKey: *aws_masterkey } - { _id: *azure_key_id, masterKey: *azure_masterkey } - { _id: *gcp_key_id, masterKey: *gcp_masterkey } + - { _id: *kmip_delegated_key_id, masterKey: *kmip_delegated_masterkey } - { _id: *kmip_key_id, masterKey: *kmip_masterkey } - { _id: *local_key_id, masterKey: *local_masterkey } expectEvents: @@ -434,5 +521,9 @@ tests: u: { $set: { masterKey: { $$type: object }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } multi: { $$unsetOrMatches: false } upsert: { $$unsetOrMatches: false } + - q: { _id: { $$type: binData } } + u: { $set: { masterKey: { $$type: object }, keyMaterial: { $$type: binData } }, $currentDate: { updateDate: true } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } writeConcern: { w: majority } - commandStartedEvent: { commandName: find } diff --git a/src/test/spec/json/collection-management/modifyCollection-errorResponse.json b/src/test/spec/json/collection-management/modifyCollection-errorResponse.json new file mode 100644 index 000000000..aea71eb08 --- /dev/null +++ b/src/test/spec/json/collection-management/modifyCollection-errorResponse.json @@ -0,0 +1,118 @@ +{ + "description": "modifyCollection-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "collMod-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "collMod-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 1 + } + ] + } + ], + "tests": [ + { + "description": "modifyCollection prepareUnique violations are accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.2" + } + ], + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + } + } + }, + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "index": { + "keyPattern": { + "x": 1 + }, + "prepareUnique": true + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 1 + } + }, + "expectError": { + "errorCode": 11000 + } + }, + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "index": { + "keyPattern": { + "x": 1 + }, + "unique": true + } + }, + "expectError": { + "isClientError": false, + "errorCode": 359, + "errorResponse": { + "violations": [ + { + "ids": [ + 1, + 2 + ] + } + ] + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/collection-management/modifyCollection-errorResponse.yml b/src/test/spec/json/collection-management/modifyCollection-errorResponse.yml new file mode 100644 index 000000000..e61a01211 --- /dev/null +++ b/src/test/spec/json/collection-management/modifyCollection-errorResponse.yml @@ -0,0 +1,59 @@ +description: "modifyCollection-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name collMod-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "modifyCollection prepareUnique violations are accessible" + runOnRequirements: + - minServerVersion: "5.2" # SERVER-61158 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + prepareUnique: true + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 1 } + expectError: + errorCode: 11000 # DuplicateKey + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + index: + keyPattern: { x: 1 } + unique: true + expectError: + isClientError: false + errorCode: 359 # CannotConvertIndexToUnique + errorResponse: + violations: + - { ids: [ 1, 2 ] } diff --git a/src/test/spec/json/collection-management/timeseries-collection.json b/src/test/spec/json/collection-management/timeseries-collection.json index b5638fd36..8525056fd 100644 --- a/src/test/spec/json/collection-management/timeseries-collection.json +++ b/src/test/spec/json/collection-management/timeseries-collection.json @@ -250,6 +250,71 @@ ] } ] + }, + { + "description": "createCollection with bucketing options", + "runOnRequirements": [ + { + "minServerVersion": "7.0" + } + ], + "operations": [ + { + "name": "dropCollection", + "object": "database0", + "arguments": { + "collection": "test" + } + }, + { + "name": "createCollection", + "object": "database0", + "arguments": { + "collection": "test", + "timeseries": { + "timeField": "time", + "bucketMaxSpanSeconds": 3600, + "bucketRoundingSeconds": 3600 + } + } + }, + { + "name": "assertCollectionExists", + "object": "testRunner", + "arguments": { + "databaseName": "ts-tests", + "collectionName": "test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test" + }, + "databaseName": "ts-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "test", + "timeseries": { + "timeField": "time", + "bucketMaxSpanSeconds": 3600, + "bucketRoundingSeconds": 3600 + } + }, + "databaseName": "ts-tests" + } + } + ] + } + ] } ] } diff --git a/src/test/spec/json/collection-management/timeseries-collection.yml b/src/test/spec/json/collection-management/timeseries-collection.yml index cbc09b34c..00f22709b 100644 --- a/src/test/spec/json/collection-management/timeseries-collection.yml +++ b/src/test/spec/json/collection-management/timeseries-collection.yml @@ -127,3 +127,38 @@ tests: filter: {} sort: { time: 1 } databaseName: *database0Name + + - description: "createCollection with bucketing options" + runOnRequirements: + - minServerVersion: "7.0" + operations: + - name: dropCollection + object: *database0 + arguments: + collection: *collection0Name + - name: createCollection + object: *database0 + arguments: + collection: *collection0Name + timeseries: ×eries1 + timeField: "time" + bucketMaxSpanSeconds: 3600 + bucketRoundingSeconds: 3600 + - name: assertCollectionExists + object: testRunner + arguments: + databaseName: *database0Name + collectionName: *collection0Name + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + drop: *collection0Name + databaseName: *database0Name + - commandStartedEvent: + command: + create: *collection0Name + timeseries: *timeseries1 + databaseName: *database0Name + diff --git a/src/test/spec/json/command-logging-and-monitoring/README.md b/src/test/spec/json/command-logging-and-monitoring/README.md new file mode 100644 index 000000000..c6159699d --- /dev/null +++ b/src/test/spec/json/command-logging-and-monitoring/README.md @@ -0,0 +1,59 @@ +# Command Logging and Monitoring + +______________________________________________________________________ + +## Testing + +### Automated Tests + +There are tests in the [Unified Test Format](../../unified-test-format/unified-test-format.md) for both logging and +monitoring in `/logging` and `monitoring`, respectively. Drivers MUST run the logging tests with their max document +length setting (as described in the [logging specification](../../logging/logging.md#configurable-max-document-length)) +set to a large value e.g. 10,000; this is necessary in order for the driver to emit the full server reply (and to allow +matching against that reply) on certain MongoDB versions and topologies. + +### Prose Tests + +Drivers MUST implement the following logging prose tests. These tests require the ability to capture log message data in +a structured form as described in the +[Unified Test Format specification](../../unified-test-format/unified-test-format.md#expectedLogMessage). + +Note: the following tests mention string "length"; this refers to length in terms of whatever unit the driver has chosen +to support for specifying max document length as discussed in the +[logging specification](../../logging/logging.md#configurable-max-document-length). + +*Test 1: Default truncation limit* + +1. Configure logging with a minimum severity level of "debug" for the "command" component. Do not explicitly configure + the max document length. +2. Construct an array `docs` containing the document `{"x" : "y"}` repeated 100 times. +3. Insert `docs` to a collection via `insertMany`. +4. Inspect the resulting "command started" log message and assert that the "command" value is a string of length 1000 + + (length of trailing ellipsis). +5. Inspect the resulting "command succeeded" log message and assert that the "reply" value is a string of length \<= + 1000 + (length of trailing ellipsis). +6. Run `find()` on the collection where the document was inserted. +7. Inspect the resulting "command succeeded" log message and assert that the reply is a string of length 1000 + (length + of trailing ellipsis). + +*Test 2: Explicitly configured truncation limit* + +1. Configure logging with a minimum severity level of "debug" for the "command" component. Set the max document length + to 5. +2. Run the command `{"hello": true}`. +3. Inspect the resulting "command started" log message and assert that the "command" value is a string of length 5 + + (length of trailing ellipsis). +4. Inspect the resulting "command succeeded" log message and assert that the "reply" value is a string of length 5 + + (length of trailing ellipsis). +5. If the driver attaches raw server responses to failures and can access these via log messages to assert on, run the + command `{"notARealCommand": true}`. Inspect the resulting "command failed" log message and confirm that the server + error is a string of length 5 + (length of trailing ellipsis). + +*Test 3: Truncation with multi-byte codepoints* + +A specific test case is not provided here due to the allowed variations in truncation logic as well as varying extended +JSON whitespace usage. Drivers MUST write language-specific tests that confirm truncation of commands, replies, and (if +applicable) server responses included in error messages work as expected when the data being truncated includes +multi-byte Unicode codepoints. If the driver uses anything other than Unicode codepoints as the unit for max document +length, there also MUST be tests confirming that cases where the max length falls in the middle of a multi-byte +codepoint are handled gracefully. diff --git a/src/test/spec/json/command-logging-and-monitoring/README.rst b/src/test/spec/json/command-logging-and-monitoring/README.rst deleted file mode 100644 index e3687be67..000000000 --- a/src/test/spec/json/command-logging-and-monitoring/README.rst +++ /dev/null @@ -1,60 +0,0 @@ -.. role:: javascript(code) - :language: javascript - -============================== -Command Logging and Monitoring -============================== - -.. contents:: - --------- - -Testing -======= - -Automated Tests -^^^^^^^^^^^^^^^ -There are tests in the `Unified Test Format <../../unified-test-format/unified-test-format.rst>`__ for both logging and -monitoring in `/logging <./logging>`_ and `/monitoring <./monitoring>`_, respectively. Drivers MUST run the logging -tests with their max document length setting (as described in the -`logging specification <../../logging/logging.rst#configurable-max-document-length>`__) set to a large value e.g. 10,000; -this is necessary in order for the driver to emit the full server reply (and to allow matching against that reply) on -certain MongoDB versions and topologies. - -Prose Tests -^^^^^^^^^^^ -Drivers MUST implement the following logging prose tests. These tests require the ability to capture log message data in a -structured form as described in the -`Unified Test Format specification <../../unified-test-format/unified-test-format.rst#expectedLogMessage>`__. - -Note: the following tests mention string "length"; this refers to length in terms of whatever unit the driver has chosen -to support for specifying max document length as discussed in the -`logging specification <../../logging/logging.rst#configurable-max-document-length>`__. - -*Test 1: Default truncation limit* - -1. Configure logging with a minimum severity level of "debug" for the "command" component. Do not explicitly configure the max document length. -2. Construct an array ``docs`` containing the document ``{"x" : "y"}`` repeated 100 times. -3. Insert ``docs`` to a collection via ``insertMany``. -4. Inspect the resulting "command started" log message and assert that the "command" value is a string of length 1000 + (length of trailing ellipsis). -5. Inspect the resulting "command succeeded" log message and assert that the "reply" value is a string of length <= 1000 + (length of trailing ellipsis). -6. Run ``find()`` on the collection where the document was inserted. -7. Inspect the resulting "command succeeded" log message and assert that the reply is a string of length 1000 + (length of trailing ellipsis). - -*Test 2: Explicitly configured truncation limit* - -1. Configure logging with a minimum severity level of "debug" for the "command" component. Set the max document length to 5. -2. Run the command ``{"hello": true}``. -3. Inspect the resulting "command started" log message and assert that the "command" value is a string of length 5 + (length of trailing ellipsis). -4. Inspect the resulting "command succeeded" log message and assert that the "reply" value is a string of length 5 + (length of trailing ellipsis). -5. If the driver attaches raw server responses to failures and can access these via log messages to assert on, run the command - ``{"notARealCommand": true}``. Inspect the resulting "command failed" log message and confirm that the server error is - a string of length 5 + (length of trailing ellipsis). - -*Test 3: Truncation with multi-byte codepoints* - -A specific test case is not provided here due to the allowed variations in truncation logic as well as varying extended JSON whitespace usage. -Drivers MUST write language-specific tests that confirm truncation of commands, replies, and (if applicable) server responses included in error -messages work as expected when the data being truncated includes multi-byte Unicode codepoints. -If the driver uses anything other than Unicode codepoints as the unit for max document length, there also MUST be tests confirming that cases -where the max length falls in the middle of a multi-byte codepoint are handled gracefully. diff --git a/src/test/spec/json/command-logging-and-monitoring/logging/command.json b/src/test/spec/json/command-logging-and-monitoring/logging/command.json index 3d5c2570b..d2970df69 100644 --- a/src/test/spec/json/command-logging-and-monitoring/logging/command.json +++ b/src/test/spec/json/command-logging-and-monitoring/logging/command.json @@ -93,6 +93,7 @@ "component": "command", "data": { "message": "Command succeeded", + "databaseName": "logging-tests", "commandName": "ping", "reply": { "$$type": "string" @@ -177,6 +178,7 @@ "component": "command", "data": { "message": "Command failed", + "databaseName": "logging-tests", "commandName": "find", "failure": { "$$exists": true diff --git a/src/test/spec/json/command-logging-and-monitoring/logging/command.yml b/src/test/spec/json/command-logging-and-monitoring/logging/command.yml index b21a4c609..3e3410b06 100644 --- a/src/test/spec/json/command-logging-and-monitoring/logging/command.yml +++ b/src/test/spec/json/command-logging-and-monitoring/logging/command.yml @@ -52,13 +52,14 @@ tests: component: command data: message: "Command succeeded" + databaseName: *databaseName commandName: *commandName reply: { $$type: string } requestId: { $$type: [int, long] } serverHost: { $$type: string } serverPort: { $$type: [int, long] } durationMS: { $$type: [double, int, long] } - + - description: "A failed command" operations: - name: &commandName find @@ -85,10 +86,10 @@ tests: component: command data: message: "Command failed" + databaseName: *databaseName commandName: *commandName failure: { $$exists: true } requestId: { $$type: [int, long] } serverHost: { $$type: string } serverPort: { $$type: [int, long] } durationMS: { $$type: [double, int, long] } - diff --git a/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.json b/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.json index 21e247df6..0d33c020d 100644 --- a/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.json +++ b/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.json @@ -1,10 +1,11 @@ { "description": "unacknowledged-write", - "schemaVersion": "1.13", + "schemaVersion": "1.16", "createEntities": [ { "client": { "id": "client", + "useMultipleMongoses": false, "observeLogMessages": { "command": "debug" } @@ -53,11 +54,27 @@ "_id": 2 } } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] } ], "expectLogMessages": [ { "client": "client", + "ignoreExtraMessages": true, "messages": [ { "level": "debug", diff --git a/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.yml b/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.yml index 7d4847e0d..6409fb408 100644 --- a/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.yml +++ b/src/test/spec/json/command-logging-and-monitoring/logging/unacknowledged-write.yml @@ -1,10 +1,11 @@ description: "unacknowledged-write" -schemaVersion: "1.13" +schemaVersion: "1.16" createEntities: - client: id: &client client + useMultipleMongoses: false observeLogMessages: command: debug - database: @@ -31,8 +32,18 @@ tests: object: *collection arguments: document: { _id: 2 } + # Force completion of the w: 0 write by executing a find on the same connection + - name: find + object: *collection + arguments: + filter: { } + expectResult: [ + { _id: 1 }, + { _id: 2 } + ] expectLogMessages: - client: *client + ignoreExtraMessages: true messages: - level: debug component: command diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/find.json b/src/test/spec/json/command-logging-and-monitoring/monitoring/find.json index 4b5f45ae9..bc9668499 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/find.json +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/find.json @@ -1,6 +1,6 @@ { "description": "find", - "schemaVersion": "1.1", + "schemaVersion": "1.15", "createEntities": [ { "client": { @@ -103,7 +103,8 @@ ] } }, - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } } ] @@ -198,7 +199,8 @@ ] } }, - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } } ] @@ -262,7 +264,8 @@ ] } }, - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } } ] @@ -338,7 +341,8 @@ ] } }, - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } }, { @@ -376,7 +380,8 @@ ] } }, - "commandName": "getMore" + "commandName": "getMore", + "databaseName": "command-monitoring-tests" } } ] @@ -464,7 +469,8 @@ ] } }, - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } }, { @@ -498,7 +504,8 @@ ] } }, - "commandName": "getMore" + "commandName": "getMore", + "databaseName": "command-monitoring-tests" } } ] @@ -539,7 +546,8 @@ }, { "commandFailedEvent": { - "commandName": "find" + "commandName": "find", + "databaseName": "command-monitoring-tests" } } ] diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/find.yml b/src/test/spec/json/command-logging-and-monitoring/monitoring/find.yml index e2bb3f8c9..479e4a460 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/find.yml +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/find.yml @@ -1,6 +1,6 @@ description: "find" -schemaVersion: "1.1" +schemaVersion: "1.15" createEntities: - client: @@ -56,6 +56,7 @@ tests: firstBatch: - { _id: 1, x: 11 } commandName: find + databaseName: *databaseName - description: "A successful find with options" operations: @@ -98,6 +99,7 @@ tests: - { x: 33 } - { x: 22 } commandName: find + databaseName: *databaseName - description: "A successful find with showRecordId and returnKey" operations: @@ -131,6 +133,7 @@ tests: - { _id: 4 } - { _id: 5 } commandName: find + databaseName: *databaseName - description: "A successful find with a getMore" operations: @@ -162,6 +165,7 @@ tests: - { _id: 2, x: 22 } - { _id: 3, x: 33 } commandName: find + databaseName: *databaseName - commandStartedEvent: command: getMore: { $$type: [ int, long ] } @@ -179,6 +183,7 @@ tests: - { _id: 4, x: 44 } - { _id: 5, x: 55 } commandName: getMore + databaseName: *databaseName - description: "A successful find event with a getmore and the server kills the cursor (<= 4.4)" runOnRequirements: @@ -216,6 +221,7 @@ tests: - { _id: 2, x: 22 } - { _id: 3, x: 33 } commandName: find + databaseName: *databaseName - commandStartedEvent: command: getMore: { $$type: [ int, long ] } @@ -232,6 +238,7 @@ tests: nextBatch: - { _id: 4, x: 44 } commandName: getMore + databaseName: *databaseName - description: "A failed find event" operations: @@ -252,3 +259,4 @@ tests: databaseName: *databaseName - commandFailedEvent: commandName: find + databaseName: *databaseName diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.json b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.json new file mode 100644 index 000000000..14740cea3 --- /dev/null +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.json @@ -0,0 +1,225 @@ +{ + "description": "unacknowledged-client-bulkWrite", + "schemaVersion": "1.7", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent" + ], + "uriOptions": { + "w": 0 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "command-monitoring-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "command-monitoring-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "command-monitoring-tests.test" + }, + "tests": [ + { + "description": "A successful mixed client bulkWrite", + "operations": [ + { + "object": "client", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "command-monitoring-tests.test", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "command-monitoring-tests.test", + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 333 + } + } + } + } + ], + "ordered": false + }, + "expectResult": { + "$$unsetOrMatches": { + "acknowledged": { + "$$unsetOrMatches": false + }, + "insertedCount": { + "$$unsetOrMatches": 0 + }, + "upsertedCount": { + "$$unsetOrMatches": 0 + }, + "matchedCount": { + "$$unsetOrMatches": 0 + }, + "modifiedCount": { + "$$unsetOrMatches": 0 + }, + "deletedCount": { + "$$unsetOrMatches": 0 + }, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + }, + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 333 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 3 + }, + "updateMods": { + "$set": { + "x": 333 + } + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "command-monitoring-tests.test" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite", + "reply": { + "ok": 1, + "nInserted": { + "$$exists": false + }, + "nMatched": { + "$$exists": false + }, + "nModified": { + "$$exists": false + }, + "nUpserted": { + "$$exists": false + }, + "nDeleted": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.yml b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.yml new file mode 100644 index 000000000..f43f097c3 --- /dev/null +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledged-client-bulkWrite.yml @@ -0,0 +1,105 @@ +description: "unacknowledged-client-bulkWrite" + +schemaVersion: "1.7" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + uriOptions: + w: 0 + - database: + id: &database database + client: *client + databaseName: &databaseName command-monitoring-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName test + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "command-monitoring-tests.test" + +tests: + - description: 'A successful mixed client bulkWrite' + operations: + - object: *client + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 3 } + update: { $set: { x: 333 } } + ordered: false + expectResult: + $$unsetOrMatches: + acknowledged: { $$unsetOrMatches: false } + insertedCount: { $$unsetOrMatches: 0 } + upsertedCount: { $$unsetOrMatches: 0 } + matchedCount: { $$unsetOrMatches: 0 } + modifiedCount: { $$unsetOrMatches: 0 } + deletedCount: { $$unsetOrMatches: 0 } + insertResults: { $$unsetOrMatches: {} } + updateResults: { $$unsetOrMatches: {} } + deleteResults: { $$unsetOrMatches: {} } + # Force completion of the w:0 write by executing a find on the same connection + - object: *collection + name: find + arguments: + filter: {} + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 333 } + - { _id: 4, x: 44 } + + expectEvents: + - + client: *client + ignoreExtraEvents: true + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: false + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 3 } + updateMods: { $set: { x: 333 } } + multi: false + nsInfo: + - ns: *namespace + - commandSucceededEvent: + commandName: bulkWrite + reply: + ok: 1 + nInserted: { $$exists: false } + nMatched: { $$exists: false } + nModified: { $$exists: false } + nUpserted: { $$exists: false } + nDeleted: { $$exists: false } diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json index 4c16d6df1..78ddde767 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.json @@ -1,10 +1,11 @@ { "description": "unacknowledgedBulkWrite", - "schemaVersion": "1.0", + "schemaVersion": "1.7", "createEntities": [ { "client": { "id": "client", + "useMultipleMongoses": false, "observeEvents": [ "commandStartedEvent", "commandSucceededEvent", @@ -64,11 +65,19 @@ ], "ordered": false } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": {} + } } ], "expectEvents": [ { "client": "client", + "ignoreExtraEvents": true, "events": [ { "commandStartedEvent": { diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.yml b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.yml index d7c8ce0d0..c526fab32 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.yml +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/unacknowledgedBulkWrite.yml @@ -1,10 +1,11 @@ description: "unacknowledgedBulkWrite" -schemaVersion: "1.0" +schemaVersion: "1.7" createEntities: - client: id: &client client + useMultipleMongoses: false observeEvents: - commandStartedEvent - commandSucceededEvent @@ -36,8 +37,14 @@ tests: - insertOne: document: { _id: "unorderedBulkWriteInsertW0", x: 44 } ordered: false + # Force completion of the w: 0 write by executing a find on the same connection + - name: find + object: *collection + arguments: + filter: { } expectEvents: - client: *client + ignoreExtraEvents: true events: - commandStartedEvent: command: diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.json b/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.json index cc9745068..455e5422b 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.json +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.json @@ -1,9 +1,9 @@ { "description": "writeConcernError", - "schemaVersion": "1.13", + "schemaVersion": "1.4", "runOnRequirements": [ { - "minServerVersion": "4.1.0", + "minServerVersion": "4.3.1", "topologies": [ "replicaset" ], @@ -66,11 +66,11 @@ "failCommands": [ "insert" ], + "errorLabels": [ + "RetryableWriteError" + ], "writeConcernError": { - "code": 91, - "errorLabels": [ - "RetryableWriteError" - ] + "code": 91 } } } @@ -112,11 +112,11 @@ "reply": { "ok": 1, "n": 1, + "errorLabels": [ + "RetryableWriteError" + ], "writeConcernError": { - "code": 91, - "errorLabels": [ - "RetryableWriteError" - ] + "code": 91 } }, "commandName": "insert" diff --git a/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.yml b/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.yml index fbaa4a330..b63db8eef 100644 --- a/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.yml +++ b/src/test/spec/json/command-logging-and-monitoring/monitoring/writeConcernError.yml @@ -1,8 +1,8 @@ description: "writeConcernError" -schemaVersion: "1.13" +schemaVersion: "1.4" runOnRequirements: - - minServerVersion: 4.1.0 + minServerVersion: "4.3.1" # failCommand errorLabels option topologies: - replicaset serverless: "forbid" @@ -41,9 +41,9 @@ tests: mode: { times: 1 } data: failCommands: [ insert ] + errorLabels: [ RetryableWriteError ] writeConcernError: code: 91 # ShutdownInProgress - errorLabels: [RetryableWriteError] - name: insertOne object: *collection arguments: @@ -63,7 +63,8 @@ tests: reply: ok: 1 n: 1 - writeConcernError: { code: 91, errorLabels: [ "RetryableWriteError" ] } + errorLabels: [ "RetryableWriteError" ] + writeConcernError: { code: 91 } commandName: insert - commandStartedEvent: command: diff --git a/src/test/spec/json/connection-monitoring-and-pooling/README.md b/src/test/spec/json/connection-monitoring-and-pooling/README.md new file mode 100644 index 000000000..3d8aee40a --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/README.md @@ -0,0 +1,27 @@ +# Connection Monitoring and Pooling (CMAP) + +______________________________________________________________________ + +## Introduction + +Drivers MUST implement all of the following types of CMAP tests: + +- Pool unit and integration tests as described in [cmap-format/README](./cmap-format/README.md) +- Pool prose tests as described below in [Prose Tests](#prose-tests) +- Logging tests as described below in [Logging Tests](#logging-tests) + +## Prose Tests + +The following tests have not yet been automated, but MUST still be tested: + +1. All ConnectionPoolOptions MUST be specified at the MongoClient level +2. All ConnectionPoolOptions MUST be the same for all pools created by a MongoClient +3. A user MUST be able to specify all ConnectionPoolOptions via a URI string +4. A user MUST be able to subscribe to Connection Monitoring Events in a manner idiomatic to their language and driver +5. When a check out attempt fails because connection set up throws an error, assert that a ConnectionCheckOutFailedEvent + with reason="connectionError" is emitted. + +## Logging Tests + +Tests for connection pool logging can be found in the `/logging` subdirectory and are written in the +[Unified Test Format](../../unified-test-format/unified-test-format.md). diff --git a/src/test/spec/json/connection-monitoring-and-pooling/README.rst b/src/test/spec/json/connection-monitoring-and-pooling/README.rst deleted file mode 100644 index ae4af543f..000000000 --- a/src/test/spec/json/connection-monitoring-and-pooling/README.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. role:: javascript(code) - :language: javascript - -======================================== -Connection Monitoring and Pooling (CMAP) -======================================== - -.. contents:: - --------- - -Introduction -============ -Drivers MUST implement all of the following types of CMAP tests: - -* Pool unit and integration tests as described in `cmap-format/README.rst <./cmap-format/README.rst>`__ -* Pool prose tests as described below in `Prose Tests`_ -* Logging tests as described below in `Logging Tests`_ - -Prose Tests -=========== - -The following tests have not yet been automated, but MUST still be tested: - -#. All ConnectionPoolOptions MUST be specified at the MongoClient level -#. All ConnectionPoolOptions MUST be the same for all pools created by a MongoClient -#. A user MUST be able to specify all ConnectionPoolOptions via a URI string -#. A user MUST be able to subscribe to Connection Monitoring Events in a manner idiomatic to their language and driver -#. When a check out attempt fails because connection set up throws an error, - assert that a ConnectionCheckOutFailedEvent with reason="connectionError" is emitted. - -Logging Tests -============= - -Tests for connection pool logging can be found in the `/logging <./logging>`__ subdirectory and are written in the -`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__. \ No newline at end of file diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.md b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.md new file mode 100644 index 000000000..ced96961f --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.md @@ -0,0 +1,167 @@ +# Connection Monitoring and Pooling (CMAP) Unit and Integration Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests that drivers can use to prove their conformance +to the Connection Monitoring and Pooling (CMAP) Spec. + +## Common Test Format + +Each YAML file has the following keys: + +- `version`: A version number indicating the expected format of the spec tests (current version = 1) +- `style`: A string indicating what style of tests this file contains. Contains one of the following: + - `"unit"`: a test that may be run without connecting to a MongoDB deployment. + - `"integration"`: a test that MUST be run against a real MongoDB deployment. +- `description`: A text description of what the test is meant to assert + +## Unit Test Format: + +All Unit Tests have some of the following fields: + +- `poolOptions`: If present, connection pool options to use when creating a pool; both + [standard ConnectionPoolOptions](../../connection-monitoring-and-pooling.md#connection-pool-options) and the + following test-specific options are allowed: + - `backgroundThreadIntervalMS`: A time interval between the end of a + [Background Thread Run](../../connection-monitoring-and-pooling.md#background-thread) and the beginning of the + next Run. If a Connection Pool does not implement a Background Thread, the Test Runner MUST ignore the option. If + the option is not specified, an implementation is free to use any value it finds reasonable. + + Possible values (0 is not allowed): + + - A negative value: never begin a Run. + - A positive value: the interval between Runs in milliseconds. +- `operations`: A list of operations to perform. All operations support the following fields: + - `name`: A string describing which operation to issue. + - `thread`: The name of the thread in which to run this operation. If not specified, runs in the default thread +- `error`: Indicates that the main thread is expected to error during this test. An error may include of the following + fields: + - `type`: the type of error emitted + - `message`: the message associated with that error + - `address`: Address of pool emitting error +- `events`: An array of all connection monitoring events expected to occur while running `operations`. An event may + contain any of the following fields + - `type`: The type of event emitted + - `address`: The address of the pool emitting the event + - `connectionId`: The id of a connection associated with the event + - `duration`: The event duration + - `options`: Options used to create the pool + - `reason`: A reason giving more information on why the event was emitted +- `ignore`: An array of event names to ignore + +Valid Unit Test Operations are the following: + +- `start(target)`: Starts a new thread named `target` + - `target`: The name of the new thread to start +- `wait(ms)`: Sleep the current thread for `ms` milliseconds + - `ms`: The number of milliseconds to sleep the current thread for +- `waitForThread(target)`: wait for thread `target` to finish executing. Propagate any errors to the main thread. + - `target`: The name of the thread to wait for. +- `waitForEvent(event, count, timeout)`: block the current thread until `event` has occurred `count` times + - `event`: The name of the event + - `count`: The number of times the event must occur (counting from the start of the test) + - `timeout`: If specified, time out with an error after waiting for this many milliseconds without seeing the required + events +- `label = pool.checkOut()`: call `checkOut` on pool, returning the checked out connection + - `label`: If specified, associate this label with the returned connection, so that it may be referenced in later + operations +- `pool.checkIn(connection)`: call `checkIn` on pool + - `connection`: A string label identifying which connection to check in. Should be a label that was previously set + with `checkOut` +- `pool.clear()`: call `clear` on Pool + - `interruptInUseConnections`: Determines whether "in use" connections should be also interrupted +- `pool.close()`: call `close` on Pool +- `pool.ready()`: call `ready` on Pool + +## Integration Test Format + +The integration test format is identical to the unit test format with the addition of the following fields to each test: + +- `runOn` (optional): An array of server version and/or topology requirements for which the tests can be run. If the + test environment satisfies one or more of these requirements, the tests may be executed; otherwise, this test should + be skipped. If this field is omitted, the tests can be assumed to have no particular requirements and should be + executed. Each element will have some or all of the following fields: + - `minServerVersion` (optional): The minimum server version (inclusive) required to successfully run the tests. If + this field is omitted, it should be assumed that there is no lower bound on the required server version. + - `maxServerVersion` (optional): The maximum server version (inclusive) against which the tests can be run + successfully. If this field is omitted, it should be assumed that there is no upper bound on the required server + version. +- `failPoint`: optional, a document containing a `configureFailPoint` command to run against the endpoint being used for + the test. +- `poolOptions.appName` (optional): appName attribute to be set in connections, which will be affected by the fail + point. + +## Spec Test Match Function + +The definition of MATCH or MATCHES in the Spec Test Runner is as follows: + +- MATCH takes two values, `expected` and `actual` +- Notation is "Assert `actual` MATCHES `expected`" +- Assertion passes if `expected` is a subset of `actual`, with the values `42` and `"42"` acting as placeholders for + "any value" + +Pseudocode implementation of `actual` MATCHES `expected`: + +```text +If expected is "42" or 42: + Assert that actual exists (is not null or undefined) +Else: + Assert that actual is of the same JSON type as expected + If expected is a JSON array: + For every idx/value in expected: + Assert that actual[idx] MATCHES value + Else if expected is a JSON object: + For every key/value in expected + Assert that actual[key] MATCHES value + Else: + Assert that expected equals actual +``` + +## Unit Test Runner: + +For the unit tests, the behavior of a Connection is irrelevant beyond the need to asserting `connection.id`. Drivers MAY +use a mock connection class for testing the pool behavior in unit tests + +For each YAML file with `style: unit`: + +- Create a Pool `pool`, subscribe and capture any Connection Monitoring events emitted in order. + - If `poolOptions` is specified, use those options to initialize both pools + - The returned pool must have an `address` set as a string value. +- Process each `operation` in `operations` (on the main thread) + - If a `thread` is specified, the main thread MUST schedule the operation to execute in the corresponding thread. + Otherwise, execute the operation directly in the main thread. +- If `error` is presented + - Assert that an actual error `actualError` was thrown by the main thread + - Assert that `actualError` MATCHES `error` +- Else: + - Assert that no errors were thrown by the main thread +- calculate `actualEvents` as every Connection Event emitted whose `type` is not in `ignore` +- if `events` is not empty, then for every `idx`/`expectedEvent` in `events` + - Assert that `actualEvents[idx]` exists + - Assert that `actualEvents[idx]` MATCHES `expectedEvent` + +It is important to note that the `ignore` list is used for calculating `actualEvents`, but is NOT used for the +`waitForEvent` command + +## Integration Test Runner + +The steps to run the integration tests are the same as those used to run the unit tests with the following +modifications: + +- The integration tests MUST be run against an actual endpoint. If the deployment being tested contains multiple + endpoints, then the runner MUST only use one of them to run the tests against. + +- For each test, if `failPoint` is specified, its value is a `configureFailPoint` command. Run the command on the admin + database of the endpoint being tested to enable the fail point. + +- At the end of each test, any enabled fail point MUST be disabled to avoid spurious failures in subsequent tests. The + fail point may be disabled like so: + + ```javascript + db.adminCommand({ + configureFailPoint: "", + mode: "off" + }); + ``` diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.rst b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.rst deleted file mode 100644 index 5bb72dd0f..000000000 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/README.rst +++ /dev/null @@ -1,215 +0,0 @@ -.. role:: javascript(code) - :language: javascript - -=================================================================== -Connection Monitoring and Pooling (CMAP) Unit and Integration Tests -=================================================================== - -.. contents:: - --------- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests that -drivers can use to prove their conformance to the Connection Monitoring and Pooling (CMAP) Spec. - -Common Test Format -================== - -Each YAML file has the following keys: - -- ``version``: A version number indicating the expected format of the spec tests (current version = 1) -- ``style``: A string indicating what style of tests this file contains. Contains one of the following: - - - ``"unit"``: a test that may be run without connecting to a MongoDB deployment. - - ``"integration"``: a test that MUST be run against a real MongoDB deployment. - -- ``description``: A text description of what the test is meant to assert - -Unit Test Format: -================= - -All Unit Tests have some of the following fields: - -- ``poolOptions``: If present, connection pool options to use when creating a pool; - both `standard ConnectionPoolOptions `__ - and the following test-specific options are allowed: - - - ``backgroundThreadIntervalMS``: A time interval between the end of a - `Background Thread Run `__ - and the beginning of the next Run. If a Connection Pool does not implement a Background Thread, the Test Runner MUST ignore the option. - If the option is not specified, an implementation is free to use any value it finds reasonable. - - Possible values (0 is not allowed): - - - A negative value: never begin a Run. - - A positive value: the interval between Runs in milliseconds. - -- ``operations``: A list of operations to perform. All operations support the following fields: - - - ``name``: A string describing which operation to issue. - - ``thread``: The name of the thread in which to run this operation. If not specified, runs in the default thread - -- ``error``: Indicates that the main thread is expected to error during this test. An error may include of the following fields: - - - ``type``: the type of error emitted - - ``message``: the message associated with that error - - ``address``: Address of pool emitting error - -- ``events``: An array of all connection monitoring events expected to occur while running ``operations``. An event may contain any of the following fields - - - ``type``: The type of event emitted - - ``address``: The address of the pool emitting the event - - ``connectionId``: The id of a connection associated with the event - - ``options``: Options used to create the pool - - ``reason``: A reason giving mroe information on why the event was emitted - -- ``ignore``: An array of event names to ignore - -Valid Unit Test Operations are the following: - -- ``start(target)``: Starts a new thread named ``target`` - - - ``target``: The name of the new thread to start - -- ``wait(ms)``: Sleep the current thread for ``ms`` milliseconds - - - ``ms``: The number of milliseconds to sleep the current thread for - -- ``waitForThread(target)``: wait for thread ``target`` to finish executing. Propagate any errors to the main thread. - - - ``target``: The name of the thread to wait for. - -- ``waitForEvent(event, count, timeout)``: block the current thread until ``event`` has occurred ``count`` times - - - ``event``: The name of the event - - ``count``: The number of times the event must occur (counting from the start of the test) - - ``timeout``: If specified, time out with an error after waiting for this many milliseconds without seeing the required events - -- ``label = pool.checkOut()``: call ``checkOut`` on pool, returning the checked out connection - - - ``label``: If specified, associate this label with the returned connection, so that it may be referenced in later operations - -- ``pool.checkIn(connection)``: call ``checkIn`` on pool - - - ``connection``: A string label identifying which connection to check in. Should be a label that was previously set with ``checkOut`` - -- ``pool.clear()``: call ``clear`` on Pool - - - ``interruptInUseConnections``: Determines whether "in use" connections should be also interrupted - -- ``pool.close()``: call ``close`` on Pool -- ``pool.ready()``: call ``ready`` on Pool - - -Integration Test Format -======================= - -The integration test format is identical to the unit test format with -the addition of the following fields to each test: - -- ``runOn`` (optional): An array of server version and/or topology requirements - for which the tests can be run. If the test environment satisfies one or more - of these requirements, the tests may be executed; otherwise, this test should - be skipped. If this field is omitted, the tests can be assumed to have no - particular requirements and should be executed. Each element will have some or - all of the following fields: - - - ``minServerVersion`` (optional): The minimum server version (inclusive) - required to successfully run the tests. If this field is omitted, it should - be assumed that there is no lower bound on the required server version. - - - ``maxServerVersion`` (optional): The maximum server version (inclusive) - against which the tests can be run successfully. If this field is omitted, - it should be assumed that there is no upper bound on the required server - version. - -- ``failPoint``: optional, a document containing a ``configureFailPoint`` - command to run against the endpoint being used for the test. - -- ``poolOptions.appName`` (optional): appName attribute to be set in connections, which will be affected by the fail point. - -Spec Test Match Function -======================== - -The definition of MATCH or MATCHES in the Spec Test Runner is as follows: - -- MATCH takes two values, ``expected`` and ``actual`` -- Notation is "Assert [actual] MATCHES [expected] -- Assertion passes if ``expected`` is a subset of ``actual``, with the values ``42`` and ``"42"`` acting as placeholders for "any value" - -Pseudocode implementation of ``actual`` MATCHES ``expected``: - -:: - - If expected is "42" or 42: - Assert that actual exists (is not null or undefined) - Else: - Assert that actual is of the same JSON type as expected - If expected is a JSON array: - For every idx/value in expected: - Assert that actual[idx] MATCHES value - Else if expected is a JSON object: - For every key/value in expected - Assert that actual[key] MATCHES value - Else: - Assert that expected equals actual - -Unit Test Runner: -================= - -For the unit tests, the behavior of a Connection is irrelevant beyond the need to asserting ``connection.id``. Drivers MAY use a mock connection class for testing the pool behavior in unit tests - -For each YAML file with ``style: unit``: - -- Create a Pool ``pool``, subscribe and capture any Connection Monitoring events emitted in order. - - - If ``poolOptions`` is specified, use those options to initialize both pools - - The returned pool must have an ``address`` set as a string value. - -- Process each ``operation`` in ``operations`` (on the main thread) - - - If a ``thread`` is specified, the main thread MUST schedule the operation to execute in the corresponding thread. Otherwise, execute the operation directly in the main thread. - -- If ``error`` is presented - - - Assert that an actual error ``actualError`` was thrown by the main thread - - Assert that ``actualError`` MATCHES ``error`` - -- Else: - - - Assert that no errors were thrown by the main thread - -- calculate ``actualEvents`` as every Connection Event emitted whose ``type`` is not in ``ignore`` -- if ``events`` is not empty, then for every ``idx``/``expectedEvent`` in ``events`` - - - Assert that ``actualEvents[idx]`` exists - - Assert that ``actualEvents[idx]`` MATCHES ``expectedEvent`` - - -It is important to note that the ``ignore`` list is used for calculating ``actualEvents``, but is NOT used for the ``waitForEvent`` command - -Integration Test Runner -======================= - -The steps to run the integration tests are the same as those used to run the -unit tests with the following modifications: - -- The integration tests MUST be run against an actual endpoint. If the - deployment being tested contains multiple endpoints, then the runner MUST - only use one of them to run the tests against. - -- For each test, if `failPoint` is specified, its value is a - ``configureFailPoint`` command. Run the command on the admin database of the - endpoint being tested to enable the fail point. - -- At the end of each test, any enabled fail point MUST be disabled to avoid - spurious failures in subsequent tests. The fail point may be disabled like - so:: - - db.adminCommand({ - configureFailPoint: , - mode: "off" - }); diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.json index 41c522ae6..3f37f188c 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.json @@ -22,7 +22,8 @@ { "type": "ConnectionCheckedOut", "connectionId": 1, - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckedIn", @@ -32,7 +33,8 @@ { "type": "ConnectionCheckedOut", "connectionId": 1, - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.yml index 517943278..9dbd5aebe 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkin-make-available.yml @@ -12,12 +12,14 @@ events: - type: ConnectionCheckedOut connectionId: 1 address: 42 + duration: 42 - type: ConnectionCheckedIn connectionId: 1 address: 42 - type: ConnectionCheckedOut connectionId: 1 address: 42 + duration: 42 ignore: - ConnectionPoolCreated - ConnectionPoolReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.json index d89b34260..c7e8914d4 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.json @@ -23,12 +23,14 @@ { "type": "ConnectionReady", "connectionId": 1, - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckedOut", "connectionId": 1, - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.yml index bbbd03ff5..1d94778dd 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-connection.yml @@ -13,9 +13,11 @@ events: - type: ConnectionReady connectionId: 1 address: 42 + duration: 42 - type: ConnectionCheckedOut connectionId: 1 address: 42 + duration: 42 ignore: - ConnectionPoolReady - ConnectionPoolCreated diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.json new file mode 100644 index 000000000..6620f82fd --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "style": "integration", + "description": "custom maxConnecting is enforced", + "runOn": [ + { + "minServerVersion": "4.4.0" + } + ], + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "closeConnection": false, + "blockConnection": true, + "blockTimeMS": 500 + } + }, + "poolOptions": { + "maxConnecting": 1, + "maxPoolSize": 2, + "waitQueueTimeoutMS": 5000 + }, + "operations": [ + { + "name": "ready" + }, + { + "name": "start", + "target": "thread1" + }, + { + "name": "start", + "target": "thread2" + }, + { + "name": "checkOut", + "thread": "thread1" + }, + { + "name": "waitForEvent", + "event": "ConnectionCreated", + "count": 1 + }, + { + "name": "checkOut", + "thread": "thread2" + }, + { + "name": "waitForEvent", + "event": "ConnectionReady", + "count": 2 + } + ], + "events": [ + { + "type": "ConnectionCreated" + }, + { + "type": "ConnectionReady" + }, + { + "type": "ConnectionCreated" + }, + { + "type": "ConnectionReady" + } + ], + "ignore": [ + "ConnectionCheckOutStarted", + "ConnectionCheckedIn", + "ConnectionCheckedOut", + "ConnectionClosed", + "ConnectionPoolCreated", + "ConnectionPoolReady" + ] +} diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.yml new file mode 100644 index 000000000..dc8852696 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-custom-maxConnecting-is-enforced.yml @@ -0,0 +1,50 @@ +version: 1 +style: integration +description: custom maxConnecting is enforced +runOn: + - + minServerVersion: "4.4.0" +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 500 +poolOptions: + maxConnecting: 1 + # gives opportunity for the checkout in thread2 to establish a new connection, which it must not do until thread1 establishes one + maxPoolSize: 2 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # thread1 exists to consume the single permit to open a connection, + # so that thread2 would be blocked acquiring a permit, which results in ordering its ConnectionCreated event after + # the ConnectionReady event from thread1. + - name: start + target: thread1 + - name: start + target: thread2 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 1 + - name: checkOut + thread: thread2 + - name: waitForEvent + event: ConnectionReady + count: 2 +events: + - type: ConnectionCreated + - type: ConnectionReady + - type: ConnectionCreated + - type: ConnectionReady +ignore: + - ConnectionCheckOutStarted + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionClosed + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.json index ee2926e1c..614403ef5 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.json @@ -38,7 +38,8 @@ { "type": "ConnectionCheckedOut", "address": 42, - "connectionId": 42 + "connectionId": 42, + "duration": 42 }, { "type": "ConnectionCheckedIn", @@ -56,6 +57,7 @@ { "type": "ConnectionCheckOutFailed", "address": 42, + "duration": 42, "reason": "poolClosed" } ], diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.yml index 4d1b0f3b2..2d0ce8d11 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-error-closed.yml @@ -21,6 +21,7 @@ events: - type: ConnectionCheckedOut address: 42 connectionId: 42 + duration: 42 - type: ConnectionCheckedIn address: 42 connectionId: 42 @@ -30,6 +31,7 @@ events: address: 42 - type: ConnectionCheckOutFailed address: 42 + duration: 42 reason: poolClosed ignore: - ConnectionPoolReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.json index 732478bf7..3a63818bf 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.json @@ -19,7 +19,7 @@ ], "closeConnection": false, "blockConnection": true, - "blockTimeMS": 750 + "blockTimeMS": 800 } }, "poolOptions": { @@ -53,7 +53,7 @@ }, { "name": "wait", - "ms": 100 + "ms": 400 }, { "name": "checkOut", diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.yml index 1b7c4bdee..2ea7333d8 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-is-enforced.yml @@ -13,7 +13,7 @@ failPoint: failCommands: ["isMaster","hello"] closeConnection: false blockConnection: true - blockTimeMS: 750 + blockTimeMS: 800 poolOptions: maxPoolSize: 10 waitQueueTimeoutMS: 5000 @@ -36,7 +36,7 @@ operations: count: 1 # wait some more time to ensure thread1 has begun establishing a Connection - name: wait - ms: 100 + ms: 400 # start 2 check out requests. Only one thread should # start creating a Connection and the other one should be # waiting for pendingConnectionCount to be less than maxConnecting, diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.json index 84ddf8fdb..4d9fda1a6 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.json @@ -89,7 +89,8 @@ { "type": "ConnectionCheckOutFailed", "reason": "timeout", - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.yml index 383f666ad..3c6fb5da2 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-maxConnecting-timeout.yml @@ -60,6 +60,7 @@ events: - type: ConnectionCheckOutFailed reason: timeout address: 42 + duration: 42 ignore: - ConnectionCreated - ConnectionCheckedIn diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.json new file mode 100644 index 000000000..3b0d43e87 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.json @@ -0,0 +1,88 @@ +{ + "version": 1, + "style": "integration", + "description": "threads blocked by maxConnecting check out minPoolSize connections", + "runOn": [ + { + "minServerVersion": "4.4.0" + } + ], + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "closeConnection": false, + "blockConnection": true, + "blockTimeMS": 500 + } + }, + "poolOptions": { + "minPoolSize": 2, + "maxPoolSize": 3, + "waitQueueTimeoutMS": 5000 + }, + "operations": [ + { + "name": "ready" + }, + { + "name": "start", + "target": "thread1" + }, + { + "name": "start", + "target": "thread2" + }, + { + "name": "wait", + "ms": 200 + }, + { + "name": "checkOut", + "thread": "thread1" + }, + { + "name": "waitForEvent", + "event": "ConnectionCreated", + "count": 2 + }, + { + "name": "checkOut", + "thread": "thread2" + }, + { + "name": "waitForEvent", + "event": "ConnectionCheckedOut", + "count": 2 + } + ], + "events": [ + { + "type": "ConnectionCreated", + "address": 42 + }, + { + "type": "ConnectionCreated", + "address": 42 + }, + { + "type": "ConnectionCheckedOut", + "address": 42 + }, + { + "type": "ConnectionCheckedOut", + "address": 42 + } + ], + "ignore": [ + "ConnectionPoolReady", + "ConnectionClosed", + "ConnectionReady", + "ConnectionPoolCreated", + "ConnectionCheckOutStarted" + ] +} diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.yml new file mode 100644 index 000000000..0491c5398 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-minPoolSize-connection-maxConnecting.yml @@ -0,0 +1,63 @@ +version: 1 +style: integration +description: threads blocked by maxConnecting check out minPoolSize connections +runOn: + - + # required for blockConnection in fail point + minServerVersion: "4.4.0" +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 500 +poolOptions: + # allows both thread1 and the background thread to start opening connections concurrently + minPoolSize: 2 + # gives opportunity for the checkout in thread2 to open a new connection, which it must not do nonetheless + maxPoolSize: 3 + waitQueueTimeoutMS: 5000 +operations: + - name: ready + # thread1 exists to hold on one of the two permits to open a connection (the other one is initially held by the background thread), + # so that thread2 would be blocked acquiring a permit, which opens an opportunity for it to grab the connection newly opened + # by the background thread instead of opening a third connection. + - name: start + target: thread1 + - name: start + target: thread2 + # Ideally, thread1 should be holding for its permit to open a connection till the end of the test, but we cannot express that. + # This delay emulates the above requirement: + # - it is long enough to make sure that the background thread opens a connection before thread1 releases its permit; + # - it is short enough to allow thread2 to become blocked acquiring a permit to open a connection, and then grab the connection + # opened by the background thread, before the background thread releases its permit. + - name: wait + ms: 200 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 2 + - name: checkOut + thread: thread2 + - name: waitForEvent + event: ConnectionCheckedOut + count: 2 +events: + # exactly 2 connections must be created and checked out + - type: ConnectionCreated + address: 42 + - type: ConnectionCreated + address: 42 + - type: ConnectionCheckedOut + address: 42 + - type: ConnectionCheckedOut + address: 42 +ignore: + - ConnectionPoolReady + - ConnectionClosed + - ConnectionReady + - ConnectionPoolCreated + - ConnectionCheckOutStarted diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.json index 965d56f6d..10b526e0c 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.json @@ -23,6 +23,7 @@ } }, "poolOptions": { + "maxConnecting": 2, "maxPoolSize": 10, "waitQueueTimeoutMS": 5000 }, @@ -72,9 +73,8 @@ "connection": "conn0" }, { - "name": "waitForEvent", - "event": "ConnectionCheckedOut", - "count": 4 + "name": "wait", + "ms": 100 } ], "events": [ @@ -104,14 +104,6 @@ "type": "ConnectionCheckedOut", "connectionId": 1, "address": 42 - }, - { - "type": "ConnectionCheckedOut", - "address": 42 - }, - { - "type": "ConnectionCheckedOut", - "address": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.yml index dab6e557d..5e2b5890a 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-checkout-returned-connection-maxConnecting.yml @@ -15,6 +15,7 @@ failPoint: blockConnection: true blockTimeMS: 750 poolOptions: + maxConnecting: 2 maxPoolSize: 10 waitQueueTimeoutMS: 5000 operations: @@ -45,14 +46,13 @@ operations: count: 4 - name: wait ms: 100 - # check original connection back in, so the thread that isn't - # currently establishing will become unblocked. Then wait for - # all threads to complete. + # Check original connection back in, so one of the waiting threads can check + # out the idle connection before the two new connections are ready. - name: checkIn connection: conn0 - - name: waitForEvent - event: ConnectionCheckedOut - count: 4 + # Wait for 100ms to let one of the blocked checkOut operations complete. + - name: wait + ms: 100 events: # main thread checking out a Connection and holding it - type: ConnectionCreated @@ -69,15 +69,13 @@ events: - type: ConnectionCheckedIn connectionId: 1 address: 42 - # remaining thread checking out the returned Connection + # Another thread checks out the returned Connection before the two new + # connections are checked out. - type: ConnectionCheckedOut connectionId: 1 address: 42 - # first two threads finishing Connection establishment - - type: ConnectionCheckedOut - address: 42 - - type: ConnectionCheckedOut - address: 42 + # Events after this can come in different orders but still be valid. + # See DRIVERS-2223 for details. ignore: - ConnectionPoolReady - ConnectionClosed diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.json index d4aef928c..e6077f12a 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.json @@ -59,7 +59,8 @@ }, { "type": "ConnectionCheckedOut", - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckOutStarted", @@ -76,17 +77,20 @@ { "type": "ConnectionCheckOutFailed", "reason": "connectionError", - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckOutFailed", "reason": "connectionError", - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckOutFailed", "reason": "connectionError", - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.yml index 521f8ed24..388056f4f 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-clears-waitqueue.yml @@ -38,6 +38,7 @@ events: address: 42 - type: ConnectionCheckedOut address: 42 + duration: 42 - type: ConnectionCheckOutStarted address: 42 - type: ConnectionCheckOutStarted @@ -47,12 +48,15 @@ events: - type: ConnectionCheckOutFailed reason: connectionError address: 42 + duration: 42 - type: ConnectionCheckOutFailed reason: connectionError address: 42 + duration: 42 - type: ConnectionCheckOutFailed reason: connectionError address: 42 + duration: 42 ignore: - ConnectionPoolReady - ConnectionPoolCleared diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.json new file mode 100644 index 000000000..c1fd74632 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.json @@ -0,0 +1,77 @@ +{ + "version": 1, + "style": "integration", + "description": "clear with interruptInUseConnections = true closes pending connections", + "runOn": [ + { + "minServerVersion": "4.9.0" + } + ], + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "isMaster", + "hello" + ], + "closeConnection": false, + "blockConnection": true, + "blockTimeMS": 10000 + } + }, + "poolOptions": { + "minPoolSize": 0 + }, + "operations": [ + { + "name": "ready" + }, + { + "name": "start", + "target": "thread1" + }, + { + "name": "checkOut", + "thread": "thread1" + }, + { + "name": "waitForEvent", + "event": "ConnectionCreated", + "count": 1 + }, + { + "name": "clear", + "interruptInUseConnections": true + }, + { + "name": "waitForEvent", + "event": "ConnectionCheckOutFailed", + "count": 1 + } + ], + "events": [ + { + "type": "ConnectionCheckOutStarted" + }, + { + "type": "ConnectionCreated" + }, + { + "type": "ConnectionPoolCleared", + "interruptInUseConnections": true + }, + { + "type": "ConnectionClosed" + }, + { + "type": "ConnectionCheckOutFailed" + } + ], + "ignore": [ + "ConnectionCheckedIn", + "ConnectionCheckedOut", + "ConnectionPoolCreated", + "ConnectionPoolReady" + ] +} diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.yml new file mode 100644 index 000000000..ea0bbc7d4 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-interrupting-pending-connections.yml @@ -0,0 +1,42 @@ +version: 1 +style: integration +description: clear with interruptInUseConnections = true closes pending connections +runOn: + - + minServerVersion: "4.9.0" +failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: ["isMaster","hello"] + closeConnection: false + blockConnection: true + blockTimeMS: 10000 +poolOptions: + minPoolSize: 0 +operations: + - name: ready + - name: start + target: thread1 + - name: checkOut + thread: thread1 + - name: waitForEvent + event: ConnectionCreated + count: 1 + - name: clear + interruptInUseConnections: true + - name: waitForEvent + event: ConnectionCheckOutFailed + count: 1 +events: + - type: ConnectionCheckOutStarted + - type: ConnectionCreated + - type: ConnectionPoolCleared + interruptInUseConnections: true + - type: ConnectionClosed + - type: ConnectionCheckOutFailed +ignore: + - ConnectionCheckedIn + - ConnectionCheckedOut + - ConnectionPoolCreated + - ConnectionPoolReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.json index 800c3545a..88c2988ac 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.json @@ -40,7 +40,8 @@ { "type": "ConnectionCheckedOut", "address": 42, - "connectionId": 42 + "connectionId": 42, + "duration": 42 }, { "type": "ConnectionPoolCleared", @@ -49,6 +50,7 @@ { "type": "ConnectionCheckOutFailed", "address": 42, + "duration": 42, "reason": "connectionError" }, { @@ -57,7 +59,8 @@ }, { "type": "ConnectionCheckedOut", - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.yml index c783d4d09..93c85bfbe 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-ready.yml @@ -20,15 +20,18 @@ events: - type: ConnectionCheckedOut address: 42 connectionId: 42 + duration: 42 - type: ConnectionPoolCleared address: 42 - type: ConnectionCheckOutFailed address: 42 + duration: 42 reason: connectionError - type: ConnectionPoolReady address: 42 - type: ConnectionCheckedOut address: 42 + duration: 42 ignore: - ConnectionPoolCreated - ConnectionReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.json new file mode 100644 index 000000000..3d7536951 --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.json @@ -0,0 +1,81 @@ +{ + "version": 1, + "style": "unit", + "description": "Pool clear SHOULD schedule the next background thread run immediately (interruptInUseConnections = false)", + "poolOptions": { + "backgroundThreadIntervalMS": 10000 + }, + "operations": [ + { + "name": "ready" + }, + { + "name": "checkOut" + }, + { + "name": "checkOut", + "label": "conn" + }, + { + "name": "checkIn", + "connection": "conn" + }, + { + "name": "clear", + "interruptInUseConnections": false + }, + { + "name": "waitForEvent", + "event": "ConnectionPoolCleared", + "count": 1, + "timeout": 1000 + }, + { + "name": "waitForEvent", + "event": "ConnectionClosed", + "count": 1, + "timeout": 1000 + }, + { + "name": "close" + } + ], + "events": [ + { + "type": "ConnectionCheckedOut", + "connectionId": 1, + "address": 42 + }, + { + "type": "ConnectionCheckedOut", + "connectionId": 2, + "address": 42 + }, + { + "type": "ConnectionCheckedIn", + "connectionId": 2, + "address": 42 + }, + { + "type": "ConnectionPoolCleared", + "interruptInUseConnections": false + }, + { + "type": "ConnectionClosed", + "connectionId": 2, + "reason": "stale", + "address": 42 + }, + { + "type": "ConnectionPoolClosed", + "address": 42 + } + ], + "ignore": [ + "ConnectionCreated", + "ConnectionPoolReady", + "ConnectionReady", + "ConnectionCheckOutStarted", + "ConnectionPoolCreated" + ] +} diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.yml new file mode 100644 index 000000000..dcaafec8b --- /dev/null +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear-schedule-run-interruptInUseConnections-false.yml @@ -0,0 +1,48 @@ +version: 1 +style: unit +description: Pool clear SHOULD schedule the next background thread run immediately (interruptInUseConnections = false) +poolOptions: + # ensure it's not involved by default + backgroundThreadIntervalMS: 10000 +operations: + - name: ready + - name: checkOut + - name: checkOut + label: conn + - name: checkIn + connection: conn + - name: clear + interruptInUseConnections: false + - name: waitForEvent + event: ConnectionPoolCleared + count: 1 + timeout: 1000 + - name: waitForEvent + event: ConnectionClosed + count: 1 + timeout: 1000 + - name: close +events: + - type: ConnectionCheckedOut + connectionId: 1 + address: 42 + - type: ConnectionCheckedOut + connectionId: 2 + address: 42 + - type: ConnectionCheckedIn + connectionId: 2 + address: 42 + - type: ConnectionPoolCleared + interruptInUseConnections: false + - type: ConnectionClosed + connectionId: 2 + reason: stale + address: 42 + - type: ConnectionPoolClosed + address: 42 +ignore: + - ConnectionCreated + - ConnectionPoolReady + - ConnectionReady + - ConnectionCheckOutStarted + - ConnectionPoolCreated diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.json deleted file mode 100644 index 89da40d83..000000000 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "version": 1, - "style": "unit", - "description": "pool clear halts background minPoolSize establishments (new)", - "poolOptions": { - "minPoolSize": 1 - }, - "operations": [ - { - "name": "ready" - }, - { - "name": "waitForEvent", - "event": "ConnectionReady", - "count": 1 - }, - { - "name": "clear" - }, - { - "name": "wait", - "ms": 200 - }, - { - "name": "ready" - }, - { - "name": "waitForEvent", - "event": "ConnectionReady", - "count": 1 - } - ], - "events": [ - { - "type": "ConnectionPoolReady", - "address": 42 - }, - { - "type": "ConnectionCreated", - "address": 42 - }, - { - "type": "ConnectionReady", - "address": 42 - }, - { - "type": "ConnectionPoolCleared", - "address": 42 - }, - { - "type": "ConnectionPoolReady", - "address": 42 - }, - { - "type": "ConnectionCreated", - "address": 42 - }, - { - "type": "ConnectionReady", - "address": 42 - } - ], - "ignore": [ - "ConnectionPoolCreated", - "ConnectionClosed" - ] -} diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.yml deleted file mode 100644 index 9e162618a..000000000 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-clear.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 1 -style: unit -description: pool clear halts background minPoolSize establishments (new) -poolOptions: - minPoolSize: 1 -operations: - - name: ready - - name: waitForEvent - event: ConnectionReady - count: 1 - - name: clear - # ensure no connections created after clear - - name: wait - ms: 200 - - name: ready - - name: waitForEvent - event: ConnectionReady -events: - - type: ConnectionPoolReady - address: 42 - - type: ConnectionCreated - address: 42 - - type: ConnectionReady - address: 42 - - type: ConnectionPoolCleared - address: 42 - - type: ConnectionPoolReady - address: 42 - - type: ConnectionCreated - address: 42 - - type: ConnectionReady - address: 42 -ignore: - - ConnectionPoolCreated diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json index 1c744b850..509b2a235 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.json @@ -49,15 +49,15 @@ "type": "ConnectionCreated", "address": 42 }, + { + "type": "ConnectionPoolCleared", + "address": 42 + }, { "type": "ConnectionClosed", "address": 42, "connectionId": 42, "reason": "error" - }, - { - "type": "ConnectionPoolCleared", - "address": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml index dd5890b1d..f43c4ee15 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-create-min-size-error.yml @@ -30,11 +30,11 @@ events: address: 42 - type: ConnectionCreated address: 42 + - type: ConnectionPoolCleared + address: 42 - type: ConnectionClosed address: 42 connectionId: 42 reason: error - - type: ConnectionPoolCleared - address: 42 ignore: - ConnectionPoolCreated diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.json index 29ce7326c..a90aed04d 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.json @@ -31,7 +31,8 @@ { "type": "ConnectionCheckOutFailed", "reason": "connectionError", - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionPoolReady", @@ -47,7 +48,8 @@ }, { "type": "ConnectionCheckedOut", - "address": 42 + "address": 42, + "duration": 42 } ], "ignore": [ diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.yml index 730d4d27b..233209939 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/pool-ready.yml @@ -17,6 +17,7 @@ events: - type: ConnectionCheckOutFailed reason: connectionError address: 42 + duration: 42 - type: ConnectionPoolReady address: 42 - type: ConnectionCheckOutStarted @@ -25,6 +26,7 @@ events: address: 42 - type: ConnectionCheckedOut address: 42 + duration: 42 ignore: - ConnectionPoolCreated - ConnectionReady diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.json b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.json index fbcbdfb04..8bd7c4949 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.json @@ -48,7 +48,8 @@ { "type": "ConnectionCheckedOut", "connectionId": 42, - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckOutStarted", @@ -57,7 +58,8 @@ { "type": "ConnectionCheckOutFailed", "reason": "timeout", - "address": 42 + "address": 42, + "duration": 42 }, { "type": "ConnectionCheckedIn", diff --git a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.yml b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.yml index 5433c1489..fdb3b5862 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/cmap-format/wait-queue-timeout.yml @@ -32,11 +32,13 @@ events: - type: ConnectionCheckedOut connectionId: 42 address: 42 + duration: 42 - type: ConnectionCheckOutStarted address: 42 - type: ConnectionCheckOutFailed reason: timeout address: 42 + duration: 42 - type: ConnectionCheckedIn connectionId: 42 address: 42 diff --git a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.json b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.json index e21a3d049..72103b3ca 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.json @@ -135,6 +135,80 @@ "serverHost": { "$$type": "string" }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "connection", + "data": { + "message": "Connection checked out", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "connection", + "data": { + "message": "Connection checked in", + "driverConnectionId": { + "$$type": [ + "int", + "long" + ] + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "connection", + "data": { + "message": "Connection checkout started", + "serverHost": { + "$$type": "string" + }, "serverPort": { "$$type": [ "int", @@ -162,6 +236,13 @@ "int", "long" ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] } } }, @@ -424,6 +505,13 @@ "reason": "An error occurred while trying to establish a new connection", "error": { "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] } } } diff --git a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.yml b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.yml index 58ac7ec34..49868a062 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-logging.yml @@ -66,7 +66,33 @@ tests: driverConnectionId: { $$type: [int, long] } serverHost: { $$type: string } serverPort: { $$type: [int, long] } - + durationMS: { $$type: [double, int, long] } + + - level: debug + component: connection + data: + message: "Connection checked out" + driverConnectionId: { $$type: [int, long] } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + durationMS: { $$type: [double, int, long] } + + - level: debug + component: connection + data: + message: "Connection checked in" + driverConnectionId: { $$type: [int, long] } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + + # The next three expected logs are for ending a session. + - level: debug + component: connection + data: + message: "Connection checkout started" + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug component: connection data: @@ -74,6 +100,7 @@ tests: driverConnectionId: { $$type: [int, long] } serverHost: { $$type: string } serverPort: { $$type: [int, long] } + durationMS: { $$type: [double, int, long] } - level: debug component: connection @@ -194,3 +221,4 @@ tests: serverPort: { $$type: [int, long] } reason: "An error occurred while trying to establish a new connection" error: { $$exists: true } + durationMS: { $$type: [double, int, long] } diff --git a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.json b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.json index e67804915..7055a5486 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.json +++ b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.json @@ -1,5 +1,5 @@ { - "description": "connection-logging", + "description": "connection-pool-options", "schemaVersion": "1.13", "runOnRequirements": [ { @@ -128,6 +128,13 @@ "int", "long" ] + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] } } } diff --git a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.yml b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.yml index b22693a92..790dab6fe 100644 --- a/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.yml +++ b/src/test/spec/json/connection-monitoring-and-pooling/logging/connection-pool-options.yml @@ -1,4 +1,4 @@ -description: "connection-logging" +description: "connection-pool-options" schemaVersion: "1.13" @@ -71,6 +71,7 @@ tests: driverConnectionId: { $$type: [int, long] } serverHost: { $$type: string } serverPort: { $$type: [int, long] } + durationMS: { $$type: [double, int, long] } # Drivers who have not done DRIVERS-1943 will need to skip this test. - description: "maxConnecting should be included in connection pool created message when specified" diff --git a/src/test/spec/json/connection-string/README.md b/src/test/spec/json/connection-string/README.md new file mode 100644 index 000000000..c40d23aef --- /dev/null +++ b/src/test/spec/json/connection-string/README.md @@ -0,0 +1,55 @@ +# Connection String Tests + +The YAML and JSON files in this directory tree are platform-independent tests that drivers can use to prove their +conformance to the Connection String Spec. + +As the spec is primarily concerned with parsing the parts of a URI, these tests do not focus on host and option +validation. Where necessary, the tests use options known to be (un)supported by drivers to assert behavior such as +issuing a warning on repeated option keys. As such these YAML tests are in no way a replacement for more thorough +testing. However, they can provide an initial verification of your implementation. + +## Version + +Files in the "specifications" repository have no version scheme. They are not tied to a MongoDB server version. + +## Format + +Each YAML file contains an object with a single `tests` key. This key is an array of test case objects, each of which +have the following keys: + +- `description`: A string describing the test. +- `uri`: A string containing the URI to be parsed. +- `valid:` A boolean indicating if the URI should be considered valid. +- `warning:` A boolean indicating whether URI parsing should emit a warning (independent of whether or not the URI is + valid). +- `hosts`: An array of host objects, each of which have the following keys: + - `type`: A string denoting the type of host. Possible values are "ipv4", "ip_literal", "hostname", and "unix". + Asserting the type is *optional*. + - `host`: A string containing the parsed host. + - `port`: An integer containing the parsed port number. +- `auth`: An object containing the following keys: + - `username`: A string containing the parsed username. For auth mechanisms that do not utilize a password, this may be + the entire `userinfo` token (as discussed in [RFC 2396](https://www.ietf.org/rfc/rfc2396.txt)). + - `password`: A string containing the parsed password. + - `db`: A string containing the parsed authentication database. For legacy implementations that support namespaces + (databases and collections) this may be the full namespace eg: `.` +- `options`: An object containing key/value pairs for each parsed query string option. + +If a test case includes a null value for one of these keys (e.g. `auth: ~`, `port: ~`), no assertion is necessary. This +both simplifies parsing of the test files (keys should always exist) and allows flexibility for drivers that might +substitute default values *during* parsing (e.g. omitted `port` could be parsed as 27017). + +The `valid` and `warning` fields are boolean in order to keep the tests flexible. We are not concerned with asserting +the format of specific error or warnings messages strings. + +### Use as unit tests + +Testing whether a URI is valid or not should simply be a matter of checking whether URI parsing (or MongoClient +construction) raises an error or exception. Testing for emitted warnings may require more legwork (e.g. configuring a +log handler and watching for output). + +Not all drivers may be able to directly assert the hosts, auth credentials, and options. Doing so may require exposing +the driver's URI parsing component. + +The file `valid-db-with-dotted-name.yml` is a special case for testing drivers that allow dotted namespaces, instead of +only database names, in the Auth Database portion of the URI. diff --git a/src/test/spec/json/connection-string/README.rst b/src/test/spec/json/connection-string/README.rst deleted file mode 100644 index f221600b2..000000000 --- a/src/test/spec/json/connection-string/README.rst +++ /dev/null @@ -1,73 +0,0 @@ -======================= -Connection String Tests -======================= - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the Connection String Spec. - -As the spec is primarily concerned with parsing the parts of a URI, these tests -do not focus on host and option validation. Where necessary, the tests use -options known to be (un)supported by drivers to assert behavior such as issuing -a warning on repeated option keys. As such these YAML tests are in no way a -replacement for more thorough testing. However, they can provide an initial -verification of your implementation. - -Version -------- - -Files in the "specifications" repository have no version scheme. They are not -tied to a MongoDB server version. - -Format ------- - -Each YAML file contains an object with a single ``tests`` key. This key is an -array of test case objects, each of which have the following keys: - -- ``description``: A string describing the test. -- ``uri``: A string containing the URI to be parsed. -- ``valid:`` A boolean indicating if the URI should be considered valid. -- ``warning:`` A boolean indicating whether URI parsing should emit a warning - (independent of whether or not the URI is valid). -- ``hosts``: An array of host objects, each of which have the following keys: - - - ``type``: A string denoting the type of host. Possible values are "ipv4", - "ip_literal", "hostname", and "unix". Asserting the type is *optional*. - - ``host``: A string containing the parsed host. - - ``port``: An integer containing the parsed port number. -- ``auth``: An object containing the following keys: - - - ``username``: A string containing the parsed username. For auth mechanisms - that do not utilize a password, this may be the entire ``userinfo`` token - (as discussed in `RFC 2396 `_). - - ``password``: A string containing the parsed password. - - ``db``: A string containing the parsed authentication database. For legacy - implementations that support namespaces (databases and collections) this may - be the full namespace eg: ``.`` -- ``options``: An object containing key/value pairs for each parsed query string - option. - -If a test case includes a null value for one of these keys (e.g. ``auth: ~``, -``port: ~``), no assertion is necessary. This both simplifies parsing of the -test files (keys should always exist) and allows flexibility for drivers that -might substitute default values *during* parsing (e.g. omitted ``port`` could be -parsed as 27017). - -The ``valid`` and ``warning`` fields are boolean in order to keep the tests -flexible. We are not concerned with asserting the format of specific error or -warnings messages strings. - -Use as unit tests -================= - -Testing whether a URI is valid or not should simply be a matter of checking -whether URI parsing (or MongoClient construction) raises an error or exception. -Testing for emitted warnings may require more legwork (e.g. configuring a log -handler and watching for output). - -Not all drivers may be able to directly assert the hosts, auth credentials, and -options. Doing so may require exposing the driver's URI parsing component. - -The file valid-db-with-dotted-name.yml is a special case for testing drivers -that allow dotted namespaces, instead of only database names, in the Auth -Database portion of the URI. diff --git a/src/test/spec/json/connection-string/invalid-uris.json b/src/test/spec/json/connection-string/invalid-uris.json index e04da2b23..a7accbd27 100644 --- a/src/test/spec/json/connection-string/invalid-uris.json +++ b/src/test/spec/json/connection-string/invalid-uris.json @@ -162,15 +162,6 @@ "auth": null, "options": null }, - { - "description": "Missing delimiting slash between hosts and options", - "uri": "mongodb://example.com?w=1", - "valid": false, - "warning": null, - "hosts": null, - "auth": null, - "options": null - }, { "description": "Incomplete key value pair for option", "uri": "mongodb://example.com/?w", diff --git a/src/test/spec/json/connection-string/invalid-uris.yml b/src/test/spec/json/connection-string/invalid-uris.yml index 395e60eed..dd4d4ce31 100644 --- a/src/test/spec/json/connection-string/invalid-uris.yml +++ b/src/test/spec/json/connection-string/invalid-uris.yml @@ -143,14 +143,6 @@ tests: hosts: ~ auth: ~ options: ~ - - - description: "Missing delimiting slash between hosts and options" - uri: "mongodb://example.com?w=1" - valid: false - warning: ~ - hosts: ~ - auth: ~ - options: ~ - description: "Incomplete key value pair for option" uri: "mongodb://example.com/?w" @@ -257,5 +249,3 @@ tests: hosts: ~ auth: ~ options: ~ - - diff --git a/src/test/spec/json/connection-string/valid-auth.json b/src/test/spec/json/connection-string/valid-auth.json index 4f684ff18..60f63f4e3 100644 --- a/src/test/spec/json/connection-string/valid-auth.json +++ b/src/test/spec/json/connection-string/valid-auth.json @@ -220,29 +220,8 @@ "options": null }, { - "description": "Escaped user info and database (MONGODB-CR)", - "uri": "mongodb://%24am:f%3Azzb%40z%2Fz%3D@127.0.0.1/admin%3F?authMechanism=MONGODB-CR", - "valid": true, - "warning": false, - "hosts": [ - { - "type": "ipv4", - "host": "127.0.0.1", - "port": null - } - ], - "auth": { - "username": "$am", - "password": "f:zzb@z/z=", - "db": "admin?" - }, - "options": { - "authmechanism": "MONGODB-CR" - } - }, - { - "description": "Subdelimiters in user/pass don't need escaping (MONGODB-CR)", - "uri": "mongodb://!$&'()*+,;=:!$&'()*+,;=@127.0.0.1/admin?authMechanism=MONGODB-CR", + "description": "Subdelimiters in user/pass don't need escaping (PLAIN)", + "uri": "mongodb://!$&'()*+,;=:!$&'()*+,;=@127.0.0.1/admin?authMechanism=PLAIN", "valid": true, "warning": false, "hosts": [ @@ -258,7 +237,7 @@ "db": "admin" }, "options": { - "authmechanism": "MONGODB-CR" + "authmechanism": "PLAIN" } }, { @@ -284,7 +263,7 @@ }, { "description": "Escaped username (GSSAPI)", - "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI", + "uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI", "valid": true, "warning": false, "hosts": [ @@ -303,7 +282,8 @@ "authmechanism": "GSSAPI", "authmechanismproperties": { "SERVICE_NAME": "other", - "CANONICALIZE_HOST_NAME": true + "SERVICE_HOST": "example.com", + "CANONICALIZE_HOST_NAME": "forward" } } }, diff --git a/src/test/spec/json/connection-string/valid-auth.yml b/src/test/spec/json/connection-string/valid-auth.yml index 01c866ee9..02ed28742 100644 --- a/src/test/spec/json/connection-string/valid-auth.yml +++ b/src/test/spec/json/connection-string/valid-auth.yml @@ -173,24 +173,8 @@ tests: db: "my=db" options: ~ - - description: "Escaped user info and database (MONGODB-CR)" - uri: "mongodb://%24am:f%3Azzb%40z%2Fz%3D@127.0.0.1/admin%3F?authMechanism=MONGODB-CR" - valid: true - warning: false - hosts: - - - type: "ipv4" - host: "127.0.0.1" - port: ~ - auth: - username: "$am" - password: "f:zzb@z/z=" - db: "admin?" - options: - authmechanism: "MONGODB-CR" - - - description: "Subdelimiters in user/pass don't need escaping (MONGODB-CR)" - uri: "mongodb://!$&'()*+,;=:!$&'()*+,;=@127.0.0.1/admin?authMechanism=MONGODB-CR" + description: "Subdelimiters in user/pass don't need escaping (PLAIN)" + uri: "mongodb://!$&'()*+,;=:!$&'()*+,;=@127.0.0.1/admin?authMechanism=PLAIN" valid: true warning: false hosts: @@ -203,7 +187,7 @@ tests: password: "!$&'()*+,;=" db: "admin" options: - authmechanism: "MONGODB-CR" + authmechanism: "PLAIN" - description: "Escaped username (MONGODB-X509)" uri: "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509" @@ -222,7 +206,7 @@ tests: authmechanism: "MONGODB-X509" - description: "Escaped username (GSSAPI)" - uri: "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI" + uri: "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authMechanism=GSSAPI" valid: true warning: false hosts: @@ -238,7 +222,8 @@ tests: authmechanism: "GSSAPI" authmechanismproperties: SERVICE_NAME: "other" - CANONICALIZE_HOST_NAME: true + SERVICE_HOST: "example.com" + CANONICALIZE_HOST_NAME: "forward" - description: "At-signs in options aren't part of the userinfo" uri: "mongodb://alice:secret@example.com/admin?replicaset=my@replicaset" diff --git a/src/test/spec/json/connection-string/valid-options.json b/src/test/spec/json/connection-string/valid-options.json index 4c2bded9e..fce53873a 100644 --- a/src/test/spec/json/connection-string/valid-options.json +++ b/src/test/spec/json/connection-string/valid-options.json @@ -2,7 +2,7 @@ "tests": [ { "description": "Option names are normalized to lowercase", - "uri": "mongodb://alice:secret@example.com/admin?AUTHMechanism=MONGODB-CR", + "uri": "mongodb://alice:secret@example.com/admin?AUTHMechanism=PLAIN", "valid": true, "warning": false, "hosts": [ @@ -18,7 +18,44 @@ "db": "admin" }, "options": { - "authmechanism": "MONGODB-CR" + "authmechanism": "PLAIN" + } + }, + { + "description": "Missing delimiting slash between hosts and options", + "uri": "mongodb://example.com?tls=true", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "tls": true + } + }, + { + "description": "Colon in a key value pair", + "uri": "mongodb://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster,ENVIRONMENT:azure", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "hostname", + "host": "example.com", + "port": null + } + ], + "auth": null, + "options": { + "authmechanismProperties": { + "TOKEN_RESOURCE": "mongodb://test-cluster", + "ENVIRONMENT": "azure" + } } } ] diff --git a/src/test/spec/json/connection-string/valid-options.yml b/src/test/spec/json/connection-string/valid-options.yml index e1b94039c..c5d61f974 100644 --- a/src/test/spec/json/connection-string/valid-options.yml +++ b/src/test/spec/json/connection-string/valid-options.yml @@ -1,7 +1,7 @@ tests: - description: "Option names are normalized to lowercase" - uri: "mongodb://alice:secret@example.com/admin?AUTHMechanism=MONGODB-CR" + uri: "mongodb://alice:secret@example.com/admin?AUTHMechanism=PLAIN" valid: true warning: false hosts: @@ -14,4 +14,32 @@ tests: password: "secret" db: "admin" options: - authmechanism: "MONGODB-CR" + authmechanism: "PLAIN" + - + description: "Missing delimiting slash between hosts and options" + uri: "mongodb://example.com?tls=true" + valid: true + warning: false + hosts: + - + type: "hostname" + host: "example.com" + port: ~ + auth: ~ + options: + tls: true + - + description: Colon in a key value pair + uri: mongodb://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster,ENVIRONMENT:azure + valid: true + warning: false + hosts: + - + type: hostname + host: example.com + port: ~ + auth: ~ + options: + authmechanismProperties: + TOKEN_RESOURCE: 'mongodb://test-cluster' + ENVIRONMENT: azure diff --git a/src/test/spec/json/connection-string/valid-unix_socket-absolute.json b/src/test/spec/json/connection-string/valid-unix_socket-absolute.json index 5bb02476e..66491db13 100644 --- a/src/test/spec/json/connection-string/valid-unix_socket-absolute.json +++ b/src/test/spec/json/connection-string/valid-unix_socket-absolute.json @@ -30,6 +30,21 @@ "auth": null, "options": null }, + { + "description": "Unix domain socket (mixed case)", + "uri": "mongodb://%2Ftmp%2FMongoDB-27017.sock", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "unix", + "host": "/tmp/MongoDB-27017.sock", + "port": null + } + ], + "auth": null, + "options": null + }, { "description": "Unix domain socket (absolute path with spaces in path)", "uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock", diff --git a/src/test/spec/json/connection-string/valid-unix_socket-absolute.yml b/src/test/spec/json/connection-string/valid-unix_socket-absolute.yml index 651e06fe3..cdfd00d33 100644 --- a/src/test/spec/json/connection-string/valid-unix_socket-absolute.yml +++ b/src/test/spec/json/connection-string/valid-unix_socket-absolute.yml @@ -23,6 +23,18 @@ tests: port: ~ auth: ~ options: ~ + - + description: "Unix domain socket (mixed case)" + uri: "mongodb://%2Ftmp%2FMongoDB-27017.sock" + valid: true + warning: false + hosts: + - + type: "unix" + host: "/tmp/MongoDB-27017.sock" + port: ~ + auth: ~ + options: ~ - description: "Unix domain socket (absolute path with spaces in path)" uri: "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock" diff --git a/src/test/spec/json/connection-string/valid-unix_socket-relative.json b/src/test/spec/json/connection-string/valid-unix_socket-relative.json index 2ce649ffc..788720920 100644 --- a/src/test/spec/json/connection-string/valid-unix_socket-relative.json +++ b/src/test/spec/json/connection-string/valid-unix_socket-relative.json @@ -30,6 +30,21 @@ "auth": null, "options": null }, + { + "description": "Unix domain socket (mixed case)", + "uri": "mongodb://rel%2FMongoDB-27017.sock", + "valid": true, + "warning": false, + "hosts": [ + { + "type": "unix", + "host": "rel/MongoDB-27017.sock", + "port": null + } + ], + "auth": null, + "options": null + }, { "description": "Unix domain socket (relative path with spaces)", "uri": "mongodb://rel%2F %2Fmongodb-27017.sock", diff --git a/src/test/spec/json/connection-string/valid-unix_socket-relative.yml b/src/test/spec/json/connection-string/valid-unix_socket-relative.yml index 7164188b3..6d0d10820 100644 --- a/src/test/spec/json/connection-string/valid-unix_socket-relative.yml +++ b/src/test/spec/json/connection-string/valid-unix_socket-relative.yml @@ -23,6 +23,18 @@ tests: port: ~ auth: ~ options: ~ + - + description: "Unix domain socket (mixed case)" + uri: "mongodb://rel%2FMongoDB-27017.sock" + valid: true + warning: false + hosts: + - + type: "unix" + host: "rel/MongoDB-27017.sock" + port: ~ + auth: ~ + options: ~ - description: "Unix domain socket (relative path with spaces)" uri: "mongodb://rel%2F %2Fmongodb-27017.sock" diff --git a/src/test/spec/json/connection-string/valid-warnings.json b/src/test/spec/json/connection-string/valid-warnings.json index 7ede9bdd5..e11757eb0 100644 --- a/src/test/spec/json/connection-string/valid-warnings.json +++ b/src/test/spec/json/connection-string/valid-warnings.json @@ -93,6 +93,23 @@ ], "auth": null, "options": null + }, + { + "description": "Comma in a key value pair causes a warning", + "uri": "mongodb://localhost?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2,ENVIRONMENT:azure", + "valid": true, + "warning": true, + "hosts": [ + { + "type": "hostname", + "host": "localhost", + "port": null + } + ], + "auth": null, + "options": { + "authMechanism": "MONGODB-OIDC" + } } ] -} \ No newline at end of file +} diff --git a/src/test/spec/json/connection-string/valid-warnings.yml b/src/test/spec/json/connection-string/valid-warnings.yml index ea9cc9d1e..3495b5077 100644 --- a/src/test/spec/json/connection-string/valid-warnings.yml +++ b/src/test/spec/json/connection-string/valid-warnings.yml @@ -73,3 +73,16 @@ tests: port: ~ auth: ~ options: ~ + - + description: Comma in a key value pair causes a warning + uri: mongodb://localhost?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2,ENVIRONMENT:azure + valid: true + warning: true + hosts: + - + type: "hostname" + host: "localhost" + port: ~ + auth: ~ + options: + authMechanism: MONGODB-OIDC diff --git a/src/test/spec/json/crud/README.md b/src/test/spec/json/crud/README.md new file mode 100644 index 000000000..598326968 --- /dev/null +++ b/src/test/spec/json/crud/README.md @@ -0,0 +1,710 @@ +# CRUD Tests + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +the CRUD specification. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +## Prose Tests + +### 1. WriteConcernError.details exposes writeConcernError.errInfo + +Test that `writeConcernError.errInfo` in a command response is propagated as `WriteConcernError.details` (or equivalent) +in the driver. + +Using a 4.0+ server, set the following failpoint: + +```javascript +{ + "configureFailPoint": "failCommand", + "data": { + "failCommands": ["insert"], + "writeConcernError": { + "code": 100, + "codeName": "UnsatisfiableWriteConcern", + "errmsg": "Not enough data-bearing nodes", + "errInfo": { + "writeConcern": { + "w": 2, + "wtimeout": 0, + "provenance": "clientSupplied" + } + } + } + }, + "mode": { "times": 1 } +} +``` + +Then, perform an insert operation and assert that a WriteConcernError occurs and that its `details` property is both +accessible and matches the `errInfo` object from the failpoint. + +### 2. WriteError.details exposes writeErrors\[\].errInfo + +Test that `writeErrors[].errInfo` in a command response is propagated as `WriteError.details` (or equivalent) in the +driver. + +Using a 5.0+ server, create a collection with +[document validation](https://www.mongodb.com/docs/manual/core/schema-validation/) like so: + +```javascript +{ + "create": "test", + "validator": { + "x": { $type: "string" } + } +} +``` + +Enable [command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) to observe +CommandSucceededEvents. Then, insert an invalid document (e.g. `{x: 1}`) and assert that a WriteError occurs, that its +code is `121` (i.e. DocumentValidationFailure), and that its `details` property is accessible. Additionally, assert that +a CommandSucceededEvent was observed and that the `writeErrors[0].errInfo` field in the response document matches the +WriteError's `details` property. + +### 3. `MongoClient.bulkWrite` batch splits a `writeModels` input with greater than `maxWriteBatchSize` operations + +Test that `MongoClient.bulkWrite` properly handles `writeModels` inputs containing a number of writes greater than +`maxWriteBatchSize`. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxWriteBatchSize` value contained in the +response. Then, construct the following write model (referred to as `model`): + +```javascript +InsertOne: { + "namespace": "db.coll", + "document": { "a": "b" } +} +``` + +Construct a list of write models (referred to as `models`) with `model` repeated `maxWriteBatchSize + 1` times. Execute +`bulkWrite` on `client` with `models`. Assert that the bulk write succeeds and returns a `BulkWriteResult` with an +`insertedCount` value of `maxWriteBatchSize + 1`. + +Assert that two CommandStartedEvents (referred to as `firstEvent` and `secondEvent`) were observed for the `bulkWrite` +command. Assert that the length of `firstEvent.command.ops` is `maxWriteBatchSize`. Assert that the length of +`secondEvent.command.ops` is 1. If the driver exposes `operationId`s in its CommandStartedEvents, assert that +`firstEvent.operationId` is equal to `secondEvent.operationId`. + +### 4. `MongoClient.bulkWrite` batch splits when an `ops` payload exceeds `maxMessageSizeBytes` + +Test that `MongoClient.bulkWrite` properly handles a `writeModels` input which constructs an `ops` array larger than +`maxMessageSizeBytes`. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the following values from the response: +`maxBsonObjectSize` and `maxMessageSizeBytes`. Then, construct the following document (referred to as `document`): + +```javascript +{ + "a": "b".repeat(maxBsonObjectSize - 500) +} +``` + +Construct the following write model (referred to as `model`): + +```javascript +InsertOne: { + "namespace": "db.coll", + "document": document +} +``` + +Use the following calculation to determine the number of inserts that should be provided to `MongoClient.bulkWrite`: +`maxMessageSizeBytes / maxBsonObjectSize + 1` (referred to as `numModels`). This number ensures that the inserts +provided to `MongoClient.bulkWrite` will require multiple `bulkWrite` commands to be sent to the server. + +Construct as list of write models (referred to as `models`) with `model` repeated `numModels` times. Then execute +`bulkWrite` on `client` with `models`. Assert that the bulk write succeeds and returns a `BulkWriteResult` with an +`insertedCount` value of `numModels`. + +Assert that two CommandStartedEvents (referred to as `firstEvent` and `secondEvent`) were observed. Assert that the +length of `firstEvent.command.ops` is `numModels - 1`. Assert that the length of `secondEvent.command.ops` is 1. If the +driver exposes `operationId`s in its CommandStartedEvents, assert that `firstEvent.operationId` is equal to +`secondEvent.operationId`. + +### 5. `MongoClient.bulkWrite` collects `WriteConcernError`s across batches + +Test that `MongoClient.bulkWrite` properly collects and reports `writeConcernError`s returned in separate batches. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with `retryWrites: false` configured and +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxWriteBatchSize` value contained in the +response. Then, configure the following fail point with `client`: + +```javascript +{ + "configureFailPoint": "failCommand", + "mode": { "times": 2 }, + "data": { + "failCommands": ["bulkWrite"], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } +} +``` + +Construct the following write model (referred to as `model`): + +```javascript +InsertOne: { + "namespace": "db.coll", + "document": { "a": "b" } +} +``` + +Construct a list of write models (referred to as `models`) with `model` repeated `maxWriteBatchSize + 1` times. Execute +`bulkWrite` on `client` with `models`. Assert that the bulk write fails and returns a `BulkWriteError` (referred to as +`error`). + +Assert that `error.writeConcernErrors` has a length of 2. + +Assert that `error.partialResult` is populated. Assert that `error.partialResult.insertedCount` is equal to +`maxWriteBatchSize + 1`. + +Assert that two CommandStartedEvents were observed for the `bulkWrite` command. + +### 6. `MongoClient.bulkWrite` handles individual `WriteError`s across batches + +Test that `MongoClient.bulkWrite` handles individual write errors across batches for ordered and unordered bulk writes. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxWriteBatchSize` value contained in the +response. + +Construct a `MongoCollection` (referred to as `collection`) with the namespace "db.coll" (referred to as `namespace`). +Drop `collection`. Then, construct the following document (referred to as `document`): + +```javascript +{ + "_id": 1 +} +``` + +Insert `document` into `collection`. + +Create the following write model (referred to as `model`): + +```javascript +InsertOne { + "namespace": namespace, + "document": document +} +``` + +Construct a list of write models (referred to as `models`) with `model` repeated `maxWriteBatchSize + 1` times. + +#### Unordered + +Test that an unordered bulk write collects `WriteError`s across batches. + +Execute `bulkWrite` on `client` with `models` and `ordered` set to false. Assert that the bulk write fails and returns a +`BulkWriteError` (referred to as `unorderedError`). + +Assert that `unorderedError.writeErrors` has a length of `maxWriteBatchSize + 1`. + +Assert that two CommandStartedEvents were observed for the `bulkWrite` command. + +#### Ordered + +Test that an ordered bulk write does not execute further batches when a `WriteError` occurs. + +Execute `bulkWrite` on `client` with `models` and `ordered` set to true. Assert that the bulk write fails and returns a +`BulkWriteError` (referred to as `orderedError`). + +Assert that `orderedError.writeErrors` has a length of 1. + +Assert that one CommandStartedEvent was observed for the `bulkWrite` command. + +### 7. `MongoClient.bulkWrite` handles a cursor requiring a `getMore` + +Test that `MongoClient.bulkWrite` properly iterates the results cursor when `getMore` is required. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxBsonObjectSize` value from the +response. + +Construct a `MongoCollection` (referred to as `collection`) with the namespace "db.coll" (referred to as `namespace`). +Drop `collection`. Then create the following list of write models (referred to as `models`): + +```javascript +UpdateOne { + "namespace": namespace, + "filter": { "_id": "a".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +UpdateOne { + "namespace": namespace, + "filter": { "_id": "b".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +``` + +Execute `bulkWrite` on `client` with `models` and `verboseResults` set to true. Assert that the bulk write succeeds and +returns a `BulkWriteResult` (referred to as `result`). + +Assert that `result.upsertedCount` is equal to 2. + +Assert that the length of `result.updateResults` is equal to 2. + +Assert that a CommandStartedEvent was observed for the `getMore` command. + +### 8. `MongoClient.bulkWrite` handles a cursor requiring `getMore` within a transaction + +Test that `MongoClient.bulkWrite` executed within a transaction properly iterates the results cursor when `getMore` is +required. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. This test must not be run +against standalone servers. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxBsonObjectSize` value from the +response. + +Construct a `MongoCollection` (referred to as `collection`) with the namespace "db.coll" (referred to as `namespace`). +Drop `collection`. + +Start a session on `client` (referred to as `session`). Start a transaction on `session`. + +Create the following list of write models (referred to as `models`): + +```javascript +UpdateOne { + "namespace": namespace, + "filter": { "_id": "a".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +UpdateOne { + "namespace": namespace, + "filter": { "_id": "b".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +``` + +Execute `bulkWrite` on `client` with `models`, `session`, and `verboseResults` set to true. Assert that the bulk write +succeeds and returns a `BulkWriteResult` (referred to as `result`). + +Assert that `result.upsertedCount` is equal to 2. + +Assert that the length of `result.updateResults` is equal to 2. + +Assert that a CommandStartedEvent was observed for the `getMore` command. + +### 9. `MongoClient.bulkWrite` handles a `getMore` error + +Test that `MongoClient.bulkWrite` properly handles a failure that occurs when attempting a `getMore`. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxBsonObjectSize` value from the +response. Then, configure the following fail point with `client`: + +```javascript +{ + "configureFailPoint": "failCommand", + "mode": { "times": 1 }, + "data": { + "failCommands": ["getMore"], + "errorCode": 8 + } +} +``` + +Construct a `MongoCollection` (referred to as `collection`) with the namespace "db.coll" (referred to as `namespace`). +Drop `collection`. Then create the following list of write models (referred to as `models`): + +```javascript +UpdateOne { + "namespace": namespace, + "filter": { "_id": "a".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +UpdateOne { + "namespace": namespace, + "filter": { "_id": "b".repeat(maxBsonObjectSize / 2) }, + "update": { "$set": { "x": 1 } }, + "upsert": true +}, +``` + +Execute `bulkWrite` on `client` with `models` and `verboseResults` set to true. Assert that the bulk write fails and +returns a `BulkWriteError` (referred to as `bulkWriteError`). + +Assert that `bulkWriteError.error` is populated with an error (referred to as `topLevelError`). Assert that +`topLevelError.errorCode` is equal to 8. + +Assert that `bulkWriteError.partialResult` is populated with a result (referred to as `partialResult`). Assert that +`partialResult.upsertedCount` is equal to 2. Assert that the length of `partialResult.updateResults` is equal to 1. + +Assert that a CommandStartedEvent was observed for the `getMore` command. + +Assert that a CommandStartedEvent was observed for the `killCursors` command. + +### 10. `MongoClient.bulkWrite` returns error for unacknowledged too-large insert + +Removed. + +#### With insert + +Removed. + +#### With replace + +Removed. + +### 11. `MongoClient.bulkWrite` batch splits when the addition of a new namespace exceeds the maximum message size + +Test that `MongoClient.bulkWrite` batch splits a bulk write when the addition of a new namespace to `nsInfo` causes the +size of the message to exceed `maxMessageSizeBytes - 1000`. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Repeat the following setup for each test case: + +### Setup + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the following values from the response: +`maxBsonObjectSize` and `maxMessageSizeBytes`. + +Calculate the following values: + +```javascript +opsBytes = maxMessageSizeBytes - 1122 +numModels = opsBytes / maxBsonObjectSize +remainderBytes = opsBytes % maxBsonObjectSize +``` + +Construct the following write model (referred to as `firstModel`): + +```javascript +InsertOne { + "namespace": "db.coll", + "document": { "a": "b".repeat(maxBsonObjectSize - 57) } +} +``` + +Create a list of write models (referred to as `models`) with `firstModel` repeated `numModels` times. + +If `remainderBytes` is greater than or equal to 217, add 1 to `numModels` and append the following write model to +`models`: + +```javascript +InsertOne { + "namespace": "db.coll", + "document": { "a": "b".repeat(remainderBytes - 57) } +} +``` + +Then perform the following two tests: + +#### Case 1: No batch-splitting required + +Create the following write model (referred to as `sameNamespaceModel`): + +```javascript +InsertOne { + "namespace": "db.coll", + "document": { "a": "b" } +} +``` + +Append `sameNamespaceModel` to `models`. + +Execute `bulkWrite` on `client` with `models`. Assert that the bulk write succeeds and returns a `BulkWriteResult` +(referred to as `result`). + +Assert that `result.insertedCount` is equal to `numModels + 1`. + +Assert that one CommandStartedEvent was observed for the `bulkWrite` command (referred to as `event`). + +Assert that the length of `event.command.ops` is `numModels + 1`. Assert that the length of `event.command.nsInfo` is 1. +Assert that the namespace contained in `event.command.nsInfo` is "db.coll". + +#### Case 2: Batch-splitting required + +Construct the following namespace (referred to as `namespace`): + +```javascript +"db." + "c".repeat(200) +``` + +Create the following write model (referred to as `newNamespaceModel`): + +```javascript +InsertOne { + "namespace": namespace, + "document": { "a": "b" } +} +``` + +Append `newNamespaceModel` to `models`. + +Execute `bulkWrite` on `client` with `models`. Assert that the bulk write succeeds and returns a `BulkWriteResult` +(referred to as `result`). + +Assert that `result.insertedCount` is equal to `numModels + 1`. + +Assert that two CommandStartedEvents were observed for the `bulkWrite` command (referred to as `firstEvent` and +`secondEvent`). + +Assert that the length of `firstEvent.command.ops` is equal to `numModels`. Assert that the length of +`firstEvent.command.nsInfo` is equal to 1. Assert that the namespace contained in `firstEvent.command.nsInfo` is +"db.coll". + +Assert that the length of `secondEvent.command.ops` is equal to 1. Assert that the length of +`secondEvent.command.nsInfo` is equal to 1. Assert that the namespace contained in `secondEvent.command.nsInfo` is +`namespace`. + +#### Details on size calculations + +This information is not needed to implement this prose test, but is documented for future reference. This test is +designed to work if `maxBsonObjectSize` or `maxMessageSizeBytes` changes, but will need to be updated if a required +field is added to the `bulkWrite` command or the `insert` operation document, or if the overhead `OP_MSG` allowance is +changed in the bulk write specification. + +The command document for the `bulkWrite` has the following structure and size: + +```typescript +{ + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true +} + +// Size: 43 bytes +``` + +Each write model will create an `ops` document with the following structure and size: + +```typescript +{ + "insert": <0 | 1>, + "document": { + "_id": , + "a": + } +} + +// Size: 57 bytes + +``` + +The `ops` document for both `newNamespaceModel` and `sameNamespaceModel` has a string with one character, so it is a +total of 58 bytes. + +The models using the "db.coll" namespace will create one `nsInfo` document with the following structure and size: + +```javascript +{ + "ns": "db.coll" +} + +// Size: 21 bytes +``` + +`newNamespaceModel` will create an `nsInfo` document with the following structure and size: + +```javascript +{ + "ns": "db." +} + +// Size: 217 bytes +``` + +We need to fill up the rest of the message with bytes such that another `ops` document will fit, but another `nsInfo` +entry will not. The following calculations are used: + +```python +# 1000 is the OP_MSG overhead required in the spec +maxBulkWriteBytes = maxMessageSizeBytes - 1000 + +# bulkWrite command + first namespace entry +existingMessageBytes = 43 + 21 + +# Space to fit the last model's ops entry +lastModelBytes = 58 + +remainingBulkWriteBytes = maxBulkWriteBytes - existingMessageBytes - lastModelBytes + +# With the actual numbers plugged in +remainingBulkWriteBytes = maxMessageSizeBytes - 1122 +``` + +### 12. `MongoClient.bulkWrite` returns an error if no operations can be added to `ops` + +Test that `MongoClient.bulkWrite` returns an error if an operation provided exceeds `maxMessageSizeBytes` such that an +empty `ops` payload would be sent. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. This test may be skipped by +drivers that are not able to construct arbitrarily large documents. + +Construct a `MongoClient` (referred to as `client`). Perform a `hello` command using `client` and record the +`maxMessageSizeBytes` value contained in the response. + +#### Case 1: `document` too large + +Construct the following write model (referred to as `largeDocumentModel`): + +```javascript +InsertOne { + "namespace": "db.coll", + "document": { "a": "b".repeat(maxMessageSizeBytes) } +} +``` + +Execute `bulkWrite` on `client` with `largeDocumentModel`. Assert that an error (referred to as `error`) is returned. +Assert that `error` is a client error. + +#### Case 2: `namespace` too large + +Construct the following namespace (referred to as `namespace`): + +```javascript +"db." + "c".repeat(maxMessageSizeBytes) +``` + +Construct the following write model (referred to as `largeNamespaceModel`): + +```javascript +InsertOne { + "namespace": namespace, + "document": { "a": "b" } +} +``` + +Execute `bulkWrite` on `client` with `largeNamespaceModel`. Assert that an error (referred to as `error`) is returned. +Assert that `error` is a client error. + +### 13. `MongoClient.bulkWrite` returns an error if auto-encryption is configured + +This test is expected to be removed when [DRIVERS-2888](https://jira.mongodb.org/browse/DRIVERS-2888) is resolved. + +Test that `MongoClient.bulkWrite` returns an error if the client has auto-encryption configured. + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +Construct a `MongoClient` (referred to as `client`) configured with the following `AutoEncryptionOpts`: + +```javascript +AutoEncryptionOpts { + "keyVaultNamespace": "db.coll", + "kmsProviders": { + "aws": { + "accessKeyId": "foo", + "secretAccessKey": "bar" + } + } +} +``` + +Construct the following write model (referred to as `model`): + +```javascript +InsertOne { + "namespace": "db.coll", + "document": { "a": "b" } +} +``` + +Execute `bulkWrite` on `client` with `model`. Assert that an error (referred to as `error`) is returned. Assert that +`error` is a client error containing the message: "bulkWrite does not currently support automatic encryption". + +### 14. `explain` helpers allow users to specify `maxTimeMS` + +Drivers that provide multiple APIs to specify explain should ensure this test is run at least once with each distinct +API. For example, the Node driver runs this test with option API (`collection.find({}, { explain: ... })`) and the +fluent API (`collection.find({}).explain(...)`). + +Create a MongoClient with command monitoring enabled (referred to as `client`). + +Create a collection, referred to as `collection`, with the namespace `explain-test.collection`. + +Run an explained find on `collection`. The find will have the query predicate `{ name: 'john doe' }`. Specify a +maxTimeMS value of 2000ms for the `explain`. + +Obtain the command started event for the explain. Confirm that the top-level explain command should has a `maxTimeMS` +value of `2000`. + +### 15. `MongoClient.bulkWrite` with unacknowledged write concern uses `w:0` for all batches + +This test must only be run on 8.0+ servers. This test must be skipped on Atlas Serverless. + +If testing with a sharded cluster, only connect to one mongos. This is intended to ensure the `countDocuments` operation +uses the same connection as the `bulkWrite` to get the correct connection count. (See +[DRIVERS-2921](https://jira.mongodb.org/browse/DRIVERS-2921)). + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. Perform a `hello` command using `client` and record the `maxBsonObjectSize` and +`maxMessageSizeBytes` values in the response. + +Construct a `MongoCollection` (referred to as `coll`) for the collection "db.coll". Drop `coll`. + +Use the `create` command to create "db.coll" to workaround [SERVER-95537](https://jira.mongodb.org/browse/SERVER-95537). + +Construct the following write model (referred to as `model`): + +```javascript +InsertOne: { + "namespace": "db.coll", + "document": { "a": "b".repeat(maxBsonObjectSize - 500) } +} +``` + +Construct a list of write models (referred to as `models`) with `model` repeated +`maxMessageSizeBytes / maxBsonObjectSize + 1` times. + +Call `client.bulkWrite` with `models`. Pass `BulkWriteOptions` with `ordered` set to `false` and `writeConcern` set to +an unacknowledged write concern. Assert no error occurred. Assert the result indicates the write was unacknowledged. + +Assert that two CommandStartedEvents (referred to as `firstEvent` and `secondEvent`) were observed for the `bulkWrite` +command. Assert that the length of `firstEvent.command.ops` is `maxMessageSizeBytes / maxBsonObjectSize`. Assert that +the length of `secondEvent.command.ops` is 1. If the driver exposes `operationId`s in its CommandStartedEvents, assert +that `firstEvent.operationId` is equal to `secondEvent.operationId`. Assert both commands include +`writeConcern: {w: 0}`. + +To force completion of the `w:0` writes, execute `coll.countDocuments` and expect the returned count is +`maxMessageSizeBytes / maxBsonObjectSize + 1`. This is intended to avoid incomplete writes interfering with other tests +that may use this collection. + +### 16. Generated document identifiers are the first field in their document + +Construct a `MongoClient` (referred to as `client`) with +[command monitoring](../../command-logging-and-monitoring/command-logging-and-monitoring.md) enabled to observe +CommandStartedEvents. For each of `insertOne`, client `bulkWrite`, and collection `bulkWrite`, do the following: + +- Execute the command with a document that does not contain an `_id` field. +- If possible, capture the wire protocol message (referred to as `request`) of the command and assert that the first + field of `request.documents[0]` is `_id`. +- Otherwise, capture the CommandStartedEvent (referred to as `event`) emitted by the command and assert that the first + field of `event.command.documents[0]` is `_id`. diff --git a/src/test/spec/json/crud/README.rst b/src/test/spec/json/crud/README.rst deleted file mode 100644 index 0bfea3cd1..000000000 --- a/src/test/spec/json/crud/README.rst +++ /dev/null @@ -1,278 +0,0 @@ -========== -CRUD Tests -========== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the CRUD spec. - -Running these integration tests will require a running MongoDB server or -cluster with server versions 2.6.0 or later. Some tests have specific server -version requirements as noted by the ``runOn`` section, if provided. - -Subdirectories for Test Formats -------------------------------- - -This document describes a legacy format for CRUD tests: legacy-v1, which dates back -to the first version of the CRUD specification. New CRUD tests should be written -in the `unified test format <../../unified-test-format/unified-test-format.rst>`_ -and placed under ``unified/``. Until such time that all original tests have been ported -to the unified test format, tests in each format will be grouped in their own subdirectory: - -- ``v1/``: Legacy-v1 format tests -- ``unified/``: Tests using the `unified test format <../../unified-test-format/unified-test-format.rst>`_ - -Since some drivers may not have a unified test runner capable of executing tests -in all two formats, segregating tests in this manner will make it easier for -drivers to sync and feed test files to different test runners. - -Legacy-v1 Test Format for Single Operations -------------------------------------------- - -*Note: this section pertains to test files in the "v1" directory.* - -The test format above supports both multiple operations and APM expectations, -and is consistent with the formats used by other specifications. Previously, the -CRUD spec tests used a simplified format that only allowed for executing a -single operation. Notable differences from the legacy-v2 format are as follows: - -- Instead of a ``tests[i].operations`` array, a single operation was defined as - a document in ``tests[i].operation``. That document consisted of only the - ``name``, ``arguments``, and an optional ``object`` field. - -- Instead of ``error`` and ``result`` fields within each element in the - ``tests[i].operations`` array, the single operation's error and result were - defined under the ``tests[i].outcome.error`` and ``tests[i].outcome.result`` - fields. - -- Instead of a top-level ``runOn`` field, server requirements are denoted by - separate top-level ``minServerVersion``, ``maxServerVersion``, and - ``serverless`` fields. The minimum server version is an inclusive lower bound - for running the test. The maximum server version is an exclusive upper bound - for running the test. If a field is not present, it should be assumed that - there is no corresponding bound on the required server version. The - ``serverless`` requirement behaves the same as the ``serverless`` field of the - `unified test format's runOnRequirement <../../unified-test-format/unified-test-format.rst#runonrequirement>`_. - -The legacy-v1 format should not conflict with the newer, multi-operation format -used by other specs (e.g. Transactions). It is possible to create a unified test -runner capable of executing both legacy formats (as some drivers do). - -Error Assertions for Bulk Write Operations -========================================== - -When asserting errors (e.g. ``errorContains``, ``errorCodeName``) for bulk write -operations, the test harness should inspect the ``writeConcernError`` and/or -``writeErrors`` properties of the bulk write exception. This may not be needed for -``errorContains`` if a driver concatenates all write and write concern error -messages into the bulk write exception's top-level message. - -Test Runner Implementation -========================== - -This section provides guidance for implementing a test runner for legacy-v1 -tests. See the `unified test format spec <../../../../unified-test-format/unified-test-format.rst>`_ for how to run tests under -``unified/``. - -Before running the tests: - -- Create a global MongoClient (``globalMongoClient``) and connect to the server. - This client will be used for executing meta operations, such as checking - server versions and preparing data fixtures. - -For each test file: - -- Using ``globalMongoClient``, check that the current server version satisfies - one of the configurations provided in the top-level ``runOn`` field in the test - file (if applicable). If the - requirements are not satisifed, the test file should be skipped. - -- Determine the collection and database under test, utilizing the top-level - ``collection_name`` and/or ``database_name`` fields if present. - -- For each element in the ``tests`` array: - - - Using ``globalMongoClient``, ensure that the collection and/or database - under test is in a "clean" state, as needed. This may be accomplished by - dropping the database; however, drivers may also decide to drop individual - collections as needed (this may be more performant). - - - If the top-level ``data`` field is present in the test file, insert the - corresponding data into the collection under test using - ``globalMongoClient``. - - - If the the ``failPoint`` field is present, use ``globalMongoClient`` to - configure the fail point on the primary server. See - `Server Fail Point <../../transactions/tests#server-fail-point>`_ in the - Transactions spec test documentation for more information. - - - Create a local MongoClient (``localMongoClient``) and connect to the server. - This client will be used for executing the test case. - - - If ``clientOptions`` is present, those options should be used to create - the client. Drivers MAY merge these options atop existing defaults (e.g. - reduced ``serverSelectionTimeoutMS`` value for faster test failures) at - their own discretion. - - - Activate command monitoring for ``localMongoClient`` and begin capturing - events. Note that some events may need to be filtered out if the driver - uses global listeners or reports internal commands (e.g. ``hello``, legacy - hello, authentication). - - - For each element in the ``operations`` array: - - - Using ``localMongoClient``, select the appropriate ``object`` to execute - the operation. Default to the collection under test if this field is not - present. - - - If ``collectionOptions`` is present, those options should be used to - construct the collection object. - - - Given the ``name`` and ``arguments``, execute the operation on the object - under test. Capture the result of the operation, if any, and observe - whether an error occurred. If an error is encountered that includes a - result (e.g. BulkWriteException), extract the result object. - - - If ``error`` is present and true, assert that the operation encountered an - error. Otherwise, assert that no error was encountered. - - - if ``result`` is present, assert that it matches the operation's result. - - - Deactivate command monitoring for ``localMongoClient``. - - - If the ``expectations`` array is present, assert that the sequence of - emitted CommandStartedEvents from executing the operation(s) matches the - sequence of ``command_started_event`` objects in the ``expectations`` array. - - - If the ``outcome`` field is present, assert the contents of the specified - collection using ``globalMongoClient``. - Note the server does not guarantee that documents returned by a find - command will be in inserted order. This find MUST sort by ``{_id:1}``. - -Evaluating Matches ------------------- - -The expected values for results (e.g. ``result`` for an operation -operation, ``command_started_event.command``, elements in ``outcome.data``) are -written in `Extended JSON <../../extended-json.rst>`_. Drivers may adopt any of -the following approaches to comparisons, as long as they are consistent: - -- Convert ``actual`` to Extended JSON and compare to ``expected`` -- Convert ``expected`` and ``actual`` to BSON, and compare them -- Convert ``expected`` and ``actual`` to native representations, and compare - them - -Extra Fields in Actual Documents -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When comparing ``actual`` and ``expected`` *documents*, drivers should permit -``actual`` documents to contain additional fields not present in ``expected``. -For example, the following documents match: - -- ``expected`` is ``{ "x": 1 }`` -- ``actual`` is ``{ "_id": { "$oid" : "000000000000000000000001" }, "x": 1 }`` - -In this sense, ``expected`` may be a subset of ``actual``. It may also be -helpful to think of ``expected`` as a form of query criteria. The intention -behind this rule is that it is not always feasible for the test to express all -fields in the expected document(s) (e.g. session and cluster time information -in a ``command_started_event.command`` document). - -This rule for allowing extra fields in ``actual`` only applies for values that -correspond to a document. For instance, an actual result of ``[1, 2, 3, 4]`` for -a ``distinct`` operation would not match an expected result of ``[1, 2, 3]``. -Likewise with the ``find`` operation, this rule would only apply when matching -documents *within* the expected result array and actual cursor. - -Note that in the case of result objects for some CRUD operations, ``expected`` -may condition additional, optional fields (see: -`Optional Fields in Expected Result Objects`_). - -Fields that must NOT be present in Actual Documents -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some command-started events in ``expectations`` include ``null`` values for -optional fields such as ``allowDiskUse``. -Tests MUST assert that the actual command **omits** any field that has a -``null`` value in the expected command. - -Optional Fields in Expected Result Objects -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some ``expected`` results may include fields that are optional in the CRUD -specification, such as ``insertedId`` (for InsertOneResult), ``insertedIds`` -(for InsertManyResult), and ``upsertedCount`` (for UpdateResult). Drivers that -do not implement these fields should ignore them when comparing ``actual`` with -``expected``. - -Prose Tests -=========== - -The following tests have not yet been automated, but MUST still be tested. - -1. WriteConcernError.details exposes writeConcernError.errInfo --------------------------------------------------------------- - -Test that ``writeConcernError.errInfo`` in a command response is propagated as -``WriteConcernError.details`` (or equivalent) in the driver. - -Using a 4.0+ server, set the following failpoint: - -.. code:: javascript - - { - "configureFailPoint": "failCommand", - "data": { - "failCommands": ["insert"], - "writeConcernError": { - "code": 100, - "codeName": "UnsatisfiableWriteConcern", - "errmsg": "Not enough data-bearing nodes", - "errInfo": { - "writeConcern": { - "w": 2, - "wtimeout": 0, - "provenance": "clientSupplied" - } - } - } - }, - "mode": { "times": 1 } - } - -Then, perform an insert operation and assert that a WriteConcernError occurs and -that its ``details`` property is both accessible and matches the ``errInfo`` -object from the failpoint. - -2. WriteError.details exposes writeErrors[].errInfo ---------------------------------------------------- - -Test that ``writeErrors[].errInfo`` in a command response is propagated as -``WriteError.details`` (or equivalent) in the driver. - -Using a 5.0+ server, create a collection with -`document validation `_ -like so: - -.. code:: javascript - - { - "create": "test", - "validator": { - "x": { $type: "string" } - } - } - -Enable `command monitoring <../../command-monitoring/command-monitoring.rst>`_ -to observe CommandSucceededEvents. Then, insert an invalid document (e.g. -``{x: 1}``) and assert that a WriteError occurs, that its code is ``121`` -(i.e. DocumentValidationFailure), and that its ``details`` property is -accessible. Additionally, assert that a CommandSucceededEvent was observed and -that the ``writeErrors[0].errInfo`` field in the response document matches the -WriteError's ``details`` property. diff --git a/src/test/spec/json/crud/unified/aggregate-collation.json b/src/test/spec/json/crud/unified/aggregate-collation.json new file mode 100644 index 000000000..e7f0c3a7f --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-collation.json @@ -0,0 +1,73 @@ +{ + "description": "aggregate-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with collation", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "x": "PING" + } + } + ], + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": [ + { + "_id": 1, + "x": "ping" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/aggregate-collation.yml b/src/test/spec/json/crud/unified/aggregate-collation.yml new file mode 100644 index 000000000..088dc5705 --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-collation.yml @@ -0,0 +1,48 @@ +description: aggregate-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: ping } + +tests: + - + description: 'Aggregate with collation' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $match: { x: PING } } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + - { _id: 1, x: ping } diff --git a/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.json b/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.json new file mode 100644 index 000000000..6c7305fd9 --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.json @@ -0,0 +1,90 @@ +{ + "description": "aggregate-merge-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 1 + } + ] + } + ], + "tests": [ + { + "description": "aggregate $merge DuplicateKey error is accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.1", + "topologies": [ + "single", + "replicaset" + ] + } + ], + "operations": [ + { + "name": "aggregate", + "object": "database0", + "arguments": { + "pipeline": [ + { + "$documents": [ + { + "_id": 2, + "x": 1 + } + ] + }, + { + "$merge": { + "into": "test", + "whenMatched": "fail" + } + } + ] + }, + "expectError": { + "errorCode": 11000, + "errorResponse": { + "keyPattern": { + "_id": 1 + }, + "keyValue": { + "_id": 2 + } + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.yml b/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.yml new file mode 100644 index 000000000..5fd679bff --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-merge-errorResponse.yml @@ -0,0 +1,42 @@ +description: "aggregate-merge-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 1 } + +tests: + - description: "aggregate $merge DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "5.1" # SERVER-59097 + # Exclude sharded topologies since the aggregate command fails with + # IllegalOperation(20) instead of DuplicateKey(11000) + topologies: [ single, replicaset ] + operations: + - name: aggregate + object: *database0 + arguments: + pipeline: + - { $documents: [ { _id: 2, x: 1 } ] } + - { $merge: { into: *collection0Name, whenMatched: "fail" } } + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { _id: 1 } + keyValue: { _id: 2 } diff --git a/src/test/spec/json/crud/unified/aggregate-merge.yml b/src/test/spec/json/crud/unified/aggregate-merge.yml index 821f03e1c..48dd54d1b 100644 --- a/src/test/spec/json/crud/unified/aggregate-merge.yml +++ b/src/test/spec/json/crud/unified/aggregate-merge.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: aggregate-merge schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/aggregate-out-readConcern.yml b/src/test/spec/json/crud/unified/aggregate-out-readConcern.yml index c210c4692..67bdee676 100644 --- a/src/test/spec/json/crud/unified/aggregate-out-readConcern.yml +++ b/src/test/spec/json/crud/unified/aggregate-out-readConcern.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: aggregate-out-readConcern schemaVersion: '1.4' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/aggregate-out.json b/src/test/spec/json/crud/unified/aggregate-out.json new file mode 100644 index 000000000..db0d7918c --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-out.json @@ -0,0 +1,143 @@ +{ + "description": "aggregate-out", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "2.6", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with $out", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "batchSize": 2 + } + } + ], + "outcome": [ + { + "collectionName": "other_test_collection", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "Aggregate with $out and batch size of 0", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "batchSize": 0 + } + } + ], + "outcome": [ + { + "collectionName": "other_test_collection", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/aggregate-out.yml b/src/test/spec/json/crud/unified/aggregate-out.yml new file mode 100644 index 000000000..a85eed032 --- /dev/null +++ b/src/test/spec/json/crud/unified/aggregate-out.yml @@ -0,0 +1,72 @@ +description: aggregate-out + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '2.6' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'Aggregate with $out' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $out: other_test_collection } + batchSize: 2 + outcome: + - + collectionName: other_test_collection + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'Aggregate with $out and batch size of 0' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $out: other_test_collection } + batchSize: 0 + outcome: + - + collectionName: other_test_collection + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/aggregate-write-readPreference.json b/src/test/spec/json/crud/unified/aggregate-write-readPreference.json index bc887e83c..c1fa3b457 100644 --- a/src/test/spec/json/crud/unified/aggregate-write-readPreference.json +++ b/src/test/spec/json/crud/unified/aggregate-write-readPreference.json @@ -78,11 +78,6 @@ "x": 33 } ] - }, - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [] } ], "tests": [ @@ -159,22 +154,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -250,22 +229,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -344,22 +307,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] }, { @@ -438,22 +385,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll1", - "databaseName": "db0", - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } ] } ] diff --git a/src/test/spec/json/crud/unified/aggregate-write-readPreference.yml b/src/test/spec/json/crud/unified/aggregate-write-readPreference.yml index 86f5a4399..16f103575 100644 --- a/src/test/spec/json/crud/unified/aggregate-write-readPreference.yml +++ b/src/test/spec/json/crud/unified/aggregate-write-readPreference.yml @@ -51,9 +51,6 @@ initialData: - { _id: 1, x: 11 } - { _id: 2, x: 22 } - { _id: 3, x: 33 } - - collectionName: *collection1Name - databaseName: *database0Name - documents: [] tests: - description: "Aggregate with $out includes read preference for 5.0+ server" @@ -78,12 +75,6 @@ tests: $readPreference: *readPreference readConcern: *readConcern writeConcern: *writeConcern - outcome: &outcome - - collectionName: *collection1Name - databaseName: *database0Name - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - description: "Aggregate with $out omits read preference for pre-5.0 server" runOnRequirements: @@ -108,7 +99,6 @@ tests: $readPreference: { $$exists: false } readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome - description: "Aggregate with $merge includes read preference for 5.0+ server" runOnRequirements: @@ -131,7 +121,6 @@ tests: $readPreference: *readPreference readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome - description: "Aggregate with $merge omits read preference for pre-5.0 server" runOnRequirements: @@ -152,4 +141,3 @@ tests: $readPreference: { $$exists: false } readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome diff --git a/src/test/spec/json/crud/unified/aggregate.json b/src/test/spec/json/crud/unified/aggregate.json index 0cbfb4e6e..55634f05f 100644 --- a/src/test/spec/json/crud/unified/aggregate.json +++ b/src/test/spec/json/crud/unified/aggregate.json @@ -562,6 +562,54 @@ ] } ] + }, + { + "description": "Aggregate with multiple stages", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "batchSize": 2 + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + } + ] + } + ] } ] } diff --git a/src/test/spec/json/crud/unified/aggregate.yml b/src/test/spec/json/crud/unified/aggregate.yml index 032aece0f..6f99aaf0f 100644 --- a/src/test/spec/json/crud/unified/aggregate.yml +++ b/src/test/spec/json/crud/unified/aggregate.yml @@ -213,3 +213,20 @@ tests: comment: { $$exists: false } commandName: getMore databaseName: *database0Name + - + description: 'Aggregate with multiple stages' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + batchSize: 2 + expectResult: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } diff --git a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters-clientError.yml b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters-clientError.yml index 8b4c7a1c9..12bc84c70 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters-clientError.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-arrayFilters-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.json b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.json index 70ee014f7..bc4e7b9fc 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.json +++ b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.json @@ -274,6 +274,91 @@ ] } ] + }, + { + "description": "BulkWrite with arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + }, + { + "updateMany": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 3, + "modifiedCount": 3, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 2 + } + ] + } + ] + } + ] } ] } diff --git a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.yml b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.yml index a236acb12..9db833581 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-arrayFilters.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-arrayFilters schemaVersion: '1.0' runOnRequirements: @@ -172,3 +169,42 @@ tests: b: 0 - b: 2 + - + description: 'BulkWrite with arrayFilters' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + # UpdateOne when one document matches arrayFilters + updateOne: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 3 } + - + # UpdateMany when multiple documents match arrayFilters + updateMany: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 1 } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 3 + modifiedCount: 3 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 2 }, { b: 2 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 2 } ] } diff --git a/src/test/spec/json/crud/unified/bulkWrite-collation.json b/src/test/spec/json/crud/unified/bulkWrite-collation.json new file mode 100644 index 000000000..fe54b1a1e --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-collation.json @@ -0,0 +1,254 @@ +{ + "description": "bulkWrite-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + }, + { + "_id": 4, + "x": "pong" + }, + { + "_id": 5, + "x": "pONg" + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite with delete operations and collation", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "deleteOne": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "deleteMany": { + "filter": { + "x": "PONG" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 4, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "BulkWrite with update operations and collation", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateMany": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 3 + } + } + }, + { + "updateOne": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "replaceOne": { + "filter": { + "x": "ping" + }, + "replacement": { + "_id": 6, + "x": "ping" + }, + "upsert": true, + "collation": { + "locale": "en_US", + "strength": 3 + } + } + }, + { + "updateMany": { + "filter": { + "x": "pong" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 6, + "modifiedCount": 4, + "upsertedCount": 1, + "upsertedIds": { + "2": 6 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "PONG" + }, + { + "_id": 3, + "x": "PONG" + }, + { + "_id": 4, + "x": "PONG" + }, + { + "_id": 5, + "x": "PONG" + }, + { + "_id": 6, + "x": "ping" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-collation.yml b/src/test/spec/json/crud/unified/bulkWrite-collation.yml new file mode 100644 index 000000000..b9b3fc4fe --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-collation.yml @@ -0,0 +1,130 @@ +description: bulkWrite-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + - { _id: 4, x: pong } + - { _id: 5, x: pONg } + +# See: https://www.mongodb.com/docs/manual/reference/collation/#collation-document +tests: + - + description: 'BulkWrite with delete operations and collation' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + # matches two documents but deletes one + deleteOne: + filter: { x: "PING" } + collation: { locale: "en_US", strength: 2 } + - + # matches the remaining document and deletes it + deleteOne: + filter: { x: "PING" } + collation: { locale: "en_US", strength: 2 } + - + # matches two documents and deletes them + deleteMany: + filter: { x: "PONG" } + collation: { locale: "en_US", strength: 2 } + ordered: true + expectResult: + deletedCount: 4 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'BulkWrite with update operations and collation' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + # matches only one document due to strength and updates + updateMany: + filter: { x: "ping" } + update: { $set: { x: "PONG" } } + collation: { locale: "en_US", strength: 3 } + - + # matches one document and updates + updateOne: + filter: { x: "ping" } + update: { $set: { x: "PONG" } } + collation: { locale: "en_US", strength: 2 } + - + # matches no document due to strength and upserts + replaceOne: + filter: { x: "ping" } + replacement: { _id: 6, x: "ping" } + upsert: true + collation: { locale: "en_US", strength: 3 } + - + # matches two documents and updates + updateMany: + filter: { x: "pong" } + update: { $set: { x: "PONG" } } + collation: { locale: "en_US", strength: 2 } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 6 + modifiedCount: 4 + upsertedCount: 1 + upsertedIds: { '2': 6 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - {_id: 1, x: 11 } + - {_id: 2, x: "PONG" } + - {_id: 3, x: "PONG" } + - {_id: 4, x: "PONG" } + - {_id: 5, x: "PONG" } + - {_id: 6, x: "ping" } diff --git a/src/test/spec/json/crud/unified/bulkWrite-delete-hint-clientError.yml b/src/test/spec/json/crud/unified/bulkWrite-delete-hint-clientError.yml index 2b0bdb1c2..5c1726100 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-delete-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-delete-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-delete-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-delete-hint-serverError.yml b/src/test/spec/json/crud/unified/bulkWrite-delete-hint-serverError.yml index e757bade0..06ad7787a 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-delete-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-delete-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-delete-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-delete-hint.yml b/src/test/spec/json/crud/unified/bulkWrite-delete-hint.yml index 8b7f84aa9..ac451bfc4 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-delete-hint.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-delete-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-delete-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-errorResponse.json b/src/test/spec/json/crud/unified/bulkWrite-errorResponse.json new file mode 100644 index 000000000..157637c71 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-errorResponse.json @@ -0,0 +1,88 @@ +{ + "description": "bulkWrite-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "bulkWrite operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-errorResponse.yml b/src/test/spec/json/crud/unified/bulkWrite-errorResponse.yml new file mode 100644 index 000000000..d4f335dfd --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-errorResponse.yml @@ -0,0 +1,50 @@ +description: "bulkWrite-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # This test intentionally executes only a single insert operation in the bulk + # write to make the error code and response assertions less ambiguous. That + # said, some drivers may still need to skip this test because the CRUD spec + # does not prescribe how drivers should formulate a BulkWriteException beyond + # collecting write and write concern errors. + - description: "bulkWrite operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: bulkWrite + object: *collection0 + arguments: + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.json b/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.json new file mode 100644 index 000000000..c0bd38351 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.json @@ -0,0 +1,239 @@ +{ + "description": "BulkWrite replaceOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite replaceOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + }, + { + "description": "BulkWrite replaceOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.yml b/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.yml new file mode 100644 index 000000000..6f326fe04 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-replaceOne-sort.yml @@ -0,0 +1,94 @@ +description: BulkWrite replaceOne-sort + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent, commandSucceededEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: BulkWrite replaceOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - replaceOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - commandSucceededEvent: + reply: { ok: 1, n: 1 } + commandName: update + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 1 } + + - description: BulkWrite replaceOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - replaceOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectError: + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/bulkWrite-update-hint-clientError.yml b/src/test/spec/json/crud/unified/bulkWrite-update-hint-clientError.yml index df1eae485..cf75a7c45 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-update-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-update-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-update-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-update-hint-serverError.yml b/src/test/spec/json/crud/unified/bulkWrite-update-hint-serverError.yml index 0b4ed166e..64310a2ea 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-update-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-update-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-update-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-update-hint.yml b/src/test/spec/json/crud/unified/bulkWrite-update-hint.yml index 9f5a0e080..e739074ea 100644 --- a/src/test/spec/json/crud/unified/bulkWrite-update-hint.yml +++ b/src/test/spec/json/crud/unified/bulkWrite-update-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: bulkWrite-update-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.json b/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.json new file mode 100644 index 000000000..e938ea753 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.json @@ -0,0 +1,148 @@ +{ + "description": "bulkWrite-updateMany-pipeline", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany in bulk write using pipelines", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateMany": { + "filter": {}, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ] + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + }, + "commandName": "update", + "databaseName": "crud-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.yml b/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.yml new file mode 100644 index 000000000..d6503e2aa --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateMany-pipeline.yml @@ -0,0 +1,67 @@ +description: bulkWrite-updateMany-pipeline + +schemaVersion: '1.0' + +runOnRequirements: + - minServerVersion: 4.1.11 + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-tests + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name test + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, y: 1, t: { u: { v: 1 } } } + - { _id: 2, x: 2, y: 1 } + +tests: + - + description: 'UpdateMany in bulk write using pipelines' + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - updateMany: + filter: { } + update: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - q: { } + u: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + multi: true + upsert: { $$unsetOrMatches: false } + commandName: update + databaseName: *database_name + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, foo: 1 } + - { _id: 2, x: 2, foo: 1 } diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.json b/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.json new file mode 100644 index 000000000..769bd106f --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.json @@ -0,0 +1,156 @@ +{ + "description": "bulkWrite-updateOne-pipeline", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne in bulk write using pipelines", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + }, + "commandName": "update", + "databaseName": "crud-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "u": { + "v": 1 + }, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.yml b/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.yml new file mode 100644 index 000000000..9e15c0bfa --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateOne-pipeline.yml @@ -0,0 +1,66 @@ +description: bulkWrite-updateOne-pipeline + +schemaVersion: '1.0' + +runOnRequirements: + - minServerVersion: 4.1.11 + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-tests + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name test + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, y: 1, t: { u: { v: 1 } } } + - { _id: 2, x: 2, y: 1 } + +tests: + - + description: 'UpdateOne in bulk write using pipelines' + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - updateOne: + filter: { _id: 1 } + update: + - { $replaceRoot: { newRoot: $t } } + - { $addFields: { foo: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - q: { _id: 1 } + u: + - { $replaceRoot: { newRoot: $t } } + - { $addFields: { foo: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + commandName: update + databaseName: *database_name + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, u: { v: 1 }, foo: 1 } + - { _id: 2, x: 2, y: 1 } diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.json b/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.json new file mode 100644 index 000000000..f78bd3bf3 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.json @@ -0,0 +1,255 @@ +{ + "description": "BulkWrite updateOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite updateOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": [ + { + "$set": { + "x": 1 + } + } + ] + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": [ + { + "$set": { + "x": 1 + } + } + ], + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + }, + { + "description": "BulkWrite updateOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": [ + { + "$set": { + "x": 1 + } + } + ] + } + } + ] + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": [ + { + "$set": { + "x": 1 + } + } + ], + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.yml b/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.yml new file mode 100644 index 000000000..72bc814d6 --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite-updateOne-sort.yml @@ -0,0 +1,94 @@ +description: BulkWrite updateOne-sort + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent, commandSucceededEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: BulkWrite updateOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - updateOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: [ $set: { x: 1 } ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: [ $set: { x: 1 } ] + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - commandSucceededEvent: + reply: { ok: 1, n: 1 } + commandName: update + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 1 } + + - description: BulkWrite updateOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - updateOne: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: [ $set: { x: 1 } ] + expectError: + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: [ $set: { x: 1 } ] + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/bulkWrite.json b/src/test/spec/json/crud/unified/bulkWrite.json new file mode 100644 index 000000000..59b33cbac --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite.json @@ -0,0 +1,829 @@ +{ + "description": "bulkWrite", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite with deleteOne operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 3 + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 2 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "BulkWrite with deleteMany operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteMany": { + "filter": { + "x": { + "$lt": 11 + } + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$lte": 22 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 2, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [] + } + ] + }, + { + "description": "BulkWrite with insertOne operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "insertedIds": { + "$$unsetOrMatches": { + "0": 3, + "1": 4 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "BulkWrite with replaceOne operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 33 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 12 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 33 + }, + "upsert": true + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "2": 3 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite with updateOne operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": 0 + }, + "update": { + "$set": { + "x": 0 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 11 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 33 + } + }, + "upsert": true + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 2, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "3": 3 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite with updateMany operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateMany": { + "filter": { + "x": { + "$lt": 11 + } + }, + "update": { + "$set": { + "x": 0 + } + } + } + }, + { + "updateMany": { + "filter": { + "x": { + "$lte": 22 + } + }, + "update": { + "$unset": { + "y": 1 + } + } + } + }, + { + "updateMany": { + "filter": { + "x": { + "$lte": 22 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 33 + } + }, + "upsert": true + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 4, + "modifiedCount": 2, + "upsertedCount": 1, + "upsertedIds": { + "3": 3 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite with mixed ordered operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "deleteMany": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 2, + "insertedCount": 2, + "insertedIds": { + "$$unsetOrMatches": { + "0": 3, + "3": 4 + } + }, + "matchedCount": 3, + "modifiedCount": 3, + "upsertedCount": 1, + "upsertedIds": { + "5": 4 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 34 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "BulkWrite with mixed unordered operations", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "replaceOne": { + "filter": { + "_id": 3 + }, + "replacement": { + "_id": 3, + "x": 33 + }, + "upsert": true + } + }, + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": false + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "0": 3 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "ordered": false + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "ordered": false + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bulkWrite.yml b/src/test/spec/json/crud/unified/bulkWrite.yml new file mode 100644 index 000000000..677e6725c --- /dev/null +++ b/src/test/spec/json/crud/unified/bulkWrite.yml @@ -0,0 +1,448 @@ +description: bulkWrite + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '2.6' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'BulkWrite with deleteOne operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + # Note: as in the "DeleteOne when many documents match" test in + # deleteOne.yml, we omit a deleteOne operation that might match + # multiple documents as that would hinder our ability to assert the + # final state of the collection under test. + requests: + - + # does not match an existing document + deleteOne: + filter: { _id: 3 } + - + # deletes the matched document + deleteOne: + filter: { _id: 2 } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'BulkWrite with deleteMany operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + # does not match any existing documents + deleteMany: + filter: { x: { $lt: 11 } } + - + # deletes the matched documents + deleteMany: + filter: { x: { $lte: 22 } } + ordered: true + expectResult: + deletedCount: 2 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'BulkWrite with insertOne operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 3, x: 33 } + - + insertOne: + document: { _id: 4, x: 44 } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 2 + insertedIds: + $$unsetOrMatches: + '0': 3 + '1': 4 + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'BulkWrite with replaceOne operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + # Note: as in the "ReplaceOne when many documents match" test in + # replaceOne.yml, we omit a replaceOne operation that might match + # multiple documents as that would hinder our ability to assert the + # final state of the collection under test. + requests: + - + # does not match an existing document + replaceOne: + filter: { _id: 3 } + replacement: { x: 33 } + - + # modifies the matched document + replaceOne: + filter: { _id: 1 } + replacement: { x: 12 } + - + # does not match an existing document and upserts + replaceOne: + filter: { _id: 3 } + replacement: { x: 33 } + upsert: true + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 1 + upsertedIds: { '2': 3 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite with updateOne operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + # Note: as in the "UpdateOne when many documents match" test in + # updateOne.yml, we omit an updateOne operation that might match + # multiple documents as that would hinder our ability to assert the + # final state of the collection under test. + requests: + - + # does not match an existing document + updateOne: + filter: { _id: 0 } + update: { $set: { x: 0 } } + - + # does not modify the matched document + updateOne: + filter: { _id: 1 } + update: { $set: { x: 11 } } + - + # modifies the matched document + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + # does not match an existing document and upserts + updateOne: + filter: { _id: 3 } + update: { $set: { x: 33 } } + upsert: true + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 2 + modifiedCount: 1 + upsertedCount: 1 + upsertedIds: { '3': 3 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite with updateMany operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + # does not match any existing documents + updateMany: + filter: { x: { $lt: 11 } } + update: { $set: { x: 0 } } + - + # does not modify the matched documents + updateMany: + filter: { x: { $lte: 22 } } + update: { $unset: { y: 1 } } + - + # modifies the matched documents + updateMany: + filter: { x: { $lte: 22 } } + update: { $inc: { x: 1 } } + - + # does not match any existing documents and upserts + updateMany: + filter: { _id: 3 } + update: { $set: { x: 33 } } + upsert: true + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 4 + modifiedCount: 2 + upsertedCount: 1 + upsertedIds: { '3': 3 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite with mixed ordered operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + updateMany: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + - + insertOne: + document: { _id: 4, x: 44 } + - + deleteMany: + filter: { x: { $nin: [ 24, 34 ] } } + - + replaceOne: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + upsert: true + ordered: true + expectResult: + deletedCount: 2 + insertedCount: 2 + insertedIds: + $$unsetOrMatches: + '0': 3 + '3': 4 + matchedCount: 3 + modifiedCount: 3 + upsertedCount: 1 + upsertedIds: { '5': 4 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 24 } + - { _id: 3, x: 34 } + - { _id: 4, x: 44 } + - + description: 'BulkWrite with mixed unordered operations' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + # We omit inserting multiple documents and updating documents that may + # not exist at the start of this test as we cannot assume the order in + # which the operations will execute. + requests: + - + replaceOne: + filter: { _id: 3 } + replacement: { _id: 3, x: 33 } + upsert: true + - + deleteOne: + filter: { _id: 1 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: false + expectResult: + deletedCount: 1 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 1 + upsertedIds: { '0': 3 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + insertOne: + document: { _id: 3, x: 33 } + - + insertOne: + document: { _id: 4, x: 44 } + ordered: false + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 2 + # Since the map of insertedIds is generated before execution it + # could indicate inserts that did not actually succeed. We omit this + # field rather than expect drivers to provide an accurate map + # filtered by write errors. + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'BulkWrite continue-on-error behavior with unordered (duplicate key in requests)' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 3, x: 33 } + - + insertOne: + document: { _id: 3, x: 33 } + - + insertOne: + document: { _id: 4, x: 44 } + ordered: false + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 2 + # Since the map of insertedIds is generated before execution it + # could indicate inserts that did not actually succeed. We omit this + # field rather than expect drivers to provide an accurate map + # filtered by write errors. + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } diff --git a/src/test/spec/json/crud/unified/bypassDocumentValidation.json b/src/test/spec/json/crud/unified/bypassDocumentValidation.json new file mode 100644 index 000000000..aff2d37f8 --- /dev/null +++ b/src/test/spec/json/crud/unified/bypassDocumentValidation.json @@ -0,0 +1,493 @@ +{ + "description": "bypassDocumentValidation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.2", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with $out passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "bypassDocumentValidation": false + }, + "commandName": "aggregate", + "databaseName": "crud" + } + } + ] + } + ] + }, + { + "description": "BulkWrite passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndReplace passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "InsertOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4, + "x": 44 + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "ReplaceOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 32 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateMany passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + }, + { + "description": "UpdateOne passes bypassDocumentValidation: false", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "bypassDocumentValidation": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "bypassDocumentValidation": false + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/bypassDocumentValidation.yml b/src/test/spec/json/crud/unified/bypassDocumentValidation.yml new file mode 100644 index 000000000..508256475 --- /dev/null +++ b/src/test/spec/json/crud/unified/bypassDocumentValidation.yml @@ -0,0 +1,222 @@ +description: bypassDocumentValidation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.2' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'Aggregate with $out passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: aggregate + arguments: + pipeline: &pipeline + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $out: other_test_collection } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection_name + pipeline: *pipeline + bypassDocumentValidation: false + commandName: aggregate + databaseName: *database_name + - + description: 'BulkWrite passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: &inserted_document { _id: 4, x: 44 } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'FindOneAndReplace passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: &filter { _id: { $gt: 1 } } + replacement: &replacement { x: 32 } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + findAndModify: *collection_name + query: *filter + update: *replacement + bypassDocumentValidation: false + - + description: 'FindOneAndUpdate passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: *filter + update: &update { $inc: { x: 1 } } + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + findAndModify: *collection_name + query: *filter + update: *update + bypassDocumentValidation: false + - + description: 'InsertMany passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: insertMany + arguments: + documents: + - *inserted_document + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'InsertOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: insertOne + arguments: + document: *inserted_document + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - *inserted_document + bypassDocumentValidation: false + - + description: 'ReplaceOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: *filter + replacement: *replacement + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *replacement + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false + - + description: 'UpdateMany passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: *filter + update: *update + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *update + multi: true + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false + - + description: 'UpdateOne passes bypassDocumentValidation: false' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: *filter + update: *update + bypassDocumentValidation: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - + q: *filter + u: *update + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + bypassDocumentValidation: false diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.json b/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.json new file mode 100644 index 000000000..d9987897d --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.json @@ -0,0 +1,268 @@ +{ + "description": "client bulkWrite delete options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "collation": { + "locale": "simple" + }, + "hint": "_id_" + }, + "tests": [ + { + "description": "client bulk write delete with collation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "collation": { + "locale": "simple" + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "collation": { + "locale": "simple" + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 3, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "1": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "delete": 0, + "filter": { + "_id": 1 + }, + "collation": { + "locale": "simple" + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "collation": { + "locale": "simple" + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [] + } + ] + }, + { + "description": "client bulk write delete with hint", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 3, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "1": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "delete": 0, + "filter": { + "_id": 1 + }, + "hint": "_id_", + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_", + "multi": true + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.yml b/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.yml new file mode 100644 index 000000000..929783853 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-delete-options.yml @@ -0,0 +1,137 @@ +description: "client bulkWrite delete options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + collation: &collation { "locale": "simple" } + hint: &hint _id_ + +tests: + - description: "client bulk write delete with collation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + collation: *collation + - deleteMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + collation: *collation + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 3 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 1: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - delete: 0 + filter: { _id: 1 } + collation: *collation + multi: false + - delete: 0 + filter: { _id: { $gt: 1 } } + collation: *collation + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + - description: "client bulk write delete with hint" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + hint: *hint + - deleteMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + hint: *hint + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 3 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 1: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - delete: 0 + filter: { _id: 1 } + hint: *hint + multi: false + - delete: 0 + filter: { _id: { $gt: 1 } } + hint: *hint + multi: true + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.json b/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.json new file mode 100644 index 000000000..b828aad3b --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.json @@ -0,0 +1,69 @@ +{ + "description": "client bulkWrite errorResponse", + "schemaVersion": "1.12", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite operations support errorResponse assertions", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 8 + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.yml b/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.yml new file mode 100644 index 000000000..d63010afc --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-errorResponse.yml @@ -0,0 +1,38 @@ +description: "client bulkWrite errorResponse" +schemaVersion: "1.12" +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false # Avoid setting fail points with multiple mongoses + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite operations support errorResponse assertions" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ bulkWrite ] + errorCode: &errorCode 8 # UnknownError + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-errors.json b/src/test/spec/json/crud/unified/client-bulkWrite-errors.json new file mode 100644 index 000000000..015bd95c9 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-errors.json @@ -0,0 +1,513 @@ +{ + "description": "client bulkWrite errors", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "uriOptions": { + "retryWrites": false + }, + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "writeConcernErrorCode": 91, + "writeConcernErrorMessage": "Replication is being shut down", + "undefinedVarCode": 17276 + }, + "tests": [ + { + "description": "an individual operation fails during an ordered bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 1, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + } + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "an individual operation fails during an unordered bulkWrite", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true, + "ordered": false + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 2, + "insertResults": {}, + "updateResults": {}, + "deleteResults": { + "0": { + "deletedCount": 1 + }, + "2": { + "deletedCount": 1 + } + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "detailed results are omitted from error when verboseResults is false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 1, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + }, + "writeErrors": { + "1": { + "code": 17276 + } + } + } + } + ] + }, + { + "description": "a top-level failure occurs during a bulkWrite", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 8 + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "x": 1 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "errorCode": 8 + } + } + ] + }, + { + "description": "a bulk write with only errors does not report a partial result", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": {} + }, + "writeErrors": { + "0": { + "code": 17276 + } + } + } + } + ] + }, + { + "description": "a write concern error occurs during a bulkWrite", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 10 + } + }, + "updateResults": {}, + "deleteResults": {} + }, + "writeConcernErrors": [ + { + "code": 91, + "message": "Replication is being shut down" + } + ] + } + } + ] + }, + { + "description": "an empty list of write models is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [], + "verboseResults": true + }, + "expectError": { + "isClientError": true + } + } + ] + }, + { + "description": "Requesting unacknowledged write with verboseResults is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "verboseResults": true, + "ordered": false, + "writeConcern": { + "w": 0 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot request unacknowledged write concern and verbose results" + } + } + ] + }, + { + "description": "Requesting unacknowledged write with ordered is a client-side error", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 10 + } + } + } + ], + "writeConcern": { + "w": 0 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot request unacknowledged write concern and ordered writes" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-errors.yml b/src/test/spec/json/crud/unified/client-bulkWrite-errors.yml new file mode 100644 index 000000000..79c049616 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-errors.yml @@ -0,0 +1,270 @@ +description: "client bulkWrite errors" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + uriOptions: + retryWrites: false + useMultipleMongoses: false # Target a single mongos with failpoint + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + writeConcernErrorCode: &writeConcernErrorCode 91 + writeConcernErrorMessage: &writeConcernErrorMessage "Replication is being shut down" + undefinedVarCode: &undefinedVarCode 17276 # Use of an undefined variable + +tests: + - description: "an individual operation fails during an ordered bulkWrite" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 1 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + writeErrors: + 1: + code: *undefinedVarCode + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "an individual operation fails during an unordered bulkWrite" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + ordered: false + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 2 + insertResults: {} + updateResults: {} + deleteResults: + 0: + deletedCount: 1 + 2: + deletedCount: 1 + writeErrors: + 1: + code: *undefinedVarCode + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 2, x: 22 } + - description: "detailed results are omitted from error when verboseResults is false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: { _id: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: false + expectError: + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 1 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + writeErrors: + 1: + code: *undefinedVarCode + - description: "a top-level failure occurs during a bulkWrite" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - bulkWrite + errorCode: 8 # UnknownError + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + verboseResults: true + expectError: + errorCode: 8 + - description: "a bulk write with only errors does not report a partial result" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] # Attempt to access a nonexistent let var + verboseResults: true + expectError: + expectResult: + $$unsetOrMatches: {} # Empty or nonexistent result when no successful writes occurred + writeErrors: + 0: + code: *undefinedVarCode + - description: "a write concern error occurs during a bulkWrite" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - bulkWrite + writeConcernError: + code: *writeConcernErrorCode + errmsg: *writeConcernErrorMessage + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 10 + updateResults: {} + deleteResults: {} + writeConcernErrors: + - code: *writeConcernErrorCode + message: *writeConcernErrorMessage + - description: "an empty list of write models is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: [] + verboseResults: true + expectError: + isClientError: true + - description: "Requesting unacknowledged write with verboseResults is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + verboseResults: true + ordered: false + writeConcern: { w: 0 } + expectError: + isClientError: true + errorContains: "Cannot request unacknowledged write concern and verbose results" + - description: "Requesting unacknowledged write with ordered is a client-side error" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 10 } + # Omit `ordered` option. Defaults to true. + writeConcern: { w: 0 } + expectError: + isClientError: true + errorContains: "Cannot request unacknowledged write concern and ordered writes" diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.json b/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.json new file mode 100644 index 000000000..55f061892 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.json @@ -0,0 +1,315 @@ +{ + "description": "client bulkWrite with mixed namespaces", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "db0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "coll1" + } + }, + { + "database": { + "id": "database1", + "client": "client0", + "databaseName": "db1" + } + }, + { + "collection": { + "id": "collection2", + "database": "database1", + "collectionName": "coll2" + } + } + ], + "initialData": [ + { + "databaseName": "db0", + "collectionName": "coll0", + "documents": [] + }, + { + "databaseName": "db0", + "collectionName": "coll1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + }, + { + "databaseName": "db1", + "collectionName": "coll2", + "documents": [ + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "_yamlAnchors": { + "db0Coll0Namespace": "db0.coll0", + "db0Coll1Namespace": "db0.coll1", + "db1Coll2Namespace": "db1.coll2" + }, + "tests": [ + { + "description": "client bulkWrite with mixed namespaces", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "db0.coll0", + "document": { + "_id": 1 + } + } + }, + { + "insertOne": { + "namespace": "db0.coll0", + "document": { + "_id": 2 + } + } + }, + { + "updateOne": { + "namespace": "db0.coll1", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "namespace": "db1.coll2", + "filter": { + "_id": 3 + } + } + }, + { + "deleteOne": { + "namespace": "db0.coll1", + "filter": { + "_id": 2 + } + } + }, + { + "replaceOne": { + "namespace": "db1.coll2", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 45 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 2, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 2, + "insertResults": { + "0": { + "insertedId": 1 + }, + "1": { + "insertedId": 2 + } + }, + "updateResults": { + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "5": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + }, + "4": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "bulkWrite": 1, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1 + } + }, + { + "insert": 0, + "document": { + "_id": 2 + } + }, + { + "update": 1, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "delete": 2, + "filter": { + "_id": 3 + }, + "multi": false + }, + { + "delete": 1, + "filter": { + "_id": 2 + }, + "multi": false + }, + { + "update": 2, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 45 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "db0.coll0" + }, + { + "ns": "db0.coll1" + }, + { + "ns": "db1.coll2" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "db0", + "collectionName": "coll0", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + }, + { + "databaseName": "db0", + "collectionName": "coll1", + "documents": [ + { + "_id": 1, + "x": 12 + } + ] + }, + { + "databaseName": "db1", + "collectionName": "coll2", + "documents": [ + { + "_id": 4, + "x": 45 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.yml b/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.yml new file mode 100644 index 000000000..9788bce8c --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-mixed-namespaces.yml @@ -0,0 +1,147 @@ +description: "client bulkWrite with mixed namespaces" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name db0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - collection: + id: &collection1 collection1 + database: *database0 + collectionName: &collection1Name coll1 + - database: + id: &database1 database1 + client: *client0 + databaseName: &database1Name db1 + - collection: + id: &collection2 collection2 + database: *database1 + collectionName: &collection2Name coll2 + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: [] + - databaseName: *database0Name + collectionName: *collection1Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - databaseName: *database1Name + collectionName: *collection2Name + documents: + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + +_yamlAnchors: + db0Coll0Namespace: &db0Coll0Namespace "db0.coll0" + db0Coll1Namespace: &db0Coll1Namespace "db0.coll1" + db1Coll2Namespace: &db1Coll2Namespace "db1.coll2" + +tests: + - description: "client bulkWrite with mixed namespaces" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *db0Coll0Namespace + document: { _id: 1 } + - insertOne: + namespace: *db0Coll0Namespace + document: { _id: 2 } + - updateOne: + namespace: *db0Coll1Namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - deleteOne: + namespace: *db1Coll2Namespace + filter: { _id: 3 } + - deleteOne: + namespace: *db0Coll1Namespace + filter: { _id: 2 } + - replaceOne: + namespace: *db1Coll2Namespace + filter: { _id: 4 } + replacement: { x: 45 } + verboseResults: true + expectResult: + insertedCount: 2 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 2 + insertResults: + 0: + insertedId: 1 + 1: + insertedId: 2 + updateResults: + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 5: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + 4: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + bulkWrite: 1 + ops: + - insert: 0 + document: { _id: 1 } + - insert: 0 + document: { _id: 2 } + - update: 1 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - delete: 2 + filter: { _id: 3 } + multi: false + - delete: 1 + filter: { _id: 2 } + multi: false + - update: 2 + filter: { _id: 4 } + updateMods: { x: 45 } + multi: false + nsInfo: + - ns: *db0Coll0Namespace + - ns: *db0Coll1Namespace + - ns: *db1Coll2Namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1 } + - { _id: 2 } + - databaseName: *database0Name + collectionName: *collection1Name + documents: + - { _id: 1, x: 12 } + - databaseName: *database1Name + collectionName: *collection2Name + documents: + - { _id: 4, x: 45 } diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-options.json b/src/test/spec/json/crud/unified/client-bulkWrite-options.json new file mode 100644 index 000000000..708fe4e85 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-options.json @@ -0,0 +1,716 @@ +{ + "description": "client bulkWrite top-level options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "client": { + "id": "writeConcernClient", + "uriOptions": { + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "comment": { + "bulk": "write" + }, + "let": { + "id1": 1, + "id2": 2 + }, + "writeConcern": { + "w": "majority" + } + }, + "tests": [ + { + "description": "client bulkWrite comment", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "comment": { + "bulk": "write" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "comment": { + "bulk": "write" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite bypassDocumentValidation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "bypassDocumentValidation": true, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "bypassDocumentValidation": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite let", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id1" + ] + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + } + } + } + ], + "let": { + "id1": 1, + "id2": 2 + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 1, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "1": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "let": { + "id1": 1, + "id2": 2 + }, + "ops": [ + { + "update": 0, + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id1" + ] + } + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$expr": { + "$eq": [ + "$_id", + "$$id2" + ] + } + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 12 + } + ] + } + ] + }, + { + "description": "client bulkWrite bypassDocumentValidation: false is sent", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "bypassDocumentValidation": false, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "bypassDocumentValidation": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite writeConcern", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "writeConcern": { + "w": "majority" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": "majority" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite inherits writeConcern from client", + "operations": [ + { + "object": "writeConcernClient", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "writeConcernClient", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": 1 + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite writeConcern option overrides client writeConcern", + "operations": [ + { + "object": "writeConcernClient", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "writeConcern": { + "w": "majority" + }, + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 3 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "writeConcernClient", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "writeConcern": { + "w": "majority" + }, + "ops": [ + { + "insert": 0, + "document": { + "_id": 3, + "x": 33 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-options.yml b/src/test/spec/json/crud/unified/client-bulkWrite-options.yml new file mode 100644 index 000000000..e0cbe747b --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-options.yml @@ -0,0 +1,351 @@ +description: "client bulkWrite top-level options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - client: + id: &writeConcernClient writeConcernClient + uriOptions: + &clientWriteConcern { w: 1 } + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + comment: &comment { bulk: "write" } + let: &let { id1: 1, id2: 2 } + writeConcern: &majorityWriteConcern { w: "majority" } + +tests: + - description: "client bulkWrite comment" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + comment: *comment + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + comment: *comment + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite bypassDocumentValidation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + bypassDocumentValidation: true + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + bypassDocumentValidation: true + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite let" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id1" ] + update: + $inc: { x: 1 } + - deleteOne: + namespace: *namespace + filter: + $expr: + $eq: [ "$_id", "$$id2" ] + let: *let + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 1 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 1: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + let: *let + ops: + - update: 0 + filter: + $expr: + $eq: [ "$_id", "$$id1" ] + updateMods: { $inc: { x: 1 } } + multi: false + - delete: 0 + filter: + $expr: + $eq: [ "$_id", "$$id2" ] + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, x: 12 } + - description: "client bulkWrite bypassDocumentValidation: false is sent" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + bypassDocumentValidation: false + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + bypassDocumentValidation: false + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - description: "client bulkWrite writeConcern" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + writeConcern: *majorityWriteConcern + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: *majorityWriteConcern + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + - description: "client bulkWrite inherits writeConcern from client" + operations: + - object: *writeConcernClient + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *writeConcernClient + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: { w: 1 } + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace + - description: "client bulkWrite writeConcern option overrides client writeConcern" + operations: + - object: *writeConcernClient + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 3, x: 33 } + writeConcern: *majorityWriteConcern + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 3 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *writeConcernClient + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + writeConcern: *majorityWriteConcern + ops: + - insert: 0 + document: { _id: 3, x: 33 } + nsInfo: + - ns: *namespace diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-ordered.json b/src/test/spec/json/crud/unified/client-bulkWrite-ordered.json new file mode 100644 index 000000000..6fb10d992 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-ordered.json @@ -0,0 +1,291 @@ +{ + "description": "client bulkWrite with ordered option", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with ordered: false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true, + "ordered": false + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": false, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "client bulkWrite with ordered: true", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true, + "ordered": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "client bulkWrite defaults to ordered: true", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 1 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 1, + "x": 11 + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-ordered.yml b/src/test/spec/json/crud/unified/client-bulkWrite-ordered.yml new file mode 100644 index 000000000..48aa8ad40 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-ordered.yml @@ -0,0 +1,153 @@ +description: "client bulkWrite with ordered option" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite with ordered: false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + ordered: false + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: false + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - description: "client bulkWrite with ordered: true" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + ordered: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - description: "client bulkWrite defaults to ordered: true" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 1, x: 11 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 1 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 1, x: 11 } + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.json b/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.json new file mode 100644 index 000000000..1b75e3783 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.json @@ -0,0 +1,540 @@ +{ + "description": "client bulkWrite partial results", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "newDocument": { + "_id": 2, + "x": 22 + } + }, + "tests": [ + { + "description": "partialResult is unset when first operation fails during an ordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is unset when first operation fails during an ordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an ordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": true, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an ordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": true, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + }, + { + "description": "partialResult is unset when all operations fail during an unordered bulk write", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false + }, + "expectError": { + "expectResult": { + "$$unsetOrMatches": { + "insertedCount": { + "$$exists": false + }, + "upsertedCount": { + "$$exists": false + }, + "matchedCount": { + "$$exists": false + }, + "modifiedCount": { + "$$exists": false + }, + "deletedCount": { + "$$exists": false + }, + "insertResults": { + "$$exists": false + }, + "updateResults": { + "$$exists": false + }, + "deleteResults": { + "$$exists": false + } + } + } + } + } + ] + }, + { + "description": "partialResult is set when first operation fails during an unordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": false, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "1": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when first operation fails during an unordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": false, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an unordered bulk write (verbose)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false, + "verboseResults": true + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 2 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + } + ] + }, + { + "description": "partialResult is set when second operation fails during an unordered bulk write (summary)", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "ordered": false, + "verboseResults": false + }, + "expectError": { + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.yml b/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.yml new file mode 100644 index 000000000..1cda7318f --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-partialResults.yml @@ -0,0 +1,262 @@ +description: "client bulkWrite partial results" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - &existingDocument { _id: 1, x: 11 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + newDocument: &newDocument { _id: 2, x: 22 } + +tests: + - description: "partialResult is unset when first operation fails during an ordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: true + verboseResults: true + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is unset when first operation fails during an ordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: true + verboseResults: false + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is set when second operation fails during an ordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: true + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when second operation fails during an ordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: true + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - description: "partialResult is unset when all operations fail during an unordered bulk write" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + expectError: + expectResult: + $$unsetOrMatches: + insertedCount: { $$exists: false } + upsertedCount: { $$exists: false } + matchedCount: { $$exists: false } + modifiedCount: { $$exists: false } + deletedCount: { $$exists: false } + insertResults: { $$exists: false } + updateResults: { $$exists: false } + deleteResults: { $$exists: false } + - description: "partialResult is set when first operation fails during an unordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: false + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 1: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when first operation fails during an unordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *existingDocument + - insertOne: + namespace: *namespace + document: *newDocument + ordered: false + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - description: "partialResult is set when second operation fails during an unordered bulk write (verbose)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + verboseResults: true + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 2 + updateResults: {} + deleteResults: {} + - description: "partialResult is set when second operation fails during an unordered bulk write (summary)" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: *newDocument + - insertOne: + namespace: *namespace + document: *existingDocument + ordered: false + verboseResults: false + expectError: + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.json b/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.json new file mode 100644 index 000000000..b86bc5f94 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.json @@ -0,0 +1,163 @@ +{ + "description": "client bulkWrite updateOne-sort", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite replaceOne with sort option", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "ops": [ + { + "update": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "updateMods": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "nErrors": 0, + "nMatched": 1, + "nModified": 1 + }, + "commandName": "bulkWrite" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.yml b/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.yml new file mode 100644 index 000000000..a159ba595 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-replaceOne-sort.yml @@ -0,0 +1,77 @@ +description: client bulkWrite updateOne-sort + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid # Serverless does not support bulkWrite: CLOUDP-256344. + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: client bulkWrite replaceOne with sort option + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - replaceOne: + namespace: *namespace + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + ops: + - update: 0 + filter: { _id: { $gt: 1 } } + updateMods: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + nsInfo: + - ns: *namespace + - commandSucceededEvent: + reply: + ok: 1 + nErrors: 0 + nMatched: 1 + nModified: 1 + commandName: bulkWrite + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 1 } diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-results.json b/src/test/spec/json/crud/unified/client-bulkWrite-results.json new file mode 100644 index 000000000..accf5a9cb --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-results.json @@ -0,0 +1,833 @@ +{ + "description": "client bulkWrite results", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with verboseResults: true returns detailed results", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "0": { + "insertedId": 8 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "3": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 4 + } + }, + "deleteResults": { + "4": { + "deletedCount": 1 + }, + "5": { + "deletedCount": 2 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite with verboseResults: false omits detailed results", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": false + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite defaults to verboseResults: false", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ] + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-results.yml b/src/test/spec/json/crud/unified/client-bulkWrite-results.yml new file mode 100644 index 000000000..86cb5346a --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-results.yml @@ -0,0 +1,312 @@ +description: "client bulkWrite results" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite with verboseResults: true returns detailed results" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + 0: + insertedId: 8 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 3: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 4 + deleteResults: + 4: + deletedCount: 1 + 5: + deletedCount: 2 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: "client bulkWrite with verboseResults: false omits detailed results" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: false + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: "client bulkWrite defaults to verboseResults: false" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-options.json b/src/test/spec/json/crud/unified/client-bulkWrite-update-options.json new file mode 100644 index 000000000..ce6241c68 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-options.json @@ -0,0 +1,949 @@ +{ + "description": "client bulkWrite update options", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0", + "collation": { + "locale": "simple" + }, + "hint": "_id_" + }, + "tests": [ + { + "description": "client bulkWrite update with arrayFilters", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array.$[i]": 4 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ] + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array.$[i]": 5 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array.$[i]": 4 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ], + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array.$[i]": 5 + } + }, + "arrayFilters": [ + { + "i": { + "$gte": 2 + } + } + ], + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 4, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 5, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 5, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with collation", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "collation": { + "locale": "simple" + } + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "collation": { + "locale": "simple" + } + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "collation": { + "locale": "simple" + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "collation": { + "locale": "simple" + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "collation": { + "locale": "simple" + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "collation": { + "locale": "simple" + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with hint", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "hint": "_id_" + } + }, + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "hint": "_id_" + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "hint": "_id_" + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 4, + "modifiedCount": 4, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "1": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "hint": "_id_", + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 5 + ] + } + }, + "hint": "_id_", + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "hint": "_id_", + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 5 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + }, + { + "description": "client bulkWrite update with upsert", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 5 + }, + "update": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "upsert": true + } + }, + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 6 + }, + "replacement": { + "array": [ + 1, + 2, + 6 + ] + }, + "upsert": true + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 5 + }, + "1": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 6 + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 5 + }, + "updateMods": { + "$set": { + "array": [ + 1, + 2, + 4 + ] + } + }, + "upsert": true, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 6 + }, + "updateMods": { + "array": [ + 1, + 2, + 6 + ] + }, + "upsert": true, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 2, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 3, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 4, + "array": [ + 1, + 2, + 3 + ] + }, + { + "_id": 5, + "array": [ + 1, + 2, + 4 + ] + }, + { + "_id": 6, + "array": [ + 1, + 2, + 6 + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-options.yml b/src/test/spec/json/crud/unified/client-bulkWrite-update-options.yml new file mode 100644 index 000000000..c5cc20d48 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-options.yml @@ -0,0 +1,338 @@ +description: "client bulkWrite update options" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, array: [ 1, 2, 3 ] } + - { _id: 2, array: [ 1, 2, 3 ] } + - { _id: 3, array: [ 1, 2, 3 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + collation: &collation { "locale": "simple" } + hint: &hint _id_ + +tests: + - description: "client bulkWrite update with arrayFilters" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $set: + array.$[i]: 4 + arrayFilters: [ i: { $gte: 2 } ] + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: + $set: + array.$[i]: 5 + arrayFilters: [ i: { $gte: 2 } ] + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $set: + array.$[i]: 4 + arrayFilters: [ i: { $gte: 2 } ] + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: + $set: + array.$[i]: 5 + arrayFilters: [ i: { $gte: 2 } ] + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 4, 4 ] } + - { _id: 2, array: [ 1, 5, 5 ] } + - { _id: 3, array: [ 1, 5, 5 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + - description: "client bulkWrite update with collation" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $set: { array: [ 1, 2, 4 ] } } + collation: *collation + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $set: { array: [ 1, 2, 5 ] } } + collation: *collation + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { array: [ 1, 2, 6 ] } + collation: *collation + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + collation: *collation + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $set: { array: [ 1, 2, 5 ] } } + collation: *collation + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { array: [ 1, 2, 6 ] } + collation: *collation + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 4 ] } + - { _id: 2, array: [ 1, 2, 5 ] } + - { _id: 3, array: [ 1, 2, 5 ] } + - { _id: 4, array: [ 1, 2, 6 ] } + - description: "client bulkWrite update with hint" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $set: { array: [ 1, 2, 4 ] } } + hint: *hint + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $set: { array: [ 1, 2, 5 ] } } + hint: *hint + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { array: [ 1, 2, 6 ] } + hint: *hint + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 4 + modifiedCount: 4 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 1: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + hint: *hint + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $set: { array: [ 1, 2, 5 ] } } + hint: *hint + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { array: [ 1, 2, 6 ] } + hint: *hint + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 4 ] } + - { _id: 2, array: [ 1, 2, 5 ] } + - { _id: 3, array: [ 1, 2, 5 ] } + - { _id: 4, array: [ 1, 2, 6 ] } + - description: "client bulkWrite update with upsert" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 5 } + update: { $set: { array: [ 1, 2, 4 ] } } + upsert: true + - replaceOne: + namespace: *namespace + filter: { _id: 6 } + replacement: { array: [ 1, 2, 6 ] } + upsert: true + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 2 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 5 + 1: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 6 + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 5 } + updateMods: { $set: { array: [ 1, 2, 4 ] } } + upsert: true + multi: false + - update: 0 + filter: { _id: 6 } + updateMods: { array: [ 1, 2, 6 ] } + upsert: true + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, array: [ 1, 2, 3 ] } + - { _id: 2, array: [ 1, 2, 3 ] } + - { _id: 3, array: [ 1, 2, 3 ] } + - { _id: 4, array: [ 1, 2, 3 ] } + - { _id: 5, array: [ 1, 2, 4 ] } + - { _id: 6, array: [ 1, 2, 6 ] } diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.json b/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.json new file mode 100644 index 000000000..9dba5ee6c --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.json @@ -0,0 +1,258 @@ +{ + "description": "client bulkWrite update pipeline", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 2 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite updateOne with pipeline", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": [ + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 1, + "modifiedCount": 1, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": [ + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": false + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateMany with pipeline", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": {}, + "update": [ + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 0, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 0, + "insertResults": {}, + "updateResults": { + "0": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": {}, + "updateMods": [ + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "databaseName": "crud-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.yml b/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.yml new file mode 100644 index 000000000..c90e93b47 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-pipeline.yml @@ -0,0 +1,133 @@ +description: "client bulkWrite update pipeline" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - {_id: 1, x: 1} + - {_id: 2, x: 2} + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite updateOne with pipeline" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + - $addFields: + foo: 1 + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 1 + modifiedCount: 1 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { "$$exists": false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + - $addFields: + foo: 1 + multi: false + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - {_id: 1, x: 1, foo: 1} + - {_id: 2, x: 2 } + + - description: "client bulkWrite updateMany with pipeline" + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: {} + update: + - $addFields: + foo: 1 + verboseResults: true + expectResult: + insertedCount: 0 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 0 + insertResults: {} + updateResults: + 0: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { "$$exists": false } + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - update: 0 + filter: { } + updateMods: + - $addFields: + foo: 1 + multi: true + nsInfo: + - ns: *namespace + outcome: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - {_id: 1, x: 1, foo: 1} + - {_id: 2, x: 2, foo: 1} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.json b/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.json new file mode 100644 index 000000000..617e71133 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.json @@ -0,0 +1,216 @@ +{ + "description": "client-bulkWrite-update-validation", + "schemaVersion": "1.1", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite replaceOne prohibits atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "replaceOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "replacement": { + "$set": { + "x": 22 + } + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateOne requires atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "x": 22 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "client bulkWrite updateMany requires atomic modifiers", + "operations": [ + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "x": 44 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.yml b/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.yml new file mode 100644 index 000000000..478554c32 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-update-validation.yml @@ -0,0 +1,79 @@ +description: "client-bulkWrite-update-validation" + +schemaVersion: "1.1" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: "client bulkWrite replaceOne prohibits atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - replaceOne: + namespace: *namespace + filter: { _id: 1 } + replacement: { $set: { x: 22 } } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData + + - description: "client bulkWrite updateOne requires atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { x: 22 } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData + + - description: "client bulkWrite updateMany requires atomic modifiers" + operations: + - name: clientBulkWrite + object: *client0 + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: { $gt: 1 } } + update: { x: 44 } + expectError: + isClientError: true + expectEvents: + - client: *client0 + events: [] + outcome: *initialData diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.json b/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.json new file mode 100644 index 000000000..ef75dcb37 --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.json @@ -0,0 +1,167 @@ +{ + "description": "client bulkWrite updateOne-sort", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "crud-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite updateOne with sort option", + "operations": [ + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateOne": { + "namespace": "crud-tests.coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "ops": [ + { + "update": 0, + "filter": { + "_id": { + "$gt": 1 + } + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "nsInfo": [ + { + "ns": "crud-tests.coll0" + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "nErrors": 0, + "nMatched": 1, + "nModified": 1 + }, + "commandName": "bulkWrite" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.yml b/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.yml new file mode 100644 index 000000000..73a265d6b --- /dev/null +++ b/src/test/spec/json/crud/unified/client-bulkWrite-updateOne-sort.yml @@ -0,0 +1,77 @@ +description: client bulkWrite updateOne-sort + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "8.0" + serverless: forbid # Serverless does not support bulkWrite: CLOUDP-256344. + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "crud-tests.coll0" + +tests: + - description: client bulkWrite updateOne with sort option + operations: + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateOne: + namespace: *namespace + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + ops: + - update: 0 + filter: { _id: { $gt: 1 } } + updateMods: { $inc: { x: 1 } } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + nsInfo: + - ns: *namespace + - commandSucceededEvent: + reply: + ok: 1 + nErrors: 0 + nMatched: 1 + nModified: 1 + commandName: bulkWrite + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 34 } diff --git a/src/test/spec/json/crud/unified/count-collation.json b/src/test/spec/json/crud/unified/count-collation.json new file mode 100644 index 000000000..eef65e088 --- /dev/null +++ b/src/test/spec/json/crud/unified/count-collation.json @@ -0,0 +1,83 @@ +{ + "description": "count-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "Count documents with collation", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "x": "ping" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "Deprecated count with collation", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": { + "x": "ping" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": 1 + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/count-collation.yml b/src/test/spec/json/crud/unified/count-collation.yml new file mode 100644 index 000000000..692a3c698 --- /dev/null +++ b/src/test/spec/json/crud/unified/count-collation.yml @@ -0,0 +1,57 @@ +description: count-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: ping } + +tests: + - + description: 'Count documents with collation' + operations: + - + object: *collection0 + name: countDocuments + arguments: + filter: { x: ping } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: 1 + - + description: 'Deprecated count with collation' + operations: + - + object: *collection0 + name: count + arguments: + filter: { x: ping } + collation: + locale: en_US + strength: 2 + expectResult: 1 diff --git a/src/test/spec/json/crud/unified/count-empty.json b/src/test/spec/json/crud/unified/count-empty.json new file mode 100644 index 000000000..29d8d76f6 --- /dev/null +++ b/src/test/spec/json/crud/unified/count-empty.json @@ -0,0 +1,71 @@ +{ + "description": "count-empty", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [] + } + ], + "tests": [ + { + "description": "Estimated document count with empty collection", + "operations": [ + { + "object": "collection0", + "name": "estimatedDocumentCount", + "arguments": {}, + "expectResult": 0 + } + ] + }, + { + "description": "Count documents with empty collection", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 0 + } + ] + }, + { + "description": "Deprecated count with empty collection", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 0 + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/count-empty.yml b/src/test/spec/json/crud/unified/count-empty.yml new file mode 100644 index 000000000..a5d5ae35b --- /dev/null +++ b/src/test/spec/json/crud/unified/count-empty.yml @@ -0,0 +1,52 @@ +description: count-empty + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'Estimated document count with empty collection' + operations: + - + object: *collection0 + name: estimatedDocumentCount + arguments: { } + expectResult: 0 + - + description: 'Count documents with empty collection' + operations: + - + object: *collection0 + name: countDocuments + arguments: + filter: { } + expectResult: 0 + - + description: 'Deprecated count with empty collection' + operations: + - + object: *collection0 + name: count + arguments: + filter: { } + expectResult: 0 diff --git a/src/test/spec/json/crud/unified/count.json b/src/test/spec/json/crud/unified/count.json new file mode 100644 index 000000000..80fff5a30 --- /dev/null +++ b/src/test/spec/json/crud/unified/count.json @@ -0,0 +1,148 @@ +{ + "description": "count", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Estimated document count", + "operations": [ + { + "object": "collection0", + "name": "estimatedDocumentCount", + "arguments": {}, + "expectResult": 3 + } + ] + }, + { + "description": "Count documents without a filter", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 3 + } + ] + }, + { + "description": "Count documents with a filter", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": 2 + } + ] + }, + { + "description": "Count documents with skip and limit", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {}, + "skip": 1, + "limit": 3 + }, + "expectResult": 2 + } + ] + }, + { + "description": "Deprecated count without a filter", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 3 + } + ] + }, + { + "description": "Deprecated count with a filter", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": 2 + } + ] + }, + { + "description": "Deprecated count with skip and limit", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {}, + "skip": 1, + "limit": 3 + }, + "expectResult": 2 + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/count.yml b/src/test/spec/json/crud/unified/count.yml new file mode 100644 index 000000000..5f9fbb763 --- /dev/null +++ b/src/test/spec/json/crud/unified/count.yml @@ -0,0 +1,95 @@ +description: count + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'Estimated document count' + operations: + - + object: *collection0 + name: estimatedDocumentCount + arguments: { } + expectResult: 3 + - + description: 'Count documents without a filter' + operations: + - + object: *collection0 + name: countDocuments + arguments: + filter: { } + expectResult: 3 + - + description: 'Count documents with a filter' + operations: + - + object: *collection0 + name: countDocuments + arguments: + filter: { _id: { $gt: 1 } } + expectResult: 2 + - + description: 'Count documents with skip and limit' + operations: + - + object: *collection0 + name: countDocuments + arguments: + filter: { } + skip: 1 + limit: 3 + expectResult: 2 + - + description: 'Deprecated count without a filter' + operations: + - + object: *collection0 + name: count + arguments: + filter: { } + expectResult: 3 + - + description: 'Deprecated count with a filter' + operations: + - + object: *collection0 + name: count + arguments: + filter: { _id: { $gt: 1 } } + expectResult: 2 + - + description: 'Deprecated count with skip and limit' + operations: + - + object: *collection0 + name: count + arguments: + filter: { } + skip: 1 + limit: 3 + expectResult: 2 diff --git a/src/test/spec/json/crud/unified/create-null-ids.json b/src/test/spec/json/crud/unified/create-null-ids.json new file mode 100644 index 000000000..8e0c3ac5d --- /dev/null +++ b/src/test/spec/json/crud/unified/create-null-ids.json @@ -0,0 +1,253 @@ +{ + "description": "create-null-ids", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "crud_id" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "type_tests" + } + } + ], + "initialData": [ + { + "collectionName": "type_tests", + "databaseName": "crud_id", + "documents": [] + } + ], + "tests": [ + { + "description": "inserting _id with type null via insertOne", + "operations": [ + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": null + } + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via insertMany", + "operations": [ + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": null + } + ] + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via updateOne", + "operations": [ + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": null + }, + "update": { + "$unset": { + "a": "" + } + }, + "upsert": true + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via updateMany", + "operations": [ + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": null + }, + "update": { + "$unset": { + "a": "" + } + }, + "upsert": true + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via replaceOne", + "operations": [ + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "_id": null + }, + "upsert": true + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via bulkWrite", + "operations": [ + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": null + } + } + } + ] + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + }, + { + "description": "inserting _id with type null via clientBulkWrite", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "crud_id.type_tests", + "document": { + "_id": null + } + } + } + ] + } + }, + { + "name": "countDocuments", + "object": "collection", + "arguments": { + "filter": { + "_id": { + "$type": "null" + } + } + }, + "expectResult": 1 + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/create-null-ids.yml b/src/test/spec/json/crud/unified/create-null-ids.yml new file mode 100644 index 000000000..7511f0401 --- /dev/null +++ b/src/test/spec/json/crud/unified/create-null-ids.yml @@ -0,0 +1,97 @@ +description: create-null-ids +schemaVersion: '1.0' + +createEntities: + - client: + id: client + observeEvents: + - commandStartedEvent + - database: + id: &database database + client: client + databaseName: crud_id + - collection: + id: &collection collection + database: *database + collectionName: type_tests + +initialData: + - collectionName: type_tests + databaseName: crud_id + documents: [] + +tests: + + - description: inserting _id with type null via insertOne + operations: + - name: insertOne + object: *collection + arguments: {document: &null_id {_id: null}} + # We use countDocuments with a $type query to verify the insert of the correct BSON type + # this is to avoid client side type conversions (potentially common: undefined -> null) + - name: countDocuments + object: *collection + arguments: {filter: &null_id_filter {_id: {$type: 'null'}}} + expectResult: 1 + + - description: inserting _id with type null via insertMany + operations: + - name: insertMany + object: *collection + arguments: {documents: [*null_id]} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 + + - description: inserting _id with type null via updateOne + operations: + - name: updateOne + object: *collection + arguments: {filter: *null_id, update: {$unset: {a: ''}}, upsert: true} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 + + - description: inserting _id with type null via updateMany + operations: + - name: updateMany + object: *collection + arguments: {filter: *null_id, update: {$unset: {a: ''}}, upsert: true} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 + + - description: inserting _id with type null via replaceOne + operations: + - name: replaceOne + object: *collection + arguments: {filter: {}, replacement: *null_id, upsert: true} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 + + - description: inserting _id with type null via bulkWrite + operations: + - name: bulkWrite + object: *collection + arguments: {requests: [{insertOne: {document: *null_id}}]} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 + + - description: inserting _id with type null via clientBulkWrite + runOnRequirements: + - minServerVersion: '8.0' + operations: + - name: clientBulkWrite + object: client + arguments: {models: [{insertOne: {namespace: crud_id.type_tests, document: *null_id}}]} + - name: countDocuments + object: *collection + arguments: {filter: *null_id_filter} + expectResult: 1 diff --git a/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.json b/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.json index 2a81282de..b6460f001 100644 --- a/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.json +++ b/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.json @@ -52,13 +52,6 @@ } } ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [] - } - ], "tests": [ { "description": "Database-level aggregate with $out includes read preference for 5.0+ server", @@ -141,17 +134,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -235,17 +217,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -332,17 +303,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] }, { @@ -429,17 +389,6 @@ } ] } - ], - "outcome": [ - { - "collectionName": "coll0", - "databaseName": "db0", - "documents": [ - { - "_id": 1 - } - ] - } ] } ] diff --git a/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.yml b/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.yml index 04a3b2169..03fcd35aa 100644 --- a/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.yml +++ b/src/test/spec/json/crud/unified/db-aggregate-write-readPreference.yml @@ -43,11 +43,6 @@ createEntities: database: *database0 collectionName: &collection0Name coll0 -initialData: - - collectionName: *collection0Name - databaseName: *database0Name - documents: [] - tests: - description: "Database-level aggregate with $out includes read preference for 5.0+ server" runOnRequirements: @@ -73,11 +68,6 @@ tests: $readPreference: *readPreference readConcern: *readConcern writeConcern: *writeConcern - outcome: &outcome - - collectionName: *collection0Name - databaseName: *database0Name - documents: - - { _id: 1 } - description: "Database-level aggregate with $out omits read preference for pre-5.0 server" runOnRequirements: @@ -102,7 +92,6 @@ tests: $readPreference: { $$exists: false } readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome - description: "Database-level aggregate with $merge includes read preference for 5.0+ server" runOnRequirements: @@ -127,7 +116,6 @@ tests: $readPreference: *readPreference readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome - description: "Database-level aggregate with $merge omits read preference for pre-5.0 server" runOnRequirements: @@ -148,4 +136,3 @@ tests: $readPreference: { $$exists: false } readConcern: *readConcern writeConcern: *writeConcern - outcome: *outcome diff --git a/src/test/spec/json/crud/unified/db-aggregate.yml b/src/test/spec/json/crud/unified/db-aggregate.yml index 032f94c73..df82c32b0 100644 --- a/src/test/spec/json/crud/unified/db-aggregate.yml +++ b/src/test/spec/json/crud/unified/db-aggregate.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: db-aggregate schemaVersion: '1.4' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteMany-collation.json b/src/test/spec/json/crud/unified/deleteMany-collation.json new file mode 100644 index 000000000..23d2f037c --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteMany-collation.json @@ -0,0 +1,86 @@ +{ + "description": "deleteMany-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ], + "tests": [ + { + "description": "DeleteMany when many documents match with collation", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "deletedCount": 2 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/deleteMany-collation.yml b/src/test/spec/json/crud/unified/deleteMany-collation.yml new file mode 100644 index 000000000..b7caab67d --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteMany-collation.yml @@ -0,0 +1,54 @@ +description: deleteMany-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + +tests: + - + description: 'DeleteMany when many documents match with collation' + operations: + - + object: *collection0 + name: deleteMany + arguments: + filter: { x: PING } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + deletedCount: 2 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } diff --git a/src/test/spec/json/crud/unified/deleteMany-hint-clientError.yml b/src/test/spec/json/crud/unified/deleteMany-hint-clientError.yml index 21ff1debb..c04d7507e 100644 --- a/src/test/spec/json/crud/unified/deleteMany-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/deleteMany-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteMany-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteMany-hint-serverError.yml b/src/test/spec/json/crud/unified/deleteMany-hint-serverError.yml index 2e20988d0..24260e261 100644 --- a/src/test/spec/json/crud/unified/deleteMany-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/deleteMany-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteMany-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteMany-hint.yml b/src/test/spec/json/crud/unified/deleteMany-hint.yml index 512b95e76..ce5f0a058 100644 --- a/src/test/spec/json/crud/unified/deleteMany-hint.yml +++ b/src/test/spec/json/crud/unified/deleteMany-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteMany-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteMany.json b/src/test/spec/json/crud/unified/deleteMany.json new file mode 100644 index 000000000..36cdff8dc --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteMany.json @@ -0,0 +1,115 @@ +{ + "description": "deleteMany", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "DeleteMany when many documents match", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": { + "deletedCount": 2 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "DeleteMany when no document matches", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": 4 + } + }, + "expectResult": { + "deletedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/deleteMany.yml b/src/test/spec/json/crud/unified/deleteMany.yml new file mode 100644 index 000000000..832201ed5 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteMany.yml @@ -0,0 +1,63 @@ +description: deleteMany + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'DeleteMany when many documents match' + operations: + - + object: *collection0 + name: deleteMany + arguments: + filter: { _id: { $gt: 1 } } + expectResult: + deletedCount: 2 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'DeleteMany when no document matches' + operations: + - + object: *collection0 + name: deleteMany + arguments: + filter: { _id: 4 } + expectResult: + deletedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/deleteOne-collation.json b/src/test/spec/json/crud/unified/deleteOne-collation.json new file mode 100644 index 000000000..44bab6e12 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne-collation.json @@ -0,0 +1,90 @@ +{ + "description": "deleteOne-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ], + "tests": [ + { + "description": "DeleteOne when many documents matches with collation", + "operations": [ + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/deleteOne-collation.yml b/src/test/spec/json/crud/unified/deleteOne-collation.yml new file mode 100644 index 000000000..2ec971550 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne-collation.yml @@ -0,0 +1,55 @@ +description: deleteOne-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + +tests: + - + description: 'DeleteOne when many documents matches with collation' + operations: + - + object: *collection0 + name: deleteOne + arguments: + filter: { x: PING } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 3, x: pINg } diff --git a/src/test/spec/json/crud/unified/deleteOne-errorResponse.json b/src/test/spec/json/crud/unified/deleteOne-errorResponse.json new file mode 100644 index 000000000..1f3a266f1 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne-errorResponse.json @@ -0,0 +1,82 @@ +{ + "description": "deleteOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "delete operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/deleteOne-errorResponse.yml b/src/test/spec/json/crud/unified/deleteOne-errorResponse.yml new file mode 100644 index 000000000..dcf013060 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "deleteOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "delete operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: &errorCode 8 # UnknownError + - name: deleteOne + object: *collection0 + arguments: + filter: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/src/test/spec/json/crud/unified/deleteOne-hint-clientError.yml b/src/test/spec/json/crud/unified/deleteOne-hint-clientError.yml index be218fc9b..7ff6f8d96 100644 --- a/src/test/spec/json/crud/unified/deleteOne-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/deleteOne-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteOne-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteOne-hint-serverError.yml b/src/test/spec/json/crud/unified/deleteOne-hint-serverError.yml index 6c8c0ea81..191d96aea 100644 --- a/src/test/spec/json/crud/unified/deleteOne-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/deleteOne-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteOne-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteOne-hint.yml b/src/test/spec/json/crud/unified/deleteOne-hint.yml index f72356c99..ce23b8a98 100644 --- a/src/test/spec/json/crud/unified/deleteOne-hint.yml +++ b/src/test/spec/json/crud/unified/deleteOne-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: deleteOne-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/deleteOne.json b/src/test/spec/json/crud/unified/deleteOne.json new file mode 100644 index 000000000..8177b2fb6 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne.json @@ -0,0 +1,136 @@ +{ + "description": "deleteOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "DeleteOne when many documents match", + "operations": [ + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ] + }, + { + "description": "DeleteOne when one document matches", + "operations": [ + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "DeleteOne when no documents match", + "operations": [ + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 4 + } + }, + "expectResult": { + "deletedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/deleteOne.yml b/src/test/spec/json/crud/unified/deleteOne.yml new file mode 100644 index 000000000..6c7dc9519 --- /dev/null +++ b/src/test/spec/json/crud/unified/deleteOne.yml @@ -0,0 +1,76 @@ +description: deleteOne + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + # This test doesn't verify the output collection because we cannot assume + # which document gets deleted. + description: 'DeleteOne when many documents match' + operations: + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: { $gt: 1 } } + expectResult: + deletedCount: 1 + - + description: 'DeleteOne when one document matches' + operations: + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 2 } + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 3, x: 33 } + - + description: 'DeleteOne when no documents match' + operations: + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 4 } + expectResult: + deletedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/distinct-collation.json b/src/test/spec/json/crud/unified/distinct-collation.json new file mode 100644 index 000000000..e40cb0b2c --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-collation.json @@ -0,0 +1,69 @@ +{ + "description": "distinct-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "string": "PING" + }, + { + "_id": 2, + "string": "ping" + } + ] + } + ], + "tests": [ + { + "description": "Distinct with a collation", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "string", + "filter": {}, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": [ + "PING" + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/distinct-collation.yml b/src/test/spec/json/crud/unified/distinct-collation.yml new file mode 100644 index 000000000..74cf1bae3 --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-collation.yml @@ -0,0 +1,48 @@ +description: distinct-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - {_id: 1, string: 'PING'} + - {_id: 2, string: 'ping'} + +tests: + - + description: 'Distinct with a collation' + operations: + - + object: *collection0 + name: distinct + arguments: + fieldName: string + filter: { } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + - PING diff --git a/src/test/spec/json/crud/unified/distinct-comment.json b/src/test/spec/json/crud/unified/distinct-comment.json new file mode 100644 index 000000000..11bce9ac9 --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-comment.json @@ -0,0 +1,186 @@ +{ + "description": "distinct-comment", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "distinct-comment-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "distinct-comment-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "distinct with document comment", + "runOnRequirements": [ + { + "minServerVersion": "4.4.14" + } + ], + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": {}, + "comment": { + "key": "value" + } + }, + "expectResult": [ + 11, + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": {}, + "comment": { + "key": "value" + } + }, + "commandName": "distinct", + "databaseName": "distinct-comment-tests" + } + } + ] + } + ] + }, + { + "description": "distinct with string comment", + "runOnRequirements": [ + { + "minServerVersion": "4.4.0" + } + ], + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": {}, + "comment": "comment" + }, + "expectResult": [ + 11, + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": {}, + "comment": "comment" + }, + "commandName": "distinct", + "databaseName": "distinct-comment-tests" + } + } + ] + } + ] + }, + { + "description": "distinct with document comment - pre 4.4, server error", + "runOnRequirements": [ + { + "minServerVersion": "3.6.0", + "maxServerVersion": "4.4.13" + } + ], + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": {}, + "comment": { + "key": "value" + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": {}, + "comment": { + "key": "value" + } + }, + "commandName": "distinct", + "databaseName": "distinct-comment-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/distinct-comment.yml b/src/test/spec/json/crud/unified/distinct-comment.yml new file mode 100644 index 000000000..c08200994 --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-comment.yml @@ -0,0 +1,98 @@ +description: "distinct-comment" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name distinct-comment-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: "distinct with document comment" + runOnRequirements: + # https://jira.mongodb.org/browse/SERVER-44847 + # Server supports distinct with comment of any type for comment starting from 4.4.14. + - minServerVersion: "4.4.14" + operations: + - name: distinct + object: *collection0 + arguments: + fieldName: &fieldName x + filter: &filter {} + comment: &documentComment { key: "value"} + expectResult: [ 11, 22, 33 ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + distinct: *collection0Name + key: *fieldName + query: *filter + comment: *documentComment + commandName: distinct + databaseName: *database0Name + + - description: "distinct with string comment" + runOnRequirements: + - minServerVersion: "4.4.0" + operations: + - name: distinct + object: *collection0 + arguments: + fieldName: *fieldName + filter: *filter + comment: &stringComment "comment" + expectResult: [ 11, 22, 33 ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + distinct: *collection0Name + key: *fieldName + query: *filter + comment: *stringComment + commandName: distinct + databaseName: *database0Name + + - description: "distinct with document comment - pre 4.4, server error" + runOnRequirements: + - minServerVersion: "3.6.0" + maxServerVersion: "4.4.13" + operations: + - name: distinct + object: *collection0 + arguments: + fieldName: *fieldName + filter: *filter + comment: *documentComment + expectError: + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + distinct: *collection0Name + key: *fieldName + query: *filter + comment: *documentComment + commandName: distinct + databaseName: *database0Name diff --git a/src/test/spec/json/crud/unified/distinct-hint.json b/src/test/spec/json/crud/unified/distinct-hint.json new file mode 100644 index 000000000..2a6869cbe --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-hint.json @@ -0,0 +1,139 @@ +{ + "description": "distinct-hint", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "7.1.0" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "distinct-hint-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "distinct-hint-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "distinct with hint string", + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "expectResult": [ + 11 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": { + "_id": 1 + }, + "hint": "_id_" + }, + "commandName": "distinct", + "databaseName": "distinct-hint-tests" + } + } + ] + } + ] + }, + { + "description": "distinct with hint document", + "operations": [ + { + "name": "distinct", + "object": "collection0", + "arguments": { + "fieldName": "x", + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "expectResult": [ + 11 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll0", + "key": "x", + "query": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "commandName": "distinct", + "databaseName": "distinct-hint-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/distinct-hint.yml b/src/test/spec/json/crud/unified/distinct-hint.yml new file mode 100644 index 000000000..9d277616d --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct-hint.yml @@ -0,0 +1,73 @@ +description: "distinct-hint" + +schemaVersion: "1.0" +runOnRequirements: + # https://jira.mongodb.org/browse/SERVER-14227 + # Server supports distinct with hint starting from 7.1.0. + - minServerVersion: "7.1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name distinct-hint-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: "distinct with hint string" + operations: + - name: distinct + object: *collection0 + arguments: + fieldName: &fieldName x + filter: &filter { _id: 1 } + hint: _id_ + expectResult: [ 11 ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + distinct: *collection0Name + key: *fieldName + query: *filter + hint: _id_ + commandName: distinct + databaseName: *database0Name + + - description: "distinct with hint document" + operations: + - name: distinct + object: *collection0 + arguments: + fieldName: *fieldName + filter: *filter + hint: + _id: 1 + expectResult: [ 11 ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + distinct: *collection0Name + key: *fieldName + query: *filter + hint: + _id: 1 + commandName: distinct + databaseName: *database0Name diff --git a/src/test/spec/json/crud/unified/distinct.json b/src/test/spec/json/crud/unified/distinct.json new file mode 100644 index 000000000..9accffabc --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct.json @@ -0,0 +1,86 @@ +{ + "description": "distinct", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Distinct without a filter", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": {} + }, + "expectResult": [ + 11, + 22, + 33 + ] + } + ] + }, + { + "description": "Distinct with a filter", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/distinct.yml b/src/test/spec/json/crud/unified/distinct.yml new file mode 100644 index 000000000..c554a7671 --- /dev/null +++ b/src/test/spec/json/crud/unified/distinct.yml @@ -0,0 +1,54 @@ +description: distinct + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'Distinct without a filter' + operations: + - + object: *collection0 + name: distinct + arguments: + fieldName: x + filter: { } + expectResult: + - 11 + - 22 + - 33 + - + description: 'Distinct with a filter' + operations: + - + object: *collection0 + name: distinct + arguments: + fieldName: x + filter: { _id: { $gt: 1 } } + expectResult: + - 22 + - 33 diff --git a/src/test/spec/json/crud/unified/estimatedDocumentCount.json b/src/test/spec/json/crud/unified/estimatedDocumentCount.json index 1b650c1cb..3577d9006 100644 --- a/src/test/spec/json/crud/unified/estimatedDocumentCount.json +++ b/src/test/spec/json/crud/unified/estimatedDocumentCount.json @@ -249,7 +249,7 @@ "name": "estimatedDocumentCount", "object": "collection0", "expectError": { - "isError": true + "isClientError": true } } ], diff --git a/src/test/spec/json/crud/unified/estimatedDocumentCount.yml b/src/test/spec/json/crud/unified/estimatedDocumentCount.yml index 12f33cc7e..22e1d3587 100644 --- a/src/test/spec/json/crud/unified/estimatedDocumentCount.yml +++ b/src/test/spec/json/crud/unified/estimatedDocumentCount.yml @@ -130,7 +130,7 @@ tests: - name: estimatedDocumentCount object: *collection0 expectError: - isError: true + isClientError: true expectEvents: - client: *client0 events: diff --git a/src/test/spec/json/crud/unified/find-allowdiskuse-clientError.yml b/src/test/spec/json/crud/unified/find-allowdiskuse-clientError.yml index 2bc26908f..da7f6365f 100644 --- a/src/test/spec/json/crud/unified/find-allowdiskuse-clientError.yml +++ b/src/test/spec/json/crud/unified/find-allowdiskuse-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: find-allowdiskuse-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/find-allowdiskuse-serverError.yml b/src/test/spec/json/crud/unified/find-allowdiskuse-serverError.yml index de73d8b37..86f48d18e 100644 --- a/src/test/spec/json/crud/unified/find-allowdiskuse-serverError.yml +++ b/src/test/spec/json/crud/unified/find-allowdiskuse-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: find-allowdiskuse-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/find-allowdiskuse.yml b/src/test/spec/json/crud/unified/find-allowdiskuse.yml index 1bf8eee56..2f4190b9c 100644 --- a/src/test/spec/json/crud/unified/find-allowdiskuse.yml +++ b/src/test/spec/json/crud/unified/find-allowdiskuse.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: find-allowdiskuse schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/find-collation.json b/src/test/spec/json/crud/unified/find-collation.json new file mode 100644 index 000000000..13b105ad5 --- /dev/null +++ b/src/test/spec/json/crud/unified/find-collation.json @@ -0,0 +1,69 @@ +{ + "description": "find-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "Find with a collation", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": [ + { + "_id": 1, + "x": "ping" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/find-collation.yml b/src/test/spec/json/crud/unified/find-collation.yml new file mode 100644 index 000000000..9810b6097 --- /dev/null +++ b/src/test/spec/json/crud/unified/find-collation.yml @@ -0,0 +1,46 @@ +description: find-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: ping } + +tests: + - + description: 'Find with a collation' + operations: + - + object: *collection0 + name: find + arguments: + filter: { x: PING } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + - { _id: 1, x: ping } diff --git a/src/test/spec/json/crud/unified/find.json b/src/test/spec/json/crud/unified/find.json index 275d5d351..325cd96c2 100644 --- a/src/test/spec/json/crud/unified/find.json +++ b/src/test/spec/json/crud/unified/find.json @@ -151,6 +151,154 @@ ] } ] + }, + { + "description": "Find with filter", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "Find with filter, sort, skip, and limit", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": { + "$gt": 2 + } + }, + "sort": { + "_id": 1 + }, + "skip": 2, + "limit": 2 + }, + "expectResult": [ + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + } + ] + } + ] + }, + { + "description": "Find with limit, sort, and batchsize", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4, + "batchSize": 2 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "Find with batchSize equal to limit", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": 1 + }, + "limit": 4, + "batchSize": 4 + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": { + "_id": { + "$gt": 1 + } + }, + "limit": 4, + "batchSize": 5 + }, + "commandName": "find", + "databaseName": "find-tests" + } + } + ] + } + ] } ] } diff --git a/src/test/spec/json/crud/unified/find.yml b/src/test/spec/json/crud/unified/find.yml index 5615f0723..3a09c4d83 100644 --- a/src/test/spec/json/crud/unified/find.yml +++ b/src/test/spec/json/crud/unified/find.yml @@ -65,4 +65,71 @@ tests: batchSize: 2 commandName: getMore databaseName: *database0Name - + - + description: 'Find with filter' + operations: + - + object: *collection0 + name: find + arguments: + filter: { _id: 1 } + expectResult: + - { _id: 1, x: 11 } + - + description: 'Find with filter, sort, skip, and limit' + operations: + - + object: *collection0 + name: find + arguments: + filter: { _id: { $gt: 2 } } + sort: { _id: 1 } + skip: 2 + limit: 2 + expectResult: + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - + description: 'Find with limit, sort, and batchsize' + operations: + - + object: *collection0 + name: find + arguments: + filter: { } + sort: { _id: 1 } + limit: 4 + batchSize: 2 + expectResult: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'Find with batchSize equal to limit' + operations: + - + object: *collection0 + name: find + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: 1 } + limit: 4 + batchSize: 4 + expectResult: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 1 } } + limit: 4 + # Drivers use limit + 1 for batchSize to ensure the server closes the cursor + batchSize: 5 + commandName: find + databaseName: *database0Name diff --git a/src/test/spec/json/crud/unified/findOne.json b/src/test/spec/json/crud/unified/findOne.json new file mode 100644 index 000000000..826c0f5df --- /dev/null +++ b/src/test/spec/json/crud/unified/findOne.json @@ -0,0 +1,158 @@ +{ + "description": "findOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "find-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "find-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + } + ] + } + ], + "tests": [ + { + "description": "FindOne with filter", + "operations": [ + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": { + "_id": 1 + }, + "batchSize": { + "$$exists": false + }, + "limit": 1, + "singleBatch": true + }, + "commandName": "find", + "databaseName": "find-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne with filter, sort, and skip", + "operations": [ + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": { + "$gt": 2 + } + }, + "sort": { + "_id": 1 + }, + "skip": 2 + }, + "expectResult": { + "_id": 5, + "x": 55 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll0", + "filter": { + "_id": { + "$gt": 2 + } + }, + "sort": { + "_id": 1 + }, + "skip": 2, + "batchSize": { + "$$exists": false + }, + "limit": 1, + "singleBatch": true + }, + "commandName": "find", + "databaseName": "find-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOne.yml b/src/test/spec/json/crud/unified/findOne.yml new file mode 100644 index 000000000..ed74124bf --- /dev/null +++ b/src/test/spec/json/crud/unified/findOne.yml @@ -0,0 +1,75 @@ +description: "findOne" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name find-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + +tests: + - + description: 'FindOne with filter' + operations: + - + object: *collection0 + name: findOne + arguments: + filter: { _id: 1 } + expectResult: { _id: 1, x: 11 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: 1 } + batchSize: { $$exists: false } + limit: 1 + singleBatch: true + commandName: find + databaseName: *database0Name + - + description: 'FindOne with filter, sort, and skip' + operations: + - + object: *collection0 + name: findOne + arguments: + filter: { _id: { $gt: 2 } } + sort: { _id: 1 } + skip: 2 + expectResult: { _id: 5, x: 55 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 2 } } + sort: { _id: 1 } + skip: 2 + batchSize: { $$exists: false } + limit: 1 + singleBatch: true + commandName: find + databaseName: *database0Name diff --git a/src/test/spec/json/crud/unified/findOneAndDelete-collation.json b/src/test/spec/json/crud/unified/findOneAndDelete-collation.json new file mode 100644 index 000000000..a0452876a --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndDelete-collation.json @@ -0,0 +1,98 @@ +{ + "description": "findOneAndDelete-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndDelete when one document matches with collation", + "operations": [ + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 2, + "x": "PING" + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "x": "ping" + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndDelete-collation.yml b/src/test/spec/json/crud/unified/findOneAndDelete-collation.yml new file mode 100644 index 000000000..2e6de9b08 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndDelete-collation.yml @@ -0,0 +1,56 @@ +description: findOneAndDelete-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + +tests: + - + description: 'FindOneAndDelete when one document matches with collation' + operations: + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { _id: 2, x: PING } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: { x: ping } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 3, x: pINg } diff --git a/src/test/spec/json/crud/unified/findOneAndDelete-hint-clientError.yml b/src/test/spec/json/crud/unified/findOneAndDelete-hint-clientError.yml index 220496872..46c7521ad 100644 --- a/src/test/spec/json/crud/unified/findOneAndDelete-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/findOneAndDelete-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndDelete-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndDelete-hint-serverError.yml b/src/test/spec/json/crud/unified/findOneAndDelete-hint-serverError.yml index 5fd21eedc..db7513154 100644 --- a/src/test/spec/json/crud/unified/findOneAndDelete-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/findOneAndDelete-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndDelete-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndDelete-hint.yml b/src/test/spec/json/crud/unified/findOneAndDelete-hint.yml index 3dc4f3ff4..909077983 100644 --- a/src/test/spec/json/crud/unified/findOneAndDelete-hint.yml +++ b/src/test/spec/json/crud/unified/findOneAndDelete-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndDelete-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndDelete.json b/src/test/spec/json/crud/unified/findOneAndDelete.json new file mode 100644 index 000000000..e434b3b74 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndDelete.json @@ -0,0 +1,171 @@ +{ + "description": "findOneAndDelete", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndDelete when many documents match", + "operations": [ + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete when one document matches", + "operations": [ + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 2 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete when no documents match", + "operations": [ + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 4 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndDelete.yml b/src/test/spec/json/crud/unified/findOneAndDelete.yml new file mode 100644 index 000000000..6bb3a1ccc --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndDelete.yml @@ -0,0 +1,84 @@ +description: findOneAndDelete + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'FindOneAndDelete when many documents match' + operations: + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { _id: { $gt: 1 } } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndDelete when one document matches' + operations: + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { _id: 2 } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndDelete when no documents match' + operations: + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { _id: 4 } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-collation.json b/src/test/spec/json/crud/unified/findOneAndReplace-collation.json new file mode 100644 index 000000000..0d60d5416 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace-collation.json @@ -0,0 +1,97 @@ +{ + "description": "findOneAndReplace-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace when one document matches with collation returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "x": "PING" + }, + "replacement": { + "x": "pong" + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "x": "pong" + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-collation.yml b/src/test/spec/json/crud/unified/findOneAndReplace-collation.yml new file mode 100644 index 000000000..c3d8a8e78 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace-collation.yml @@ -0,0 +1,57 @@ +description: findOneAndReplace-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + +tests: + - + description: 'FindOneAndReplace when one document matches with collation returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { x: PING } + replacement: { x: pong } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: { x: pong } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: pong } diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-hint-clientError.yml b/src/test/spec/json/crud/unified/findOneAndReplace-hint-clientError.yml index f59952ffc..a0a8f94be 100644 --- a/src/test/spec/json/crud/unified/findOneAndReplace-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/findOneAndReplace-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndReplace-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-hint-serverError.yml b/src/test/spec/json/crud/unified/findOneAndReplace-hint-serverError.yml index 664cd0bbc..1fea6f364 100644 --- a/src/test/spec/json/crud/unified/findOneAndReplace-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/findOneAndReplace-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndReplace-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-hint.yml b/src/test/spec/json/crud/unified/findOneAndReplace-hint.yml index 9c581270a..67fbf2cb2 100644 --- a/src/test/spec/json/crud/unified/findOneAndReplace-hint.yml +++ b/src/test/spec/json/crud/unified/findOneAndReplace-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndReplace-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-upsert.json b/src/test/spec/json/crud/unified/findOneAndReplace-upsert.json new file mode 100644 index 000000000..f1f18996c --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace-upsert.json @@ -0,0 +1,254 @@ +{ + "description": "findOneAndReplace-upsert", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + }, + "expectResult": { + "x": 44 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + }, + "expectResult": { + "x": 44 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndReplace-upsert.yml b/src/test/spec/json/crud/unified/findOneAndReplace-upsert.yml new file mode 100644 index 000000000..c52fdf11b --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace-upsert.yml @@ -0,0 +1,127 @@ +description: findOneAndReplace-upsert + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '2.6' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'FindOneAndReplace when no documents match without id specified with upsert returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { x: 44 } + projection: { x: 1, _id: 0 } + # Omit the sort option as it has no effect when no documents match and + # would only cause an inconsistent return value on pre-3.0 servers + # when combined with returnDocument "before" (see: SERVER-17650). + upsert: true + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'FindOneAndReplace when no documents match without id specified with upsert returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { x: 44 } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + upsert: true + expectResult: { x: 44 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'FindOneAndReplace when no documents match with id specified with upsert returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + projection: { x: 1, _id: 0 } + # Omit the sort option as it has no effect when no documents match and + # would only cause an inconsistent return value on pre-3.0 servers + # when combined with returnDocument "before" (see: SERVER-17650). + upsert: true + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - + description: 'FindOneAndReplace when no documents match with id specified with upsert returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 44 } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + upsert: true + expectResult: { x: 44 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } diff --git a/src/test/spec/json/crud/unified/findOneAndReplace.json b/src/test/spec/json/crud/unified/findOneAndReplace.json new file mode 100644 index 000000000..a4731602c --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace.json @@ -0,0 +1,332 @@ +{ + "description": "findOneAndReplace", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace when many documents match returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when many documents match returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 32 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when one document matches returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when one document matches returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 32 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when no documents match returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace when no documents match returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndReplace.yml b/src/test/spec/json/crud/unified/findOneAndReplace.yml new file mode 100644 index 000000000..ddfd53355 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndReplace.yml @@ -0,0 +1,152 @@ +description: findOneAndReplace + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'FindOneAndReplace when many documents match returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: { $gt: 1 } } + replacement: { x: 32 } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 32 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndReplace when many documents match returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: { $gt: 1 } } + replacement: { x: 32 } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: { x: 32 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 32 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndReplace when one document matches returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 2 } + replacement: { x: 32 } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 32 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndReplace when one document matches returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 2 } + replacement: { x: 32 } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: { x: 32 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 32 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndReplace when no documents match returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { x: 44 } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndReplace when no documents match returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 4 } + replacement: { x: 44 } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.json b/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.json new file mode 100644 index 000000000..6c99e4ff6 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.json @@ -0,0 +1,251 @@ +{ + "description": "findOneAndUpdate-arrayFilters", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.5.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate when no document matches arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + }, + "expectResult": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when one document matches arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + }, + "expectResult": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when multiple documents match arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + }, + "expectResult": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.yml new file mode 100644 index 000000000..42e392c2c --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-arrayFilters.yml @@ -0,0 +1,89 @@ +description: findOneAndUpdate-arrayFilters + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: 3.5.6 + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + +tests: + - + description: 'FindOneAndUpdate when no document matches arrayFilters' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 4 } + expectResult: { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - + description: 'FindOneAndUpdate when one document matches arrayFilters' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 3 } + expectResult: { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 2 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - + description: 'FindOneAndUpdate when multiple documents match arrayFilters' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 1 } + expectResult: { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 2 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-collation.json b/src/test/spec/json/crud/unified/findOneAndUpdate-collation.json new file mode 100644 index 000000000..7a49347a3 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-collation.json @@ -0,0 +1,106 @@ +{ + "description": "findOneAndUpdate-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate when many documents match with collation returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "x": "PING" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "_id": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "x": "ping" + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-collation.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-collation.yml new file mode 100644 index 000000000..8b2048bc8 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-collation.yml @@ -0,0 +1,58 @@ +description: findOneAndUpdate-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + +tests: + - + description: 'FindOneAndUpdate when many documents match with collation returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { x: PING } + update: { $set: { x: pong } } + projection: { x: 1, _id: 0 } + sort: { _id: 1 } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: { x: ping } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: pong } + - { _id: 3, x: pINg } diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.json b/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.json new file mode 100644 index 000000000..5023a450f --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.json @@ -0,0 +1,132 @@ +{ + "description": "findOneAndUpdate-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": "foo" + } + ] + } + ], + "tests": [ + { + "description": "findOneAndUpdate DuplicateKey error is accessible", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "operations": [ + { + "name": "createIndex", + "object": "collection0", + "arguments": { + "keys": { + "x": 1 + }, + "unique": true + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$set": { + "x": "foo" + } + }, + "upsert": true + }, + "expectError": { + "errorCode": 11000, + "errorResponse": { + "keyPattern": { + "x": 1 + }, + "keyValue": { + "x": "foo" + } + } + } + } + ] + }, + { + "description": "findOneAndUpdate document validation errInfo is accessible", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "modifyCollection", + "object": "database0", + "arguments": { + "collection": "test", + "validator": { + "x": { + "$type": "string" + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + }, + "expectError": { + "errorCode": 121, + "errorResponse": { + "errInfo": { + "failingDocumentId": 1, + "details": { + "$$type": "object" + } + } + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.yml new file mode 100644 index 000000000..8faed7680 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-errorResponse.yml @@ -0,0 +1,69 @@ +description: "findOneAndUpdate-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +initialData: &initialData + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: "foo" } + +tests: + - description: "findOneAndUpdate DuplicateKey error is accessible" + runOnRequirements: + - minServerVersion: "4.2" # SERVER-37124 + operations: + - name: createIndex + object: *collection0 + arguments: + keys: { x: 1 } + unique: true + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 2 } + update: { $set: { x: "foo" } } + upsert: true + expectError: + errorCode: 11000 # DuplicateKey + errorResponse: + keyPattern: { x: 1 } + keyValue: { x: "foo" } + + - description: "findOneAndUpdate document validation errInfo is accessible" + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: modifyCollection + object: *database0 + arguments: + collection: *collection0Name + validator: + x: { $type: "string" } + - name: findOneAndUpdate + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: 121 # DocumentValidationFailure + errorResponse: + # Avoid asserting the exact contents of errInfo as it may vary by + # server version. Likewise, this is why drivers do not model the + # document. The following is sufficient to test that validation + # details are accessible. See SERVER-20547 for more context. + errInfo: + failingDocumentId: 1 + details: { $$type: "object" } diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-hint-clientError.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-hint-clientError.yml index 5ad4f07cc..77b41a165 100644 --- a/src/test/spec/json/crud/unified/findOneAndUpdate-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndUpdate-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-hint-serverError.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-hint-serverError.yml index f6b4f8d62..13abba5f5 100644 --- a/src/test/spec/json/crud/unified/findOneAndUpdate-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndUpdate-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-hint.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-hint.yml index 5e835faa9..f7a3c33f0 100644 --- a/src/test/spec/json/crud/unified/findOneAndUpdate-hint.yml +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: findOneAndUpdate-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.json b/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.json new file mode 100644 index 000000000..81dba9ae9 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.json @@ -0,0 +1,130 @@ +{ + "description": "findOneAndUpdate-pipeline", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate using pipelines", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "commandName": "findAndModify", + "databaseName": "crud-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.yml b/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.yml new file mode 100644 index 000000000..6a8dfed6c --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate-pipeline.yml @@ -0,0 +1,56 @@ +description: findOneAndUpdate-pipeline + +schemaVersion: '1.0' + +runOnRequirements: + - minServerVersion: 4.1.11 + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-tests + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name test + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, y: 1, t: { u: { v: 1 } } } + - { _id: 2, x: 2, y: 1 } + +tests: + - + description: 'FindOneAndUpdate using pipelines' + operations: + - object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + findAndModify: *collection_name + update: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + commandName: findAndModify + databaseName: *database_name + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, foo: 1 } + - { _id: 2, x: 2, y: 1 } diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate.json b/src/test/spec/json/crud/unified/findOneAndUpdate.json new file mode 100644 index 000000000..d79cf8ac5 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate.json @@ -0,0 +1,448 @@ +{ + "description": "findOneAndUpdate", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate when many documents match returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when many documents match returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 23 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when one document matches returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 22 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when one document matches returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": { + "x": 23 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when no documents match returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when no documents match with upsert returning the document before modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when no documents match returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + }, + "expectResult": null + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate when no documents match with upsert returning the document after modification", + "operations": [ + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + }, + "expectResult": { + "x": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/findOneAndUpdate.yml b/src/test/spec/json/crud/unified/findOneAndUpdate.yml new file mode 100644 index 000000000..181c6b8b5 --- /dev/null +++ b/src/test/spec/json/crud/unified/findOneAndUpdate.yml @@ -0,0 +1,199 @@ +description: findOneAndUpdate + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'FindOneAndUpdate when many documents match returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when many documents match returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: { x: 23 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when one document matches returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: { x: 22 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when one document matches returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: { x: 23 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when no documents match returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + sort: { x: 1 } + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when no documents match with upsert returning the document before modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + # Omit the sort option as it has no effect when no documents match and + # would only cause an inconsistent return value on pre-3.0 servers + # when combined with returnDocument "before" (see: SERVER-17650). + upsert: true + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } + - + description: 'FindOneAndUpdate when no documents match returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + expectResult: null + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'FindOneAndUpdate when no documents match with upsert returning the document after modification' + operations: + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + projection: { x: 1, _id: 0 } + returnDocument: After + sort: { x: 1 } + upsert: true + expectResult: { x: 1 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } diff --git a/src/test/spec/json/crud/unified/insertMany.json b/src/test/spec/json/crud/unified/insertMany.json new file mode 100644 index 000000000..643b7f44d --- /dev/null +++ b/src/test/spec/json/crud/unified/insertMany.json @@ -0,0 +1,205 @@ +{ + "description": "insertMany", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "InsertMany with non-existing documents", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": false + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertMany continue-on-error behavior with unordered (duplicate key in requests)", + "operations": [ + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": false + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/insertMany.yml b/src/test/spec/json/crud/unified/insertMany.yml new file mode 100644 index 000000000..ac4e868cc --- /dev/null +++ b/src/test/spec/json/crud/unified/insertMany.yml @@ -0,0 +1,118 @@ +description: insertMany + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'InsertMany with non-existing documents' + operations: + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertMany continue-on-error behavior with unordered (preexisting duplicate key)' + operations: + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: false + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 2 + # Since the map of insertedIds is generated before execution it + # could indicate inserts that did not actually succeed. We omit this + # field rather than expect drivers to provide an accurate map + # filtered by write errors. + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertMany continue-on-error behavior with unordered (duplicate key in requests)' + operations: + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: false + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 2 + # Since the map of insertedIds is generated before execution it + # could indicate inserts that did not actually succeed. We omit this + # field rather than expect drivers to provide an accurate map + # filtered by write errors. + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/insertOne-dots_and_dollars.yml b/src/test/spec/json/crud/unified/insertOne-dots_and_dollars.yml index f255b5241..fcfcfc71c 100644 --- a/src/test/spec/json/crud/unified/insertOne-dots_and_dollars.yml +++ b/src/test/spec/json/crud/unified/insertOne-dots_and_dollars.yml @@ -198,7 +198,7 @@ tests: # Using "$db" here works for libmongoc so long as it's a string type; # however, neither $ref nor $id would be accepted on their own. # - # See https://github.com/mongodb/specifications/blob/master/source/extended-json.rst#parsers + # See https://github.com/mongodb/specifications/blob/master/source/extended-json.md#parsers document: &dbrefLikeKey { _id: 1, a: { $db: "foo" } } expectResult: *insertResult expectEvents: diff --git a/src/test/spec/json/crud/unified/insertOne-errorResponse.json b/src/test/spec/json/crud/unified/insertOne-errorResponse.json new file mode 100644 index 000000000..04ea6a745 --- /dev/null +++ b/src/test/spec/json/crud/unified/insertOne-errorResponse.json @@ -0,0 +1,82 @@ +{ + "description": "insertOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "insert operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1 + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/insertOne-errorResponse.yml b/src/test/spec/json/crud/unified/insertOne-errorResponse.yml new file mode 100644 index 000000000..b14caa173 --- /dev/null +++ b/src/test/spec/json/crud/unified/insertOne-errorResponse.yml @@ -0,0 +1,46 @@ +description: "insertOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "insert operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: &errorCode 8 # UnknownError + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1 } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/src/test/spec/json/crud/unified/insertOne.json b/src/test/spec/json/crud/unified/insertOne.json new file mode 100644 index 000000000..1a9091347 --- /dev/null +++ b/src/test/spec/json/crud/unified/insertOne.json @@ -0,0 +1,77 @@ +{ + "description": "insertOne", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "InsertOne with a non-existing document", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/insertOne.yml b/src/test/spec/json/crud/unified/insertOne.yml new file mode 100644 index 000000000..85ab7fe9e --- /dev/null +++ b/src/test/spec/json/crud/unified/insertOne.yml @@ -0,0 +1,44 @@ +description: insertOne + +schemaVersion: '1.0' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'InsertOne with a non-existing document' + operations: + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 2, x: 22 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/crud/unified/replaceOne-collation.json b/src/test/spec/json/crud/unified/replaceOne-collation.json new file mode 100644 index 000000000..dd76b9d61 --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne-collation.json @@ -0,0 +1,92 @@ +{ + "description": "replaceOne-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne when one document matches with collation", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "x": "PING" + }, + "replacement": { + "_id": 2, + "x": "pong" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/replaceOne-collation.yml b/src/test/spec/json/crud/unified/replaceOne-collation.yml new file mode 100644 index 000000000..68eddcff9 --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne-collation.yml @@ -0,0 +1,57 @@ +description: replaceOne-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + +tests: + - + description: 'ReplaceOne when one document matches with collation' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { x: PING } + replacement: { _id: 2, x: pong } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: pong } diff --git a/src/test/spec/json/crud/unified/replaceOne-hint.yml b/src/test/spec/json/crud/unified/replaceOne-hint.yml index 8a28be982..263e37217 100644 --- a/src/test/spec/json/crud/unified/replaceOne-hint.yml +++ b/src/test/spec/json/crud/unified/replaceOne-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: replaceOne-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/replaceOne-sort.json b/src/test/spec/json/crud/unified/replaceOne-sort.json new file mode 100644 index 000000000..cf2271dda --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne-sort.json @@ -0,0 +1,232 @@ +{ + "description": "replaceOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 1 + } + ] + } + ] + }, + { + "description": "replaceOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "replacement": { + "x": 1 + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 1 + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/replaceOne-sort.yml b/src/test/spec/json/crud/unified/replaceOne-sort.yml new file mode 100644 index 000000000..f4b10fbaf --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne-sort.yml @@ -0,0 +1,94 @@ +description: replaceOne-sort + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent, commandSucceededEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: ReplaceOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - name: replaceOne + object: *collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - commandSucceededEvent: + reply: { ok: 1, n: 1 } + commandName: update + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 1 } + + - description: replaceOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - name: replaceOne + object: *collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + replacement: { x: 1 } + expectError: + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { x: 1 } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/replaceOne.json b/src/test/spec/json/crud/unified/replaceOne.json new file mode 100644 index 000000000..bdb7556f2 --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne.json @@ -0,0 +1,259 @@ +{ + "description": "replaceOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne when many documents match", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ] + }, + { + "description": "ReplaceOne when one document matches", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "ReplaceOne when no documents match", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 1 + } + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "ReplaceOne with upsert when no documents match without an id specified", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 1 + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + }, + { + "description": "ReplaceOne with upsert when no documents match with an id specified", + "operations": [ + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 1 + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/replaceOne.yml b/src/test/spec/json/crud/unified/replaceOne.yml new file mode 100644 index 000000000..ebe0af933 --- /dev/null +++ b/src/test/spec/json/crud/unified/replaceOne.yml @@ -0,0 +1,138 @@ +description: replaceOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '2.6' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + # This test doesn't verify the output collection because we cannot assume + # which document gets replaced. + description: 'ReplaceOne when many documents match' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: { $gt: 1 } } + replacement: { x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - + description: 'ReplaceOne when one document matches' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'ReplaceOne when no documents match' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 1 } + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'ReplaceOne with upsert when no documents match without an id specified' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 4 } + replacement: { x: 1 } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } + - + description: 'ReplaceOne with upsert when no documents match with an id specified' + operations: + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 4 } + replacement: { _id: 4, x: 1 } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } diff --git a/src/test/spec/json/crud/unified/updateMany-arrayFilters.json b/src/test/spec/json/crud/unified/updateMany-arrayFilters.json new file mode 100644 index 000000000..8730caeb4 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-arrayFilters.json @@ -0,0 +1,233 @@ +{ + "description": "updateMany-arrayFilters", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.5.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany when no documents match arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ] + }, + { + "description": "UpdateMany when one document matches arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + ] + }, + { + "description": "UpdateMany when multiple documents match arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 2 + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateMany-arrayFilters.yml b/src/test/spec/json/crud/unified/updateMany-arrayFilters.yml new file mode 100644 index 000000000..db5ec29c8 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-arrayFilters.yml @@ -0,0 +1,98 @@ +description: updateMany-arrayFilters + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: 3.5.6 + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + +tests: + - + description: 'UpdateMany when no documents match arrayFilters' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 4 } + expectResult: + matchedCount: 2 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - + description: 'UpdateMany when one document matches arrayFilters' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 3 } + expectResult: + matchedCount: 2 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 2 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - + description: 'UpdateMany when multiple documents match arrayFilters' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 1 } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 2 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 2 } ] } diff --git a/src/test/spec/json/crud/unified/updateMany-collation.json b/src/test/spec/json/crud/unified/updateMany-collation.json new file mode 100644 index 000000000..0c780a3c2 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-collation.json @@ -0,0 +1,101 @@ +{ + "description": "updateMany-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany when many documents match with collation", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + }, + { + "_id": 3, + "x": "pong" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateMany-collation.yml b/src/test/spec/json/crud/unified/updateMany-collation.yml new file mode 100644 index 000000000..af5221c02 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-collation.yml @@ -0,0 +1,59 @@ +description: updateMany-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + - { _id: 3, x: pINg } + +tests: + - + description: 'UpdateMany when many documents match with collation' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { x: ping } + update: { $set: { x: pong } } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: pong } + - { _id: 3, x: pong } diff --git a/src/test/spec/json/crud/unified/updateMany-hint-clientError.yml b/src/test/spec/json/crud/unified/updateMany-hint-clientError.yml index 9734078ce..163168ecf 100644 --- a/src/test/spec/json/crud/unified/updateMany-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/updateMany-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateMany-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateMany-hint-serverError.yml b/src/test/spec/json/crud/unified/updateMany-hint-serverError.yml index 03b803bdd..61d7179fc 100644 --- a/src/test/spec/json/crud/unified/updateMany-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/updateMany-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateMany-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateMany-hint.yml b/src/test/spec/json/crud/unified/updateMany-hint.yml index 8b6c228f4..e59503092 100644 --- a/src/test/spec/json/crud/unified/updateMany-hint.yml +++ b/src/test/spec/json/crud/unified/updateMany-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateMany-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateMany-pipeline.json b/src/test/spec/json/crud/unified/updateMany-pipeline.json new file mode 100644 index 000000000..e0f6d9d4a --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-pipeline.json @@ -0,0 +1,142 @@ +{ + "description": "updateMany-pipeline", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany using pipelines", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": {}, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + }, + "commandName": "update", + "databaseName": "crud-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateMany-pipeline.yml b/src/test/spec/json/crud/unified/updateMany-pipeline.yml new file mode 100644 index 000000000..a177f22ef --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany-pipeline.yml @@ -0,0 +1,64 @@ +description: updateMany-pipeline + +schemaVersion: '1.0' + +runOnRequirements: + - minServerVersion: 4.1.11 + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-tests + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name test + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, y: 1, t: { u: { v: 1 } } } + - { _id: 2, x: 2, y: 1 } + +tests: + - + description: 'UpdateMany using pipelines' + operations: + - object: *collection0 + name: updateMany + arguments: + filter: { } + update: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - q: { } + u: + - { $project: { x: 1 } } + - { $addFields: { foo: 1 } } + multi: true + upsert: { $$unsetOrMatches: false } + commandName: update + databaseName: *database_name + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, foo: 1 } + - { _id: 2, x: 2, foo: 1 } diff --git a/src/test/spec/json/crud/unified/updateMany.json b/src/test/spec/json/crud/unified/updateMany.json new file mode 100644 index 000000000..19b890592 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany.json @@ -0,0 +1,236 @@ +{ + "description": "updateMany", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany when many documents match", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + }, + { + "description": "UpdateMany when one document matches", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "UpdateMany when no documents match", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "UpdateMany with upsert when no documents match", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateMany.yml b/src/test/spec/json/crud/unified/updateMany.yml new file mode 100644 index 000000000..98f29d45e --- /dev/null +++ b/src/test/spec/json/crud/unified/updateMany.yml @@ -0,0 +1,120 @@ +description: updateMany + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '2.6' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + description: 'UpdateMany when many documents match' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 23 } + - { _id: 3, x: 34 } + - + description: 'UpdateMany when one document matches' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'UpdateMany when no documents match' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'UpdateMany with upsert when no documents match' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } diff --git a/src/test/spec/json/crud/unified/updateOne-arrayFilters.json b/src/test/spec/json/crud/unified/updateOne-arrayFilters.json new file mode 100644 index 000000000..be5d05b01 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-arrayFilters.json @@ -0,0 +1,453 @@ +{ + "description": "updateOne-arrayFilters", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.5.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne when no document matches arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + ] + }, + { + "description": "UpdateOne when one document matches arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + ] + }, + { + "description": "UpdateOne when multiple documents match arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + ] + }, + { + "description": "UpdateOne when no documents match multiple arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "y.$[i].c.$[j].d": 0 + } + }, + "arrayFilters": [ + { + "i.b": 5 + }, + { + "j.d": 3 + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + ] + }, + { + "description": "UpdateOne when one document matches multiple arrayFilters", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "y.$[i].c.$[j].d": 0 + } + }, + "arrayFilters": [ + { + "i.b": 5 + }, + { + "j.d": 1 + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 0 + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne-arrayFilters.yml b/src/test/spec/json/crud/unified/updateOne-arrayFilters.yml new file mode 100644 index 000000000..8d5b238be --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-arrayFilters.yml @@ -0,0 +1,150 @@ +description: updateOne-arrayFilters + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: 3.5.6 + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 1 } ] } ] } + +tests: + - + description: 'UpdateOne when no document matches arrayFilters' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 4 } + expectResult: + matchedCount: 1 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 1 } ] } ] } + - + description: 'UpdateOne when one document matches arrayFilters' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 3 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 2 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 1 } ] } ] } + - + description: 'UpdateOne when multiple documents match arrayFilters' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { } + update: { $set: { 'y.$[i].b': 2 } } + arrayFilters: + - { i.b: 1 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 2 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 1 } ] } ] } + - + description: 'UpdateOne when no documents match multiple arrayFilters' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 3 } + update: { $set: { 'y.$[i].c.$[j].d': 0 } } + arrayFilters: + - { i.b: 5 } + - { j.d: 3 } + expectResult: + matchedCount: 1 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 1 } ] } ] } + - + description: 'UpdateOne when one document matches multiple arrayFilters' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 3 } + update: { $set: { 'y.$[i].c.$[j].d': 0 } } + arrayFilters: + - { i.b: 5 } + - { j.d: 1 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: [ { b: 3 }, { b: 1 } ] } + - { _id: 2, y: [ { b: 0 }, { b: 1 } ] } + - { _id: 3, y: [ { b: 5, c: [ { d: 2 }, { d: 0 } ] } ] } diff --git a/src/test/spec/json/crud/unified/updateOne-collation.json b/src/test/spec/json/crud/unified/updateOne-collation.json new file mode 100644 index 000000000..a39be4605 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-collation.json @@ -0,0 +1,93 @@ +{ + "description": "updateOne-collation", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.4", + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne when one document matches with collation", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "x": "PING" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne-collation.yml b/src/test/spec/json/crud/unified/updateOne-collation.yml new file mode 100644 index 000000000..85c2b5529 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-collation.yml @@ -0,0 +1,57 @@ +description: updateOne-collation + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '3.4' + serverless: forbid + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: ping } + +tests: + - + description: 'UpdateOne when one document matches with collation' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { x: PING } + update: { $set: { x: pong } } + # https://www.mongodb.com/docs/manual/reference/collation/#collation-document + collation: + locale: en_US + strength: 2 + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: pong } diff --git a/src/test/spec/json/crud/unified/updateOne-errorResponse.json b/src/test/spec/json/crud/unified/updateOne-errorResponse.json new file mode 100644 index 000000000..0ceddbc4f --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-errorResponse.json @@ -0,0 +1,87 @@ +{ + "description": "updateOne-errorResponse", + "schemaVersion": "1.12", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "tests": [ + { + "description": "update operations support errorResponse assertions", + "runOnRequirements": [ + { + "minServerVersion": "4.0.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.2.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 8 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + }, + "expectError": { + "errorCode": 8, + "errorResponse": { + "code": 8 + } + } + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne-errorResponse.yml b/src/test/spec/json/crud/unified/updateOne-errorResponse.yml new file mode 100644 index 000000000..6d42195b0 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-errorResponse.yml @@ -0,0 +1,47 @@ +description: "updateOne-errorResponse" + +schemaVersion: "1.12" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name test + +tests: + # Some drivers may still need to skip this test because the CRUD spec does not + # prescribe how drivers should formulate a WriteException beyond collecting a + # write or write concern error. + - description: "update operations support errorResponse assertions" + runOnRequirements: + - minServerVersion: "4.0.0" + topologies: [ single, replicaset ] + - minServerVersion: "4.2.0" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: &errorCode 8 # UnknownError + - name: updateOne + object: *collection0 + arguments: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorCode: *errorCode + errorResponse: + code: *errorCode diff --git a/src/test/spec/json/crud/unified/updateOne-hint-clientError.yml b/src/test/spec/json/crud/unified/updateOne-hint-clientError.yml index 87b4444c3..5107b5f4b 100644 --- a/src/test/spec/json/crud/unified/updateOne-hint-clientError.yml +++ b/src/test/spec/json/crud/unified/updateOne-hint-clientError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateOne-hint-clientError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateOne-hint-serverError.yml b/src/test/spec/json/crud/unified/updateOne-hint-serverError.yml index 0b804d05a..f9681ab82 100644 --- a/src/test/spec/json/crud/unified/updateOne-hint-serverError.yml +++ b/src/test/spec/json/crud/unified/updateOne-hint-serverError.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateOne-hint-serverError schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateOne-hint.yml b/src/test/spec/json/crud/unified/updateOne-hint.yml index 8f5d1460a..bd60cd7a1 100644 --- a/src/test/spec/json/crud/unified/updateOne-hint.yml +++ b/src/test/spec/json/crud/unified/updateOne-hint.yml @@ -1,6 +1,3 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - description: updateOne-hint schemaVersion: '1.0' runOnRequirements: diff --git a/src/test/spec/json/crud/unified/updateOne-pipeline.json b/src/test/spec/json/crud/unified/updateOne-pipeline.json new file mode 100644 index 000000000..1348c6b53 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-pipeline.json @@ -0,0 +1,150 @@ +{ + "description": "updateOne-pipeline", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne using pipelines", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + }, + "commandName": "update", + "databaseName": "crud-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "u": { + "v": 1 + }, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne-pipeline.yml b/src/test/spec/json/crud/unified/updateOne-pipeline.yml new file mode 100644 index 000000000..726d93c92 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-pipeline.yml @@ -0,0 +1,64 @@ +description: updateOne-pipeline + +schemaVersion: '1.0' + +runOnRequirements: + - minServerVersion: 4.1.11 + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-tests + - collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name test + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1, y: 1, t: { u: { v: 1 } } } + - { _id: 2, x: 2, y: 1 } + +tests: + - + description: 'UpdateOne using pipelines' + operations: + - object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: + - { $replaceRoot: { newRoot: $t } } + - { $addFields: { foo: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection_name + updates: + - q: { _id: 1 } + u: + - { $replaceRoot: { newRoot: $t } } + - { $addFields: { foo: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + commandName: update + databaseName: *database_name + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, u: { v: 1 }, foo: 1 } + - { _id: 2, x: 2, y: 1 } diff --git a/src/test/spec/json/crud/unified/updateOne-sort.json b/src/test/spec/json/crud/unified/updateOne-sort.json new file mode 100644 index 000000000..8fe4f50b9 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-sort.json @@ -0,0 +1,240 @@ +{ + "description": "updateOne-sort", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent", + "commandSucceededEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne with sort option", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + }, + { + "commandSucceededEvent": { + "reply": { + "ok": 1, + "n": 1 + }, + "commandName": "update" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + }, + { + "description": "updateOne with sort option unsupported (server-side error)", + "runOnRequirements": [ + { + "maxServerVersion": "7.99" + } + ], + "operations": [ + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "sort": { + "_id": -1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isClientError": false + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "coll0", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "sort": { + "_id": -1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ] + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "crud-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne-sort.yml b/src/test/spec/json/crud/unified/updateOne-sort.yml new file mode 100644 index 000000000..a14e1df1d --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne-sort.yml @@ -0,0 +1,96 @@ +description: updateOne-sort + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - commandSucceededEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name crud-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: UpdateOne with sort option + runOnRequirements: + - minServerVersion: "8.0" + operations: + - name: updateOne + object: *collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { $inc: { x: 1 } } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - commandSucceededEvent: + reply: { ok: 1, n: 1 } + commandName: update + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 34 } + + - description: updateOne with sort option unsupported (server-side error) + runOnRequirements: + - maxServerVersion: "7.99" + operations: + - name: updateOne + object: *collection0 + arguments: + filter: { _id: { $gt: 1 } } + sort: { _id: -1 } + update: { $inc: { x: 1 } } + expectError: + isClientError: false + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + update: *collection0Name + updates: + - q: { _id: { $gt: 1 } } + u: { $inc: { x: 1 } } + sort: { _id: -1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/unified/updateOne.json b/src/test/spec/json/crud/unified/updateOne.json new file mode 100644 index 000000000..a3f559673 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne.json @@ -0,0 +1,216 @@ +{ + "description": "updateOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "crud-v1" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne when many documents match", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ] + }, + { + "description": "UpdateOne when one document matches", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "UpdateOne when no documents match", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "UpdateOne with upsert when no documents match", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "crud-v1", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/crud/unified/updateOne.yml b/src/test/spec/json/crud/unified/updateOne.yml new file mode 100644 index 000000000..2f8f6a918 --- /dev/null +++ b/src/test/spec/json/crud/unified/updateOne.yml @@ -0,0 +1,114 @@ +description: updateOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '2.6' + +createEntities: + - + client: + id: &client0 client0 + - + database: + id: &database0 database0 + client: client0 + databaseName: &database_name crud-v1 + - + collection: + id: &collection0 collection0 + database: database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - + # This test doesn't verify the output collection because we cannot assume + # which document gets updated. + description: 'UpdateOne when many documents match' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: { $gt: 1 } } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - + description: 'UpdateOne when one document matches' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'UpdateOne when no documents match' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'UpdateOne with upsert when no documents match' + operations: + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 4 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 1 } diff --git a/src/test/spec/json/crud/unified/updateWithPipelines.json b/src/test/spec/json/crud/unified/updateWithPipelines.json deleted file mode 100644 index 164f2f6a1..000000000 --- a/src/test/spec/json/crud/unified/updateWithPipelines.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "description": "updateWithPipelines", - "schemaVersion": "1.0", - "runOnRequirements": [ - { - "minServerVersion": "4.1.11" - } - ], - "createEntities": [ - { - "client": { - "id": "client0", - "observeEvents": [ - "commandStartedEvent" - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "crud-tests" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "test" - } - } - ], - "initialData": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "x": 1, - "y": 1, - "t": { - "u": { - "v": 1 - } - } - }, - { - "_id": 2, - "x": 2, - "y": 1 - } - ] - } - ], - "tests": [ - { - "description": "UpdateOne using pipelines", - "operations": [ - { - "object": "collection0", - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": [ - { - "$replaceRoot": { - "newRoot": "$t" - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - }, - "expectResult": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": [ - { - "$replaceRoot": { - "newRoot": "$t" - } - }, - { - "$addFields": { - "foo": 1 - } - } - ], - "multi": { - "$$unsetOrMatches": false - }, - "upsert": { - "$$unsetOrMatches": false - } - } - ] - }, - "commandName": "update", - "databaseName": "crud-tests" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "u": { - "v": 1 - }, - "foo": 1 - }, - { - "_id": 2, - "x": 2, - "y": 1 - } - ] - } - ] - }, - { - "description": "UpdateMany using pipelines", - "operations": [ - { - "object": "collection0", - "name": "updateMany", - "arguments": { - "filter": {}, - "update": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - }, - "expectResult": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "update": "test", - "updates": [ - { - "q": {}, - "u": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ], - "multi": true, - "upsert": { - "$$unsetOrMatches": false - } - } - ] - }, - "commandName": "update", - "databaseName": "crud-tests" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "x": 1, - "foo": 1 - }, - { - "_id": 2, - "x": 2, - "foo": 1 - } - ] - } - ] - }, - { - "description": "FindOneAndUpdate using pipelines", - "operations": [ - { - "object": "collection0", - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "findAndModify": "test", - "update": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - }, - "commandName": "findAndModify", - "databaseName": "crud-tests" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "x": 1, - "foo": 1 - }, - { - "_id": 2, - "x": 2, - "y": 1 - } - ] - } - ] - }, - { - "description": "UpdateOne in bulk write using pipelines", - "operations": [ - { - "object": "collection0", - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "updateOne": { - "filter": { - "_id": 1 - }, - "update": [ - { - "$replaceRoot": { - "newRoot": "$t" - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - } - } - ] - }, - "expectResult": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": [ - { - "$replaceRoot": { - "newRoot": "$t" - } - }, - { - "$addFields": { - "foo": 1 - } - } - ], - "multi": { - "$$unsetOrMatches": false - }, - "upsert": { - "$$unsetOrMatches": false - } - } - ] - }, - "commandName": "update", - "databaseName": "crud-tests" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "u": { - "v": 1 - }, - "foo": 1 - }, - { - "_id": 2, - "x": 2, - "y": 1 - } - ] - } - ] - }, - { - "description": "UpdateMany in bulk write using pipelines", - "operations": [ - { - "object": "collection0", - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "updateMany": { - "filter": {}, - "update": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ] - } - } - ] - }, - "expectResult": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "update": "test", - "updates": [ - { - "q": {}, - "u": [ - { - "$project": { - "x": 1 - } - }, - { - "$addFields": { - "foo": 1 - } - } - ], - "multi": true, - "upsert": { - "$$unsetOrMatches": false - } - } - ] - }, - "commandName": "update", - "databaseName": "crud-tests" - } - } - ] - } - ], - "outcome": [ - { - "collectionName": "test", - "databaseName": "crud-tests", - "documents": [ - { - "_id": 1, - "x": 1, - "foo": 1 - }, - { - "_id": 2, - "x": 2, - "foo": 1 - } - ] - } - ] - } - ] -} diff --git a/src/test/spec/json/crud/unified/updateWithPipelines.yml b/src/test/spec/json/crud/unified/updateWithPipelines.yml deleted file mode 100644 index 30666e31d..000000000 --- a/src/test/spec/json/crud/unified/updateWithPipelines.yml +++ /dev/null @@ -1,299 +0,0 @@ -# This file was created automatically using mongodb-spec-converter. -# Please review the generated file, then remove this notice. - -description: updateWithPipelines -schemaVersion: '1.0' -runOnRequirements: - - - minServerVersion: 4.1.11 -createEntities: - - - client: - id: &client0 client0 - observeEvents: - - commandStartedEvent - - - database: - id: &database0 database0 - client: client0 - databaseName: &database_name crud-tests - - - collection: - id: &collection0 collection0 - database: database0 - collectionName: &collection_name test -initialData: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - x: 1 - 'y': 1 - t: - u: - v: 1 - - - _id: 2 - x: 2 - 'y': 1 -tests: - - - description: 'UpdateOne using pipelines' - operations: - - - object: *collection0 - name: updateOne - arguments: - filter: - _id: 1 - update: - - - $replaceRoot: - newRoot: $t - - - $addFields: - foo: 1 - expectResult: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectEvents: - - - client: *client0 - events: - - - commandStartedEvent: - command: - update: *collection_name - updates: - - - q: - _id: 1 - u: - - { $replaceRoot: { newRoot: $t } } - - { $addFields: { foo: 1 } } - multi: { $$unsetOrMatches: false } - upsert: { $$unsetOrMatches: false } - commandName: update - databaseName: *database_name - outcome: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - u: - v: 1 - foo: 1 - - - _id: 2 - x: 2 - 'y': 1 - - - description: 'UpdateMany using pipelines' - operations: - - - object: *collection0 - name: updateMany - arguments: - filter: { } - update: - - - $project: - x: 1 - - - $addFields: - foo: 1 - expectResult: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - expectEvents: - - - client: *client0 - events: - - - commandStartedEvent: - command: - update: *collection_name - updates: - - - q: { } - u: - - { $project: { x: 1 } } - - { $addFields: { foo: 1 } } - multi: true - upsert: { $$unsetOrMatches: false } - commandName: update - databaseName: *database_name - outcome: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - x: 1 - foo: 1 - - - _id: 2 - x: 2 - foo: 1 - - - description: 'FindOneAndUpdate using pipelines' - operations: - - - object: *collection0 - name: findOneAndUpdate - arguments: - filter: - _id: 1 - update: - - - $project: - x: 1 - - - $addFields: - foo: 1 - expectEvents: - - - client: *client0 - events: - - - commandStartedEvent: - command: - findAndModify: *collection_name - update: - - - $project: - x: 1 - - - $addFields: - foo: 1 - commandName: findAndModify - databaseName: *database_name - outcome: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - x: 1 - foo: 1 - - - _id: 2 - x: 2 - 'y': 1 - - - description: 'UpdateOne in bulk write using pipelines' - operations: - - - object: *collection0 - name: bulkWrite - arguments: - requests: - - - updateOne: - filter: - _id: 1 - update: - - - $replaceRoot: - newRoot: $t - - - $addFields: - foo: 1 - expectResult: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - expectEvents: - - - client: *client0 - events: - - - commandStartedEvent: - command: - update: *collection_name - updates: - - - q: - _id: 1 - u: - - { $replaceRoot: { newRoot: $t } } - - { $addFields: { foo: 1 } } - multi: { $$unsetOrMatches: false } - upsert: { $$unsetOrMatches: false } - commandName: update - databaseName: *database_name - outcome: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - u: - v: 1 - foo: 1 - - - _id: 2 - x: 2 - 'y': 1 - - - description: 'UpdateMany in bulk write using pipelines' - operations: - - - object: *collection0 - name: bulkWrite - arguments: - requests: - - - updateMany: - filter: { } - update: - - - $project: - x: 1 - - - $addFields: - foo: 1 - expectResult: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - expectEvents: - - - client: *client0 - events: - - - commandStartedEvent: - command: - update: *collection_name - updates: - - - q: { } - u: - - { $project: { x: 1 } } - - { $addFields: { foo: 1 } } - multi: true - upsert: { $$unsetOrMatches: false } - commandName: update - databaseName: *database_name - outcome: - - - collectionName: *collection_name - databaseName: *database_name - documents: - - - _id: 1 - x: 1 - foo: 1 - - - _id: 2 - x: 2 - foo: 1 diff --git a/src/test/spec/json/crud/v1/read/aggregate-collation.json b/src/test/spec/json/crud/v1/read/aggregate-collation.json deleted file mode 100644 index d958e447b..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate-collation.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "Aggregate with collation", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "x": "PING" - } - } - ], - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": "ping" - } - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/aggregate-collation.yml b/src/test/spec/json/crud/v1/read/aggregate-collation.yml deleted file mode 100644 index 924192a6e..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate-collation.yml +++ /dev/null @@ -1,18 +0,0 @@ -data: - - {_id: 1, x: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "Aggregate with collation" - operation: - name: aggregate - arguments: - pipeline: - - $match: - x: 'PING' - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - outcome: - result: - - {_id: 1, x: 'ping'} diff --git a/src/test/spec/json/crud/v1/read/aggregate-out.json b/src/test/spec/json/crud/v1/read/aggregate-out.json deleted file mode 100644 index c195e163e..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate-out.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "serverless": "forbid", - "tests": [ - { - "description": "Aggregate with $out", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$out": "other_test_collection" - } - ], - "batchSize": 2 - } - }, - "outcome": { - "collection": { - "name": "other_test_collection", - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "Aggregate with $out and batch size of 0", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$out": "other_test_collection" - } - ], - "batchSize": 0 - } - }, - "outcome": { - "collection": { - "name": "other_test_collection", - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/aggregate-out.yml b/src/test/spec/json/crud/v1/read/aggregate-out.yml deleted file mode 100644 index d6688dd08..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate-out.yml +++ /dev/null @@ -1,44 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} -minServerVersion: '2.6' -serverless: 'forbid' - -tests: - - - description: "Aggregate with $out" - operation: - name: aggregate - arguments: - pipeline: - - $sort: {x: 1} - - $match: - _id: {$gt: 1} - - $out: "other_test_collection" - batchSize: 2 - - outcome: - collection: - name: "other_test_collection" - data: - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "Aggregate with $out and batch size of 0" - operation: - name: aggregate - arguments: - pipeline: - - $sort: {x: 1} - - $match: - _id: {$gt: 1} - - $out: "other_test_collection" - batchSize: 0 - - outcome: - collection: - name: "other_test_collection" - data: - - {_id: 2, x: 22} - - {_id: 3, x: 33} diff --git a/src/test/spec/json/crud/v1/read/aggregate.json b/src/test/spec/json/crud/v1/read/aggregate.json deleted file mode 100644 index 797a92239..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Aggregate with multiple stages", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - } - ], - "batchSize": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/aggregate.yml b/src/test/spec/json/crud/v1/read/aggregate.yml deleted file mode 100644 index 98b69445c..000000000 --- a/src/test/spec/json/crud/v1/read/aggregate.yml +++ /dev/null @@ -1,21 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Aggregate with multiple stages" - operation: - name: aggregate - arguments: - pipeline: - - $sort: {x: 1} - - $match: - _id: {$gt: 1} - batchSize: 2 - - outcome: - result: - - {_id: 2, x: 22} - - {_id: 3, x: 33} diff --git a/src/test/spec/json/crud/v1/read/count-collation.json b/src/test/spec/json/crud/v1/read/count-collation.json deleted file mode 100644 index 7d6150849..000000000 --- a/src/test/spec/json/crud/v1/read/count-collation.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "PING" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "Count documents with collation", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": { - "x": "ping" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": 1 - } - }, - { - "description": "Deprecated count with collation", - "operation": { - "name": "count", - "arguments": { - "filter": { - "x": "ping" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": 1 - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/count-collation.yml b/src/test/spec/json/crud/v1/read/count-collation.yml deleted file mode 100644 index fd1c29a07..000000000 --- a/src/test/spec/json/crud/v1/read/count-collation.yml +++ /dev/null @@ -1,26 +0,0 @@ -data: - - {_id: 1, x: 'PING'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "Count documents with collation" - operation: - name: countDocuments - arguments: - filter: { x: 'ping' } - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: 1 - - - description: "Deprecated count with collation" - operation: - name: count - arguments: - filter: { x: 'ping' } - collation: { locale: 'en_US', strength: 2 } - - outcome: - result: 1 diff --git a/src/test/spec/json/crud/v1/read/count-empty.json b/src/test/spec/json/crud/v1/read/count-empty.json deleted file mode 100644 index 2b8627e0c..000000000 --- a/src/test/spec/json/crud/v1/read/count-empty.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": [], - "tests": [ - { - "description": "Estimated document count with empty collection", - "operation": { - "name": "estimatedDocumentCount", - "arguments": {} - }, - "outcome": { - "result": 0 - } - }, - { - "description": "Count documents with empty collection", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 0 - } - }, - { - "description": "Deprecated count with empty collection", - "operation": { - "name": "count", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 0 - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/count-empty.yml b/src/test/spec/json/crud/v1/read/count-empty.yml deleted file mode 100644 index 83098ec18..000000000 --- a/src/test/spec/json/crud/v1/read/count-empty.yml +++ /dev/null @@ -1,29 +0,0 @@ -data: [] - -tests: - - - description: "Estimated document count with empty collection" - operation: - name: estimatedDocumentCount - arguments: { } - - outcome: - result: 0 - - - description: "Count documents with empty collection" - operation: - name: countDocuments - arguments: - filter: { } - - outcome: - result: 0 - - - description: "Deprecated count with empty collection" - operation: - name: count - arguments: - filter: { } - - outcome: - result: 0 diff --git a/src/test/spec/json/crud/v1/read/count.json b/src/test/spec/json/crud/v1/read/count.json deleted file mode 100644 index 9642b2fbd..000000000 --- a/src/test/spec/json/crud/v1/read/count.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Estimated document count", - "operation": { - "name": "estimatedDocumentCount", - "arguments": {} - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Count documents without a filter", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Count documents with a filter", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Count documents with skip and limit", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {}, - "skip": 1, - "limit": 3 - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Deprecated count without a filter", - "operation": { - "name": "count", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Deprecated count with a filter", - "operation": { - "name": "count", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Deprecated count with skip and limit", - "operation": { - "name": "count", - "arguments": { - "filter": {}, - "skip": 1, - "limit": 3 - } - }, - "outcome": { - "result": 2 - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/count.yml b/src/test/spec/json/crud/v1/read/count.yml deleted file mode 100644 index 33d380b7d..000000000 --- a/src/test/spec/json/crud/v1/read/count.yml +++ /dev/null @@ -1,74 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Estimated document count" - operation: - name: estimatedDocumentCount - arguments: { } - - outcome: - result: 3 - - - description: "Count documents without a filter" - operation: - name: countDocuments - arguments: - filter: { } - - outcome: - result: 3 - - - description: "Count documents with a filter" - operation: - name: countDocuments - arguments: - filter: - _id: {$gt: 1} - - outcome: - result: 2 - - - description: "Count documents with skip and limit" - operation: - name: countDocuments - arguments: - filter: {} - skip: 1 - limit: 3 - - outcome: - result: 2 - - - description: "Deprecated count without a filter" - operation: - name: count - arguments: - filter: { } - - outcome: - result: 3 - - - description: "Deprecated count with a filter" - operation: - name: count - arguments: - filter: - _id: {$gt: 1} - - outcome: - result: 2 - - - description: "Deprecated count with skip and limit" - operation: - name: count - arguments: - filter: {} - skip: 1 - limit: 3 - - outcome: - result: 2 diff --git a/src/test/spec/json/crud/v1/read/distinct-collation.json b/src/test/spec/json/crud/v1/read/distinct-collation.json deleted file mode 100644 index 984991a43..000000000 --- a/src/test/spec/json/crud/v1/read/distinct-collation.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "string": "PING" - }, - { - "_id": 2, - "string": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "Distinct with a collation", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "string", - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - "PING" - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/distinct-collation.yml b/src/test/spec/json/crud/v1/read/distinct-collation.yml deleted file mode 100644 index 8785f29d4..000000000 --- a/src/test/spec/json/crud/v1/read/distinct-collation.yml +++ /dev/null @@ -1,18 +0,0 @@ -data: - - {_id: 1, string: 'PING'} - - {_id: 2, string: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "Distinct with a collation" - operation: - name: distinct - arguments: - fieldName: "string" - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - - 'PING' diff --git a/src/test/spec/json/crud/v1/read/distinct.json b/src/test/spec/json/crud/v1/read/distinct.json deleted file mode 100644 index a57ee36a8..000000000 --- a/src/test/spec/json/crud/v1/read/distinct.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Distinct without a filter", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "x", - "filter": {} - } - }, - "outcome": { - "result": [ - 11, - 22, - 33 - ] - } - }, - { - "description": "Distinct with a filter", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": [ - 22, - 33 - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/distinct.yml b/src/test/spec/json/crud/v1/read/distinct.yml deleted file mode 100644 index aefc7e0fd..000000000 --- a/src/test/spec/json/crud/v1/read/distinct.yml +++ /dev/null @@ -1,32 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Distinct without a filter" - operation: - name: distinct - arguments: - fieldName: "x" - filter: {} - - outcome: - result: - - 11 - - 22 - - 33 - - - description: "Distinct with a filter" - operation: - name: distinct - arguments: - fieldName: "x" - filter: - _id: {$gt: 1} - - outcome: - result: - - 22 - - 33 \ No newline at end of file diff --git a/src/test/spec/json/crud/v1/read/find-collation.json b/src/test/spec/json/crud/v1/read/find-collation.json deleted file mode 100644 index 4e56c0525..000000000 --- a/src/test/spec/json/crud/v1/read/find-collation.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "Find with a collation", - "operation": { - "name": "find", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": "ping" - } - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/find-collation.yml b/src/test/spec/json/crud/v1/read/find-collation.yml deleted file mode 100644 index 1ecc49154..000000000 --- a/src/test/spec/json/crud/v1/read/find-collation.yml +++ /dev/null @@ -1,16 +0,0 @@ -data: - - {_id: 1, x: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "Find with a collation" - operation: - name: "find" - arguments: - filter: {x: 'PING'} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - outcome: - result: - - {_id: 1, x: 'ping'} diff --git a/src/test/spec/json/crud/v1/read/find.json b/src/test/spec/json/crud/v1/read/find.json deleted file mode 100644 index 3597e37be..000000000 --- a/src/test/spec/json/crud/v1/read/find.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "Find with filter", - "operation": { - "name": "find", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": 11 - } - ] - } - }, - { - "description": "Find with filter, sort, skip, and limit", - "operation": { - "name": "find", - "arguments": { - "filter": { - "_id": { - "$gt": 2 - } - }, - "sort": { - "_id": 1 - }, - "skip": 2, - "limit": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 5, - "x": 55 - } - ] - } - }, - { - "description": "Find with limit, sort, and batchsize", - "operation": { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4, - "batchSize": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/read/find.yml b/src/test/spec/json/crud/v1/read/find.yml deleted file mode 100644 index 25099df7f..000000000 --- a/src/test/spec/json/crud/v1/read/find.yml +++ /dev/null @@ -1,49 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - {_id: 5, x: 55} - -tests: - - - description: "Find with filter" - operation: - name: "find" - arguments: - filter: {_id: 1} - - outcome: - result: - - {_id: 1, x: 11} - - - - description: "Find with filter, sort, skip, and limit" - operation: - name: "find" - arguments: - filter: - _id: {$gt: 2} - sort: {_id: 1} - skip: 2 - limit: 2 - - outcome: - result: - - {_id: 5, x: 55} - - - description: "Find with limit, sort, and batchsize" - operation: - name: "find" - arguments: - filter: {} - sort: {_id: 1} - limit: 4 - batchSize: 2 - - outcome: - result: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} diff --git a/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.json b/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.json deleted file mode 100644 index 99e73f5d7..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "BulkWrite with arrayFilters", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 3, - "modifiedCount": 3, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 2 - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.yml b/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.yml deleted file mode 100644 index 1089f08c0..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite-arrayFilters.yml +++ /dev/null @@ -1,45 +0,0 @@ -data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - -minServerVersion: '3.5.6' - -tests: - - - description: "BulkWrite with arrayFilters" - operation: - name: "bulkWrite" - arguments: - requests: - - - # UpdateOne when one document matches arrayFilters - name: "updateOne" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 3} - - - # UpdateMany when multiple documents match arrayFilters - name: "updateMany" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 1} - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: {} - matchedCount: 3 - modifiedCount: 3 - upsertedCount: 0 - upsertedIds: {} - collection: - data: - - {_id: 1, y: [{b: 2}, {b: 2}]} - - {_id: 2, y: [{b: 0}, {b: 2}]} diff --git a/src/test/spec/json/crud/v1/write/bulkWrite-collation.json b/src/test/spec/json/crud/v1/write/bulkWrite-collation.json deleted file mode 100644 index bc90aa817..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite-collation.json +++ /dev/null @@ -1,218 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - }, - { - "_id": 4, - "x": "pong" - }, - { - "_id": 5, - "x": "pONg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "BulkWrite with delete operations and collation", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": "PONG" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 4, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "BulkWrite with update operations and collation", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateMany", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 3 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "x": "ping" - }, - "replacement": { - "_id": 6, - "x": "ping" - }, - "upsert": true, - "collation": { - "locale": "en_US", - "strength": 3 - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": "pong" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 6, - "modifiedCount": 4, - "upsertedCount": 1, - "upsertedIds": { - "2": 6 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "PONG" - }, - { - "_id": 3, - "x": "PONG" - }, - { - "_id": 4, - "x": "PONG" - }, - { - "_id": 5, - "x": "PONG" - }, - { - "_id": 6, - "x": "ping" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/bulkWrite-collation.yml b/src/test/spec/json/crud/v1/write/bulkWrite-collation.yml deleted file mode 100644 index 75ab468a5..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite-collation.yml +++ /dev/null @@ -1,102 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} - - {_id: 4, x: 'pong'} - - {_id: 5, x: 'pONg'} - -minServerVersion: '3.4' -serverless: 'forbid' - -# See: https://www.mongodb.com/docs/manual/reference/collation/#collation-document -tests: - - - description: "BulkWrite with delete operations and collation" - operation: - name: "bulkWrite" - arguments: - requests: - - - # matches two documents but deletes one - name: "deleteOne" - arguments: - filter: { x: "PING" } - collation: { locale: "en_US", strength: 2 } - - - # matches the remaining document and deletes it - name: "deleteOne" - arguments: - filter: { x: "PING" } - collation: { locale: "en_US", strength: 2 } - - - # matches two documents and deletes them - name: "deleteMany" - arguments: - filter: { x: "PONG" } - collation: { locale: "en_US", strength: 2 } - options: { ordered: true } - outcome: - result: - deletedCount: 4 - insertedCount: 0 - insertedIds: {} - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - collection: - data: - - {_id: 1, x: 11 } - - - description: "BulkWrite with update operations and collation" - operation: - name: "bulkWrite" - arguments: - requests: - - - # matches only one document due to strength and updates - name: "updateMany" - arguments: - filter: { x: "ping" } - update: { $set: { x: "PONG" } } - collation: { locale: "en_US", strength: 3 } - - - # matches one document and updates - name: "updateOne" - arguments: - filter: { x: "ping" } - update: { $set: { x: "PONG" } } - collation: { locale: "en_US", strength: 2 } - - - # matches no document due to strength and upserts - name: "replaceOne" - arguments: - filter: { x: "ping" } - replacement: { _id: 6, x: "ping" } - upsert: true - collation: { locale: "en_US", strength: 3 } - - - # matches two documents and updates - name: "updateMany" - arguments: - filter: { x: "pong" } - update: { $set: { x: "PONG" } } - collation: { locale: "en_US", strength: 2 } - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: {} - matchedCount: 6 - modifiedCount: 4 - upsertedCount: 1 - upsertedIds: { 2: 6 } - collection: - data: - - {_id: 1, x: 11 } - - {_id: 2, x: "PONG" } - - {_id: 3, x: "PONG" } - - {_id: 4, x: "PONG" } - - {_id: 5, x: "PONG" } - - {_id: 6, x: "ping" } diff --git a/src/test/spec/json/crud/v1/write/bulkWrite.json b/src/test/spec/json/crud/v1/write/bulkWrite.json deleted file mode 100644 index dc00da28a..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite.json +++ /dev/null @@ -1,778 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "BulkWrite with deleteOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 3 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "BulkWrite with deleteMany operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$lt": 11 - } - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 2, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [] - } - } - }, - { - "description": "BulkWrite with insertOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 2, - "insertedIds": { - "0": 3, - "1": 4 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite with replaceOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "x": 33 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "x": 12 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "x": 33 - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "2": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with updateOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 0 - }, - "update": { - "$set": { - "x": 0 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 11 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "x": 33 - } - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 2, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "3": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with updateMany operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lt": 11 - } - }, - "update": { - "$set": { - "x": 0 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - }, - "update": { - "$unset": { - "y": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "x": 33 - } - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 4, - "modifiedCount": 2, - "upsertedCount": 1, - "upsertedIds": { - "3": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with mixed ordered operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$nin": [ - 24, - 34 - ] - } - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 2, - "insertedCount": 2, - "insertedIds": { - "0": 3, - "3": 4 - }, - "matchedCount": 3, - "modifiedCount": 3, - "upsertedCount": 1, - "upsertedIds": { - "5": 4 - } - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 24 - }, - { - "_id": 3, - "x": 34 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite with mixed unordered operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "_id": 3, - "x": 33 - }, - "upsert": true - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "0": 3 - } - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/bulkWrite.yml b/src/test/spec/json/crud/v1/write/bulkWrite.yml deleted file mode 100644 index 988bdf4d4..000000000 --- a/src/test/spec/json/crud/v1/write/bulkWrite.yml +++ /dev/null @@ -1,401 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - -minServerVersion: '2.6' - -tests: - - - description: "BulkWrite with deleteOne operations" - operation: - name: "bulkWrite" - arguments: - # Note: as in the "DeleteOne when many documents match" test in - # deleteOne.yml, we omit a deleteOne operation that might match - # multiple documents as that would hinder our ability to assert - # the final state of the collection under test. - requests: - - - # does not match an existing document - name: "deleteOne" - arguments: - filter: { _id: 3 } - - - # deletes the matched document - name: "deleteOne" - arguments: - filter: { _id: 2 } - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 0 - insertedIds: {} - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - collection: - data: - - {_id: 1, x: 11 } - - - description: "BulkWrite with deleteMany operations" - operation: - name: "bulkWrite" - arguments: - requests: - - - # does not match any existing documents - name: "deleteMany" - arguments: - filter: { x: { $lt: 11 } } - - - # deletes the matched documents - name: "deleteMany" - arguments: - filter: { x: { $lte: 22 } } - options: { ordered: true } - outcome: - result: - deletedCount: 2 - insertedCount: 0 - insertedIds: {} - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - collection: - data: [] - - - description: "BulkWrite with insertOne operations" - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "insertOne" - arguments: - document: { _id: 4, x: 44 } - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 2 - insertedIds: { 0: 3, 1: 4 } - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - collection: - data: - - {_id: 1, x: 11 } - - {_id: 2, x: 22 } - - {_id: 3, x: 33 } - - {_id: 4, x: 44 } - - - description: "BulkWrite with replaceOne operations" - operation: - name: "bulkWrite" - arguments: - # Note: as in the "ReplaceOne when many documents match" test in - # replaceOne.yml, we omit a replaceOne operation that might - # match multiple documents as that would hinder our ability to - # assert the final state of the collection under test. - requests: - - - # does not match an existing document - name: "replaceOne" - arguments: - filter: { _id: 3 } - replacement: { x: 33 } - - - # modifies the matched document - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { x: 12 } - - - # does not match an existing document and upserts - name: "replaceOne" - arguments: - filter: { _id: 3 } - replacement: { x: 33 } - upsert: true - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: {} - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 1 - upsertedIds: { 2: 3 } - collection: - data: - - {_id: 1, x: 12 } - - {_id: 2, x: 22 } - - {_id: 3, x: 33 } - - - description: "BulkWrite with updateOne operations" - operation: - name: "bulkWrite" - arguments: - # Note: as in the "UpdateOne when many documents match" test in - # updateOne.yml, we omit an updateOne operation that might match - # multiple documents as that would hinder our ability to assert - # the final state of the collection under test. - requests: - - - # does not match an existing document - name: "updateOne" - arguments: - filter: { _id: 0 } - update: { $set: { x: 0 } } - - - # does not modify the matched document - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $set: { x: 11 } } - - - # modifies the matched document - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x: 1 } } - - - # does not match an existing document and upserts - name: "updateOne" - arguments: - filter: { _id: 3 } - update: { $set: { x: 33 } } - upsert: true - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: {} - matchedCount: 2 - modifiedCount: 1 - upsertedCount: 1 - upsertedIds: { 3: 3 } - collection: - data: - - {_id: 1, x: 11 } - - {_id: 2, x: 23 } - - {_id: 3, x: 33 } - - - description: "BulkWrite with updateMany operations" - operation: - name: "bulkWrite" - arguments: - requests: - - - # does not match any existing documents - name: "updateMany" - arguments: - filter: { x: { $lt: 11 } } - update: { $set: { x: 0 } } - - - # does not modify the matched documents - name: "updateMany" - arguments: - filter: { x: { $lte: 22 } } - update: { $unset: { y: 1 } } - - - # modifies the matched documents - name: "updateMany" - arguments: - filter: { x: { $lte: 22 } } - update: { $inc: { x: 1 } } - - - # does not match any existing documents and upserts - name: "updateMany" - arguments: - filter: { _id: 3 } - update: { $set: { x: 33 } } - upsert: true - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: {} - matchedCount: 4 - modifiedCount: 2 - upsertedCount: 1 - upsertedIds: { 3: 3 } - collection: - data: - - {_id: 1, x: 12 } - - {_id: 2, x: 23 } - - {_id: 3, x: 33 } - - - description: "BulkWrite with mixed ordered operations" - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x: 1 } } - - - name: "updateMany" - arguments: - filter: { _id: { $gt: 1 } } - update: { $inc: { x: 1 } } - - - name: "insertOne" - arguments: - document: { _id: 4, x: 44 } - - - name: "deleteMany" - arguments: - filter: { x: { $nin: [ 24, 34 ] } } - - - name: "replaceOne" - arguments: - filter: { _id: 4 } - replacement: { _id: 4, x: 44 } - upsert: true - options: { ordered: true } - outcome: - result: - deletedCount: 2 - insertedCount: 2 - insertedIds: { 0: 3, 3: 4 } - matchedCount: 3 - modifiedCount: 3 - upsertedCount: 1 - upsertedIds: { 5: 4 } - collection: - data: - - {_id: 2, x: 24 } - - {_id: 3, x: 34 } - - {_id: 4, x: 44 } - - - description: "BulkWrite with mixed unordered operations" - operation: - name: "bulkWrite" - arguments: - # We omit inserting multiple documents and updating documents - # that may not exist at the start of this test as we cannot - # assume the order in which the operations will execute. - requests: - - - name: "replaceOne" - arguments: - filter: { _id: 3 } - replacement: { _id: 3, x: 33 } - upsert: true - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x: 1 } } - options: { ordered: false } - outcome: - result: - deletedCount: 1 - insertedCount: 0 - insertedIds: {} - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 1 - upsertedIds: { 0: 3 } - collection: - data: - - {_id: 2, x: 23 } - - {_id: 3, x: 33 } - - - description: "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)" - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "insertOne" - arguments: - document: { _id: 4, x: 44 } - options: { ordered: false } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 2 - # Since the map of insertedIds is generated before execution it - # could indicate inserts that did not actually succeed. We omit - # this field rather than expect drivers to provide an accurate - # map filtered by write errors. - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - { _id: 4, x: 44 } - - - description: "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)" - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "insertOne" - arguments: - document: { _id: 4, x: 44 } - options: { ordered: false } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 2 - # Since the map of insertedIds is generated before execution it - # could indicate inserts that did not actually succeed. We omit - # this field rather than expect drivers to provide an accurate - # map filtered by write errors. - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - { _id: 4, x: 44 } diff --git a/src/test/spec/json/crud/v1/write/deleteMany-collation.json b/src/test/spec/json/crud/v1/write/deleteMany-collation.json deleted file mode 100644 index fce75e488..000000000 --- a/src/test/spec/json/crud/v1/write/deleteMany-collation.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "DeleteMany when many documents match with collation", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/deleteMany-collation.yml b/src/test/spec/json/crud/v1/write/deleteMany-collation.yml deleted file mode 100644 index 65a49b7fd..000000000 --- a/src/test/spec/json/crud/v1/write/deleteMany-collation.yml +++ /dev/null @@ -1,23 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "DeleteMany when many documents match with collation" - operation: - name: "deleteMany" - arguments: - filter: - x: 'PING' - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - deletedCount: 2 - collection: - data: - - {_id: 1, x: 11} diff --git a/src/test/spec/json/crud/v1/write/deleteMany.json b/src/test/spec/json/crud/v1/write/deleteMany.json deleted file mode 100644 index 7eee85e77..000000000 --- a/src/test/spec/json/crud/v1/write/deleteMany.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "DeleteMany when many documents match", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": { - "deletedCount": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "DeleteMany when no document matches", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "_id": 4 - } - } - }, - "outcome": { - "result": { - "deletedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/deleteMany.yml b/src/test/spec/json/crud/v1/write/deleteMany.yml deleted file mode 100644 index e776c82f9..000000000 --- a/src/test/spec/json/crud/v1/write/deleteMany.yml +++ /dev/null @@ -1,35 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "DeleteMany when many documents match" - operation: - name: "deleteMany" - arguments: - filter: - _id: {$gt: 1} - - outcome: - result: - deletedCount: 2 - collection: - data: - - {_id: 1, x: 11} - - - description: "DeleteMany when no document matches" - operation: - name: "deleteMany" - arguments: - filter: {_id: 4} - - outcome: - result: - deletedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} diff --git a/src/test/spec/json/crud/v1/write/deleteOne-collation.json b/src/test/spec/json/crud/v1/write/deleteOne-collation.json deleted file mode 100644 index 9bcef411e..000000000 --- a/src/test/spec/json/crud/v1/write/deleteOne-collation.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "DeleteOne when many documents matches with collation", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/deleteOne-collation.yml b/src/test/spec/json/crud/v1/write/deleteOne-collation.yml deleted file mode 100644 index ce335bf3d..000000000 --- a/src/test/spec/json/crud/v1/write/deleteOne-collation.yml +++ /dev/null @@ -1,23 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "DeleteOne when many documents matches with collation" - operation: - name: "deleteOne" - arguments: - filter: {x: 'PING'} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - deletedCount: 1 - collection: - data: - - {_id: 1, x: 11} - - {_id: 3, x: 'pINg'} diff --git a/src/test/spec/json/crud/v1/write/deleteOne.json b/src/test/spec/json/crud/v1/write/deleteOne.json deleted file mode 100644 index a1106deae..000000000 --- a/src/test/spec/json/crud/v1/write/deleteOne.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "DeleteOne when many documents match", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - } - } - }, - { - "description": "DeleteOne when one document matches", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "DeleteOne when no documents match", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 4 - } - } - }, - "outcome": { - "result": { - "deletedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/deleteOne.yml b/src/test/spec/json/crud/v1/write/deleteOne.yml deleted file mode 100644 index 2d8a90779..000000000 --- a/src/test/spec/json/crud/v1/write/deleteOne.yml +++ /dev/null @@ -1,48 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "DeleteOne when many documents match" - operation: - name: "deleteOne" - arguments: - filter: - _id: {$gt: 1} - - outcome: - result: - deletedCount: 1 - # can't verify collection because we don't have a way - # of knowing which document gets deleted. - - - description: "DeleteOne when one document matches" - operation: - name: "deleteOne" - arguments: - filter: {_id: 2} - - outcome: - result: - deletedCount: 1 - collection: - data: - - {_id: 1, x: 11} - - {_id: 3, x: 33} - - - description: "DeleteOne when no documents match" - operation: - name: "deleteOne" - arguments: - filter: {_id: 4} - - outcome: - result: - deletedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} diff --git a/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.json b/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.json deleted file mode 100644 index 32480da84..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "FindOneAndDelete when one document matches with collation", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 2, - "x": "PING" - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "ping" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.yml b/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.yml deleted file mode 100644 index 720ecb2f8..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndDelete-collation.yml +++ /dev/null @@ -1,24 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "FindOneAndDelete when one document matches with collation" - operation: - name: findOneAndDelete - arguments: - filter: {_id: 2, x: 'PING'} - projection: {x: 1, _id: 0} - sort: {x: 1} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: {x: 'ping'} - collection: - data: - - {_id: 1, x: 11} - - {_id: 3, x: 'pINg'} diff --git a/src/test/spec/json/crud/v1/write/findOneAndDelete.json b/src/test/spec/json/crud/v1/write/findOneAndDelete.json deleted file mode 100644 index e424e2aad..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndDelete.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndDelete when many documents match", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndDelete when one document matches", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 2 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndDelete when no documents match", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 4 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndDelete.yml b/src/test/spec/json/crud/v1/write/findOneAndDelete.yml deleted file mode 100644 index a485ee46a..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndDelete.yml +++ /dev/null @@ -1,53 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "FindOneAndDelete when many documents match" - operation: - name: findOneAndDelete - arguments: - filter: - _id: {$gt: 1} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 3, x: 33} - - - description: "FindOneAndDelete when one document matches" - operation: - name: findOneAndDelete - arguments: - filter: {_id: 2} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 3, x: 33} - - - description: "FindOneAndDelete when no documents match" - operation: - name: findOneAndDelete - arguments: - filter: {_id: 4} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} \ No newline at end of file diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.json b/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.json deleted file mode 100644 index 9b3c25005..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "FindOneAndReplace when one document matches with collation returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "x": "PING" - }, - "replacement": { - "x": "pong" - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "pong" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.yml b/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.yml deleted file mode 100644 index f132b1211..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace-collation.yml +++ /dev/null @@ -1,25 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "FindOneAndReplace when one document matches with collation returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: {x: 'PING'} - replacement: {x: 'pong'} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: {x: 'pong'} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 'pong'} diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.json b/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.json deleted file mode 100644 index 0f07bf9c1..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 44 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 44 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.yml b/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.yml deleted file mode 100644 index 2bd64c94e..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace-upsert.yml +++ /dev/null @@ -1,91 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} -minServerVersion: '2.6' - -tests: - - - description: "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {x: 44} - projection: {x: 1, _id: 0} - # Omit the sort option as it has no effect when no documents - # match and would only cause an inconsistent return value on - # pre-3.0 servers when combined with returnDocument "before" - # (see: SERVER-17650). - upsert: true - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - - description: "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {x: 44} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - upsert: true - - outcome: - result: {x: 44} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - - description: "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {_id: 4, x: 44} - projection: {x: 1, _id: 0} - # Omit the sort option as it has no effect when no documents - # match and would only cause an inconsistent return value on - # pre-3.0 servers when combined with returnDocument "before" - # (see: SERVER-17650). - upsert: true - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - - description: "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {_id: 4, x: 44} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - upsert: true - - outcome: - result: {x: 44} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace.json b/src/test/spec/json/crud/v1/write/findOneAndReplace.json deleted file mode 100644 index 70e5c3df4..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndReplace when many documents match returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when many documents match returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 32 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when one document matches returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 2 - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when one document matches returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 2 - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 32 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndReplace.yml b/src/test/spec/json/crud/v1/write/findOneAndReplace.yml deleted file mode 100644 index c121418b7..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndReplace.yml +++ /dev/null @@ -1,113 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "FindOneAndReplace when many documents match returning the document before modification" - operation: - name: findOneAndReplace - arguments: - filter: - _id: {$gt: 1} - replacement: {x: 32} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 32} - - {_id: 3, x: 33} - - - description: "FindOneAndReplace when many documents match returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: - _id: {$gt: 1} - replacement: {x: 32} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: {x: 32} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 32} - - {_id: 3, x: 33} - - - description: "FindOneAndReplace when one document matches returning the document before modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 2} - replacement: {x: 32} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 32} - - {_id: 3, x: 33} - - - description: "FindOneAndReplace when one document matches returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 2} - replacement: {x: 32} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: {x: 32} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 32} - - {_id: 3, x: 33} - - - description: "FindOneAndReplace when no documents match returning the document before modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {x: 44} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "FindOneAndReplace when no documents match returning the document after modification" - operation: - name: findOneAndReplace - arguments: - filter: {_id: 4} - replacement: {x: 44} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.json b/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.json deleted file mode 100644 index 1aa13b863..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "FindOneAndUpdate when no document matches arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when multiple documents match arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.yml b/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.yml deleted file mode 100644 index 7665ad0c5..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate-arrayFilters.yml +++ /dev/null @@ -1,69 +0,0 @@ -data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} -minServerVersion: '3.5.6' - -tests: - - - description: "FindOneAndUpdate when no document matches arrayFilters" - operation: - name: findOneAndUpdate - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 4} - - outcome: - result: - _id: 1 - y: - - {b: 3} - - {b: 1} - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - - description: "FindOneAndUpdate when one document matches arrayFilters" - operation: - name: findOneAndUpdate - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 3} - - outcome: - result: - _id: 1 - y: - - {b: 3} - - {b: 1} - collection: - data: - - {_id: 1, y: [{b: 2}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - - description: "FindOneAndUpdate when multiple documents match arrayFilters" - operation: - name: findOneAndUpdate - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 1} - - outcome: - result: - _id: 1 - y: - - {b: 3} - - {b: 1} - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 2}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.json b/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.json deleted file mode 100644 index 8abab7bd6..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "FindOneAndUpdate when many documents match with collation returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "x": "PING" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "_id": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "ping" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.yml b/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.yml deleted file mode 100644 index 5a110b01b..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate-collation.yml +++ /dev/null @@ -1,28 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "FindOneAndUpdate when many documents match with collation returning the document before modification" - operation: - name: findOneAndUpdate - arguments: - filter: - x: 'PING' - update: - $set: {x: 'pong'} - projection: {x: 1, _id: 0} - sort: {_id: 1} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: {x: 'ping'} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 'pong'} - - {_id: 3, x: 'pINg'} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate.json b/src/test/spec/json/crud/v1/write/findOneAndUpdate.json deleted file mode 100644 index 6da832527..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate.json +++ /dev/null @@ -1,379 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndUpdate when many documents match returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when many documents match returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 23 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 23 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match with upsert returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match with upsert returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/findOneAndUpdate.yml b/src/test/spec/json/crud/v1/write/findOneAndUpdate.yml deleted file mode 100644 index bed02f146..000000000 --- a/src/test/spec/json/crud/v1/write/findOneAndUpdate.yml +++ /dev/null @@ -1,163 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "FindOneAndUpdate when many documents match returning the document before modification" - operation: - name: findOneAndUpdate - arguments: - filter: - _id: {$gt: 1} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 23} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when many documents match returning the document after modification" - operation: - name: findOneAndUpdate - arguments: - filter: - _id: {$gt: 1} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: {x: 23} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 23} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when one document matches returning the document before modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 2} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: {x: 22} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 23} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when one document matches returning the document after modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 2} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: {x: 23} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 23} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when no documents match returning the document before modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - sort: {x: 1} - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when no documents match with upsert returning the document before modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - # Omit the sort option as it has no effect when no documents - # match and would only cause an inconsistent return value on - # pre-3.0 servers when combined with returnDocument "before" - # (see: SERVER-17650). - upsert: true - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} - - - description: "FindOneAndUpdate when no documents match returning the document after modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - - outcome: - result: null - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "FindOneAndUpdate when no documents match with upsert returning the document after modification" - operation: - name: findOneAndUpdate - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - projection: {x: 1, _id: 0} - returnDocument: After - sort: {x: 1} - upsert: true - - outcome: - result: {x: 1} - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} \ No newline at end of file diff --git a/src/test/spec/json/crud/v1/write/insertMany.json b/src/test/spec/json/crud/v1/write/insertMany.json deleted file mode 100644 index 6a2e5261b..000000000 --- a/src/test/spec/json/crud/v1/write/insertMany.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertMany with non-existing documents", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany continue-on-error behavior with unordered (duplicate key in requests)", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/insertMany.yml b/src/test/spec/json/crud/v1/write/insertMany.yml deleted file mode 100644 index f76e4af68..000000000 --- a/src/test/spec/json/crud/v1/write/insertMany.yml +++ /dev/null @@ -1,77 +0,0 @@ -data: - - {_id: 1, x: 11} - -tests: - - - description: "InsertMany with non-existing documents" - operation: - name: "insertMany" - arguments: - documents: - - {_id: 2, x: 22} - - {_id: 3, x: 33} - options: { ordered: true } - outcome: - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)" - operation: - name: "insertMany" - arguments: - documents: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: false } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 2 - # Since the map of insertedIds is generated before execution it - # could indicate inserts that did not actually succeed. We omit - # this field rather than expect drivers to provide an accurate - # map filtered by write errors. - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany continue-on-error behavior with unordered (duplicate key in requests)" - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: false } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 2 - # Since the map of insertedIds is generated before execution it - # could indicate inserts that did not actually succeed. We omit - # this field rather than expect drivers to provide an accurate - # map filtered by write errors. - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } diff --git a/src/test/spec/json/crud/v1/write/insertOne.json b/src/test/spec/json/crud/v1/write/insertOne.json deleted file mode 100644 index 525de75e0..000000000 --- a/src/test/spec/json/crud/v1/write/insertOne.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertOne with a non-existing document", - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - "outcome": { - "result": { - "insertedId": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/insertOne.yml b/src/test/spec/json/crud/v1/write/insertOne.yml deleted file mode 100644 index 8d65499d9..000000000 --- a/src/test/spec/json/crud/v1/write/insertOne.yml +++ /dev/null @@ -1,18 +0,0 @@ -data: - - {_id: 1, x: 11} - -tests: - - - description: "InsertOne with a non-existing document" - operation: - name: "insertOne" - arguments: - document: {_id: 2, x: 22} - - outcome: - result: - insertedId: 2 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} \ No newline at end of file diff --git a/src/test/spec/json/crud/v1/write/replaceOne-collation.json b/src/test/spec/json/crud/v1/write/replaceOne-collation.json deleted file mode 100644 index fa4cbe997..000000000 --- a/src/test/spec/json/crud/v1/write/replaceOne-collation.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "ReplaceOne when one document matches with collation", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "x": "PING" - }, - "replacement": { - "_id": 2, - "x": "pong" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/replaceOne-collation.yml b/src/test/spec/json/crud/v1/write/replaceOne-collation.yml deleted file mode 100644 index cad343caa..000000000 --- a/src/test/spec/json/crud/v1/write/replaceOne-collation.yml +++ /dev/null @@ -1,25 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "ReplaceOne when one document matches with collation" - operation: - name: "replaceOne" - arguments: - filter: {x: 'PING'} - replacement: {_id: 2, x: 'pong'} - collation: {locale: 'en_US', strength: 2} # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 'pong'} diff --git a/src/test/spec/json/crud/v1/write/replaceOne.json b/src/test/spec/json/crud/v1/write/replaceOne.json deleted file mode 100644 index 101af25c7..000000000 --- a/src/test/spec/json/crud/v1/write/replaceOne.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "ReplaceOne when many documents match", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - }, - { - "description": "ReplaceOne when one document matches", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "ReplaceOne when no documents match", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 1 - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "ReplaceOne with upsert when no documents match without an id specified", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "ReplaceOne with upsert when no documents match with an id specified", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/replaceOne.yml b/src/test/spec/json/crud/v1/write/replaceOne.yml deleted file mode 100644 index 4bccd3600..000000000 --- a/src/test/spec/json/crud/v1/write/replaceOne.yml +++ /dev/null @@ -1,102 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} -minServerVersion: '2.6' - -tests: - - - description: "ReplaceOne when many documents match" - operation: - name: "replaceOne" - arguments: - filter: - _id: {$gt: 1} - replacement: {x: 111} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - # Can't verify collection data because we don't have a way of - # knowing which document gets updated. - - - description: "ReplaceOne when one document matches" - operation: - name: "replaceOne" - arguments: - filter: {_id: 1} - replacement: {_id: 1, x: 111} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 111} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "ReplaceOne when no documents match" - operation: - name: "replaceOne" - arguments: - filter: {_id: 4} - replacement: {_id: 4, x: 1} - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "ReplaceOne with upsert when no documents match without an id specified" - operation: - name: "replaceOne" - arguments: - filter: {_id: 4} - replacement: {x: 1} - upsert: true - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} - - - - description: "ReplaceOne with upsert when no documents match with an id specified" - operation: - name: "replaceOne" - arguments: - filter: {_id: 4} - replacement: {_id: 4, x: 1} - upsert: true - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} diff --git a/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.json b/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.json deleted file mode 100644 index ae4c123ea..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "UpdateMany when no documents match arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "UpdateMany when one document matches arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "UpdateMany when multiple documents match arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 2 - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.yml b/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.yml deleted file mode 100644 index 3cfee5692..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany-arrayFilters.yml +++ /dev/null @@ -1,66 +0,0 @@ -data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} -minServerVersion: '3.5.6' - -tests: - - - description: "UpdateMany when no documents match arrayFilters" - operation: - name: "updateMany" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 4} - - outcome: - result: - matchedCount: 2 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - - description: "UpdateMany when one document matches arrayFilters" - operation: - name: "updateMany" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 3} - - outcome: - result: - matchedCount: 2 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 2}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - - description: "UpdateMany when multiple documents match arrayFilters" - operation: - name: "updateMany" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 1} - - outcome: - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 2}]} - - {_id: 2, y: [{b: 0}, {b: 2}]} diff --git a/src/test/spec/json/crud/v1/write/updateMany-collation.json b/src/test/spec/json/crud/v1/write/updateMany-collation.json deleted file mode 100644 index 8becfd806..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany-collation.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "UpdateMany when many documents match with collation", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - }, - { - "_id": 3, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateMany-collation.yml b/src/test/spec/json/crud/v1/write/updateMany-collation.yml deleted file mode 100644 index b37378964..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany-collation.yml +++ /dev/null @@ -1,29 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} - - {_id: 3, x: 'pINg'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "UpdateMany when many documents match with collation" - operation: - name: "updateMany" - arguments: - filter: - x: 'ping' - update: - $set: {x: 'pong'} - collation: { locale: 'en_US', strength: 2 } # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 'pong'} - - {_id: 3, x: 'pong'} diff --git a/src/test/spec/json/crud/v1/write/updateMany.json b/src/test/spec/json/crud/v1/write/updateMany.json deleted file mode 100644 index a3c339987..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "UpdateMany when many documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 34 - } - ] - } - } - }, - { - "description": "UpdateMany when one document matches", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateMany when no documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateMany with upsert when no documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateMany.yml b/src/test/spec/json/crud/v1/write/updateMany.yml deleted file mode 100644 index a0eb62ba8..000000000 --- a/src/test/spec/json/crud/v1/write/updateMany.yml +++ /dev/null @@ -1,87 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} -minServerVersion: '2.6' - -tests: - - - description: "UpdateMany when many documents match" - operation: - name: "updateMany" - arguments: - filter: - _id: {$gt: 1} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 23} - - {_id: 3, x: 34} - - - description: "UpdateMany when one document matches" - operation: - name: "updateMany" - arguments: - filter: {_id: 1} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 12} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "UpdateMany when no documents match" - operation: - name: "updateMany" - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "UpdateMany with upsert when no documents match" - operation: - name: "updateMany" - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - upsert: true - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} diff --git a/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.json b/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.json deleted file mode 100644 index 087ed4b82..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "UpdateOne when no document matches arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when one document matches arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when multiple documents match arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when no documents match multiple arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "y.$[i].c.$[j].d": 0 - } - }, - "arrayFilters": [ - { - "i.b": 5 - }, - { - "j.d": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when one document matches multiple arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "y.$[i].c.$[j].d": 0 - } - }, - "arrayFilters": [ - { - "i.b": 5 - }, - { - "j.d": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 0 - } - ] - } - ] - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.yml b/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.yml deleted file mode 100644 index a35e15c68..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne-arrayFilters.yml +++ /dev/null @@ -1,114 +0,0 @@ -data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 1}] }]} -minServerVersion: '3.5.6' - -tests: - - - description: "UpdateOne when no document matches arrayFilters" - operation: - name: "updateOne" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 4} - - outcome: - result: - matchedCount: 1 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 1}] }]} - - - description: "UpdateOne when one document matches arrayFilters" - operation: - name: "updateOne" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 3} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 2}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 1}] }]} - - - description: "UpdateOne when multiple documents match arrayFilters" - operation: - name: "updateOne" - arguments: - filter: {} - update: - $set: {"y.$[i].b": 2} - arrayFilters: - - {i.b: 1} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 2}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 1}] }]} - - - description: "UpdateOne when no documents match multiple arrayFilters" - operation: - name: "updateOne" - arguments: - filter: {_id: 3} - update: - $set: {"y.$[i].c.$[j].d": 0} - arrayFilters: - - {i.b: 5} - - {j.d: 3} - - outcome: - result: - matchedCount: 1 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 1}] }]} - - - description: "UpdateOne when one document matches multiple arrayFilters" - operation: - name: "updateOne" - arguments: - filter: {_id: 3} - update: - $set: {"y.$[i].c.$[j].d": 0} - arrayFilters: - - {i.b: 5} - - {j.d: 1} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, y: [{b: 3}, {b: 1}]} - - {_id: 2, y: [{b: 0}, {b: 1}]} - - {_id: 3, y: [{b: 5, c: [{d: 2}, {d: 0}] }]} diff --git a/src/test/spec/json/crud/v1/write/updateOne-collation.json b/src/test/spec/json/crud/v1/write/updateOne-collation.json deleted file mode 100644 index 3afdb83e0..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne-collation.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "serverless": "forbid", - "tests": [ - { - "description": "UpdateOne when one document matches with collation", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "x": "PING" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateOne-collation.yml b/src/test/spec/json/crud/v1/write/updateOne-collation.yml deleted file mode 100644 index c20087176..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne-collation.yml +++ /dev/null @@ -1,26 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 'ping'} -minServerVersion: '3.4' -serverless: 'forbid' - -tests: - - - description: "UpdateOne when one document matches with collation" - operation: - name: "updateOne" - arguments: - filter: {x: 'PING'} - update: - $set: {x: 'pong'} - collation: { locale: 'en_US', strength: 2} # https://www.mongodb.com/docs/manual/reference/collation/#collation-document - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 'pong'} diff --git a/src/test/spec/json/crud/v1/write/updateOne.json b/src/test/spec/json/crud/v1/write/updateOne.json deleted file mode 100644 index 76d2086bd..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne.json +++ /dev/null @@ -1,167 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "UpdateOne when many documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - }, - { - "description": "UpdateOne when one document matches", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateOne when no documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateOne with upsert when no documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/crud/v1/write/updateOne.yml b/src/test/spec/json/crud/v1/write/updateOne.yml deleted file mode 100644 index 223532fea..000000000 --- a/src/test/spec/json/crud/v1/write/updateOne.yml +++ /dev/null @@ -1,84 +0,0 @@ -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} -minServerVersion: '2.6' - -tests: - - - description: "UpdateOne when many documents match" - operation: - name: "updateOne" - arguments: - filter: - _id: {$gt: 1} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - # Can't verify collection data because we don't have a way of - # knowing which document gets updated. - - - description: "UpdateOne when one document matches" - operation: - name: "updateOne" - arguments: - filter: {_id: 1} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 12} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "UpdateOne when no documents match" - operation: - name: "updateOne" - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - - description: "UpdateOne with upsert when no documents match" - operation: - name: "updateOne" - arguments: - filter: {_id: 4} - update: - $inc: {x: 1} - upsert: true - - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - collection: - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 1} diff --git a/src/test/spec/json/gridfs/README.md b/src/test/spec/json/gridfs/README.md new file mode 100644 index 000000000..64600a9a2 --- /dev/null +++ b/src/test/spec/json/gridfs/README.md @@ -0,0 +1,28 @@ +# GridFS Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +GridFS. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +## Conventions for Expressing Binary Data + +The unified test format allows binary stream data to be expressed and matched with `$$hexBytes` (for uploads) and +`$$matchesHexBytes` (for downloads), respectively; however, those operators are not supported in all contexts, such as +`insertData` and `outcome`. When binary data must be expressed as a base64-encoded string +([Extended JSON](../../extended-json/extended-json.md) for a BSON binary type), the test SHOULD include a comment noting +the equivalent value in hexadecimal for human-readability. For example: + +```yaml +data: { $binary: { base64: "ESIzRA==", subType: "00" } } # hex 11223344 +``` + +Creating the base64-encoded string for a sequence of hexadecimal bytes is left as an exercise to the developer. Consider +the following PHP one-liner: + +```shell-session +$ php -r 'echo base64_encode(hex2bin('11223344')), "\n";' +ESIzRA== +``` diff --git a/src/test/spec/json/gridfs/README.rst b/src/test/spec/json/gridfs/README.rst deleted file mode 100644 index 9eb0f3482..000000000 --- a/src/test/spec/json/gridfs/README.rst +++ /dev/null @@ -1,37 +0,0 @@ -============ -GridFS Tests -============ - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests -meant to exercise a driver's implementation of GridFS. These tests utilize the -`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__. - -Conventions for Expressing Binary Data -====================================== - -The unified test format allows binary stream data to be expressed and matched -with ``$$hexBytes`` (for uploads) and ``$$matchesHexBytes`` (for downloads), -respectively; however, those operators are not supported in all contexts, such -as ``insertData`` and ``outcome``. When binary data must be expressed as a -base64-encoded string (`Extended JSON <../../extended-json.rst>`__ for a BSON -binary type), the test SHOULD include a comment noting the equivalent value in -hexadecimal for human-readability. For example: - -.. code:: yaml - - data: { $binary: { base64: "ESIzRA==", subType: "00" } } # hex 11223344 - -Creating the base64-encoded string for a sequence of hexadecimal bytes is left -as an exercise to the developer. Consider the following PHP one-liner: - -.. code:: shell-session - - $ php -r 'echo base64_encode(hex2bin('11223344')), "\n";' - ESIzRA== diff --git a/src/test/spec/json/gridfs/delete.json b/src/test/spec/json/gridfs/delete.json index 7a4ec27f8..9a9b22fc1 100644 --- a/src/test/spec/json/gridfs/delete.json +++ b/src/test/spec/json/gridfs/delete.json @@ -49,10 +49,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -64,10 +61,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -79,10 +73,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -94,10 +85,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "dd254cdc958e53abaa67da9f797125f5", "filename": "length-8", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -197,10 +185,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -212,10 +197,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -227,10 +209,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "dd254cdc958e53abaa67da9f797125f5", "filename": "length-8", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -330,10 +309,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -345,10 +321,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -360,10 +333,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "dd254cdc958e53abaa67da9f797125f5", "filename": "length-8", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -448,10 +418,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -463,10 +430,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -478,10 +442,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -536,7 +497,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ], @@ -554,10 +515,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -569,10 +527,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -584,10 +539,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -599,10 +551,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "dd254cdc958e53abaa67da9f797125f5", "filename": "length-8", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -701,7 +650,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ], @@ -719,10 +668,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -734,10 +680,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -749,10 +692,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] diff --git a/src/test/spec/json/gridfs/delete.yml b/src/test/spec/json/gridfs/delete.yml index b300cad1b..70593ac27 100644 --- a/src/test/spec/json/gridfs/delete.yml +++ b/src/test/spec/json/gridfs/delete.yml @@ -30,40 +30,28 @@ initialData: length: 0 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "d41d8cd98f00b204e9800998ecf8427e" filename: "length-0" - contentType: "application/octet-stream" - aliases: [] metadata: {} - &file2 _id: { "$oid": "000000000000000000000002" } length: 0 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "d41d8cd98f00b204e9800998ecf8427e" filename: "length-0-with-empty-chunk" - contentType: "application/octet-stream" - aliases: [] metadata: {} - &file3 _id: { "$oid": "000000000000000000000003" } length: 2 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "c700ed4fdb1d27055aa3faa2c2432283" filename: "length-2" - contentType: "application/octet-stream" - aliases: [] metadata: {} - &file4 _id: { "$oid": "000000000000000000000004" } length: 8 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "dd254cdc958e53abaa67da9f797125f5" filename: "length-8" - contentType: "application/octet-stream" - aliases: [] metadata: {} - collectionName: *bucket0_chunks_collectionName databaseName: *database0Name @@ -153,7 +141,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000000" } - expectError: { isError: true } # FileNotFound + expectError: { isClientError: true } # FileNotFound outcome: - collectionName: *bucket0_files_collectionName databaseName: *database0Name @@ -182,7 +170,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000004" } - expectError: { isError: true } # FileNotFound + expectError: { isClientError: true } # FileNotFound outcome: - collectionName: *bucket0_files_collectionName databaseName: *database0Name diff --git a/src/test/spec/json/gridfs/deleteByName.json b/src/test/spec/json/gridfs/deleteByName.json new file mode 100644 index 000000000..884d0300c --- /dev/null +++ b/src/test/spec/json/gridfs/deleteByName.json @@ -0,0 +1,230 @@ +{ + "description": "gridfs-deleteByName", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "gridfs-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + }, + { + "collection": { + "id": "bucket0_files_collection", + "database": "database0", + "collectionName": "fs.files" + } + }, + { + "collection": { + "id": "bucket0_chunks_collection", + "database": "database0", + "collectionName": "fs.chunks" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "length": 8, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "otherfilename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "files_id": { + "$oid": "000000000000000000000002" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000003" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "files_id": { + "$oid": "000000000000000000000003" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "delete when multiple revisions of the file exist", + "operations": [ + { + "name": "deleteByName", + "object": "bucket0", + "arguments": { + "filename": "filename" + } + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000004" + }, + "length": 8, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "otherfilename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000004" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ] + }, + { + "description": "delete when file name does not exist", + "operations": [ + { + "name": "deleteByName", + "object": "bucket0", + "arguments": { + "filename": "missing-file" + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/src/test/spec/json/gridfs/deleteByName.yml b/src/test/spec/json/gridfs/deleteByName.yml new file mode 100644 index 000000000..b63b15633 --- /dev/null +++ b/src/test/spec/json/gridfs/deleteByName.yml @@ -0,0 +1,102 @@ +description: "gridfs-deleteByName" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name gridfs-tests + - bucket: + id: &bucket0 bucket0 + database: *database0 + - collection: + id: &bucket0_files_collection bucket0_files_collection + database: *database0 + collectionName: &bucket0_files_collectionName fs.files + - collection: + id: &bucket0_chunks_collection bucket0_chunks_collection + database: *database0 + collectionName: &bucket0_chunks_collectionName fs.chunks + +initialData: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - &file1 + _id: { "$oid": "000000000000000000000001" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file2 + _id: { "$oid": "000000000000000000000002" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file3 + _id: { "$oid": "000000000000000000000003" } + length: 2 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file4 + _id: { "$oid": "000000000000000000000004" } + length: 8 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "otherfilename" + metadata: {} + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - &file2_chunk0 + _id: { "$oid": "000000000000000000000001" } + files_id: { "$oid": "000000000000000000000002" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file3_chunk0 + _id: { "$oid": "000000000000000000000002" } + files_id: { "$oid": "000000000000000000000003" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file3_chunk1 + _id: { "$oid": "000000000000000000000003" } + files_id: { "$oid": "000000000000000000000003" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file4_chunk0 + _id: { "$oid": "000000000000000000000004" } + files_id: { "$oid": "000000000000000000000004" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + +tests: + - description: "delete when multiple revisions of the file exist" + operations: + - name: deleteByName + object: *bucket0 + arguments: + filename: filename + outcome: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - <<: *file4 + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - *file4_chunk0 + - description: "delete when file name does not exist" + operations: + - name: deleteByName + object: *bucket0 + arguments: + filename: missing-file + expectError: { isClientError: true } # FileNotFound diff --git a/src/test/spec/json/gridfs/download.json b/src/test/spec/json/gridfs/download.json index 48d324621..67658ac51 100644 --- a/src/test/spec/json/gridfs/download.json +++ b/src/test/spec/json/gridfs/download.json @@ -49,10 +49,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -64,10 +61,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "d41d8cd98f00b204e9800998ecf8427e", "filename": "length-0-with-empty-chunk", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -79,10 +73,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", "filename": "length-2", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -94,10 +85,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "dd254cdc958e53abaa67da9f797125f5", "filename": "length-8", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -109,10 +97,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "57d83cd477bfb1ccd975ab33d827a92b", "filename": "length-10", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -124,9 +109,6 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "c700ed4fdb1d27055aa3faa2c2432283", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -356,7 +338,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -388,7 +370,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -420,7 +402,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -489,7 +471,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -532,7 +514,7 @@ } }, "expectError": { - "isError": true + "isClientError": true } } ] diff --git a/src/test/spec/json/gridfs/download.yml b/src/test/spec/json/gridfs/download.yml index 3da5ee950..b9b36a5f4 100644 --- a/src/test/spec/json/gridfs/download.yml +++ b/src/test/spec/json/gridfs/download.yml @@ -29,55 +29,37 @@ initialData: length: 0 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "d41d8cd98f00b204e9800998ecf8427e" filename: "length-0" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { "$oid": "000000000000000000000002" } length: 0 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "d41d8cd98f00b204e9800998ecf8427e" filename: "length-0-with-empty-chunk" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { "$oid": "000000000000000000000003" } length: 2 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "c700ed4fdb1d27055aa3faa2c2432283" filename: "length-2" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { "$oid": "000000000000000000000004" } length: 8 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "dd254cdc958e53abaa67da9f797125f5" filename: "length-8" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { "$oid": "000000000000000000000005" } length: 10 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "57d83cd477bfb1ccd975ab33d827a92b" filename: "length-10" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { "$oid": "000000000000000000000006" } length: 2 chunkSize: 4 uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } - md5: "c700ed4fdb1d27055aa3faa2c2432283" # filename is intentionally omitted - contentType: "application/octet-stream" - aliases: [] metadata: {} - collectionName: *bucket0_chunks_collectionName databaseName: *database0Name @@ -157,7 +139,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000000" } - expectError: { isError: true } # FileNotFound + expectError: { isClientError: true } # FileNotFound - description: "download when an intermediate chunk is missing" operations: - name: deleteOne @@ -172,7 +154,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000005" } - expectError: { isError: true } # ChunkIsMissing + expectError: { isClientError: true } # ChunkIsMissing - description: "download when final chunk is missing" operations: - name: deleteOne @@ -187,7 +169,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000005" } - expectError: { isError: true } # ChunkIsMissing + expectError: { isClientError: true } # ChunkIsMissing - description: "download when an intermediate chunk is the wrong size" operations: - name: bulkWrite @@ -213,7 +195,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000005" } - expectError: { isError: true } # ChunkIsWrongSize + expectError: { isClientError: true } # ChunkIsWrongSize - description: "download when final chunk is the wrong size" operations: - name: updateOne @@ -231,7 +213,7 @@ tests: object: *bucket0 arguments: id: { $oid: "000000000000000000000005" } - expectError: { isError: true } # ChunkIsWrongSize + expectError: { isClientError: true } # ChunkIsWrongSize - description: "download legacy file with no name" operations: - name: download diff --git a/src/test/spec/json/gridfs/downloadByName.json b/src/test/spec/json/gridfs/downloadByName.json index cd4466395..45abaf7b4 100644 --- a/src/test/spec/json/gridfs/downloadByName.json +++ b/src/test/spec/json/gridfs/downloadByName.json @@ -49,10 +49,7 @@ "uploadDate": { "$date": "1970-01-01T00:00:00.000Z" }, - "md5": "47ed733b8d10be225eceba344d533586", "filename": "abc", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -64,10 +61,7 @@ "uploadDate": { "$date": "1970-01-02T00:00:00.000Z" }, - "md5": "b15835f133ff2e27c7cb28117bfae8f4", "filename": "abc", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -79,10 +73,7 @@ "uploadDate": { "$date": "1970-01-03T00:00:00.000Z" }, - "md5": "eccbc87e4b5ce2fe28308fd9f2a7baf3", "filename": "abc", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -94,10 +85,7 @@ "uploadDate": { "$date": "1970-01-04T00:00:00.000Z" }, - "md5": "f623e75af30e62bbd73d6df5b50bb7b5", "filename": "abc", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} }, { @@ -109,10 +97,7 @@ "uploadDate": { "$date": "1970-01-05T00:00:00.000Z" }, - "md5": "4c614360da93c0a041b22e537de151eb", "filename": "abc", - "contentType": "application/octet-stream", - "aliases": [], "metadata": {} } ] @@ -305,7 +290,7 @@ "filename": "xyz" }, "expectError": { - "isError": true + "isClientError": true } } ] @@ -321,7 +306,7 @@ "revision": 999 }, "expectError": { - "isError": true + "isClientError": true } } ] diff --git a/src/test/spec/json/gridfs/downloadByName.yml b/src/test/spec/json/gridfs/downloadByName.yml index 6dfc602b6..26d1301a6 100644 --- a/src/test/spec/json/gridfs/downloadByName.yml +++ b/src/test/spec/json/gridfs/downloadByName.yml @@ -29,46 +29,31 @@ initialData: length: 1 chunkSize: 4 uploadDate: { $date: "1970-01-01T00:00:00.000Z" } - md5: "47ed733b8d10be225eceba344d533586" filename: "abc" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { $oid: "000000000000000000000002" } length: 1 chunkSize: 4 uploadDate: { $date: "1970-01-02T00:00:00.000Z" } - md5: "b15835f133ff2e27c7cb28117bfae8f4" filename: "abc" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { $oid: "000000000000000000000003" } length: 1 chunkSize: 4 uploadDate: { $date: "1970-01-03T00:00:00.000Z" } - md5: "eccbc87e4b5ce2fe28308fd9f2a7baf3" filename: "abc" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { $oid: "000000000000000000000004" } length: 1 chunkSize: 4 uploadDate: { $date: "1970-01-04T00:00:00.000Z" } - md5: "f623e75af30e62bbd73d6df5b50bb7b5" filename: "abc" - contentType: "application/octet-stream" - aliases: [] metadata: {} - _id: { $oid: "000000000000000000000005" } length: 1 chunkSize: 4 uploadDate: { $date: "1970-01-05T00:00:00.000Z" } - md5: "4c614360da93c0a041b22e537de151eb" filename: "abc" - contentType: "application/octet-stream" - aliases: [] metadata: {} - collectionName: *bucket0_chunks_collectionName databaseName: *database0Name @@ -148,7 +133,7 @@ tests: object: *bucket0 arguments: filename: "xyz" - expectError: { isError: true } # FileNotFound + expectError: { isClientError: true } # FileNotFound - description: "downloadByName when revision does not exist" operations: - name: downloadByName @@ -156,4 +141,4 @@ tests: arguments: filename: "abc" revision: 999 - expectError: { isError: true } # RevisionNotFound + expectError: { isClientError: true } # RevisionNotFound diff --git a/src/test/spec/json/gridfs/rename.json b/src/test/spec/json/gridfs/rename.json new file mode 100644 index 000000000..08064d4a5 --- /dev/null +++ b/src/test/spec/json/gridfs/rename.json @@ -0,0 +1,179 @@ +{ + "description": "gridfs-rename", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "gridfs-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + }, + { + "collection": { + "id": "bucket0_files_collection", + "database": "database0", + "collectionName": "fs.files" + } + }, + { + "collection": { + "id": "bucket0_chunks_collection", + "database": "database0", + "collectionName": "fs.chunks" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "files_id": { + "$oid": "000000000000000000000002" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "rename by id", + "operations": [ + { + "name": "rename", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + }, + "newFilename": "newfilename" + } + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "newfilename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "files_id": { + "$oid": "000000000000000000000002" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ] + }, + { + "description": "rename when file id does not exist", + "operations": [ + { + "name": "rename", + "object": "bucket0", + "arguments": { + "id": { + "$oid": "000000000000000000000003" + }, + "newFilename": "newfilename" + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/src/test/spec/json/gridfs/rename.yml b/src/test/spec/json/gridfs/rename.yml new file mode 100644 index 000000000..220353299 --- /dev/null +++ b/src/test/spec/json/gridfs/rename.yml @@ -0,0 +1,78 @@ +description: "gridfs-rename" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name gridfs-tests + - bucket: + id: &bucket0 bucket0 + database: *database0 + - collection: + id: &bucket0_files_collection bucket0_files_collection + database: *database0 + collectionName: &bucket0_files_collectionName fs.files + - collection: + id: &bucket0_chunks_collection bucket0_chunks_collection + database: *database0 + collectionName: &bucket0_chunks_collectionName fs.chunks + +initialData: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - &file1 + _id: { "$oid": "000000000000000000000001" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file2 + _id: { "$oid": "000000000000000000000002" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - &file2_chunk0 + _id: { "$oid": "000000000000000000000001" } + files_id: { "$oid": "000000000000000000000002" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + +tests: + - description: "rename by id" + operations: + - name: rename + object: *bucket0 + arguments: + id: { "$oid": "000000000000000000000001" } + newFilename: newfilename + outcome: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - <<: *file1 + filename: newfilename + - <<: *file2 + filename: filename + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - *file2_chunk0 + - description: "rename when file id does not exist" + operations: + - name: rename + object: *bucket0 + arguments: + id: { "$oid": "000000000000000000000003" } + newFilename: newfilename + expectError: { isClientError: true } # FileNotFound diff --git a/src/test/spec/json/gridfs/renameByName.json b/src/test/spec/json/gridfs/renameByName.json new file mode 100644 index 000000000..26f04fb9e --- /dev/null +++ b/src/test/spec/json/gridfs/renameByName.json @@ -0,0 +1,313 @@ +{ + "description": "gridfs-renameByName", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "gridfs-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + }, + { + "collection": { + "id": "bucket0_files_collection", + "database": "database0", + "collectionName": "fs.files" + } + }, + { + "collection": { + "id": "bucket0_chunks_collection", + "database": "database0", + "collectionName": "fs.chunks" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "filename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "length": 8, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "otherfilename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "files_id": { + "$oid": "000000000000000000000002" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000003" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 1, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "rename when multiple revisions of the file exist", + "operations": [ + { + "name": "renameByName", + "object": "bucket0", + "arguments": { + "filename": "filename", + "newFilename": "newfilename" + } + } + ], + "outcome": [ + { + "collectionName": "fs.files", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "newfilename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "length": 0, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "newfilename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "length": 2, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "newfilename", + "metadata": {} + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "length": 8, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "otherfilename", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "gridfs-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "files_id": { + "$oid": "000000000000000000000002" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000003" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000003" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 0, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + }, + { + "_id": { + "$oid": "000000000000000000000004" + }, + "files_id": { + "$oid": "000000000000000000000004" + }, + "n": 1, + "data": { + "$binary": { + "base64": "", + "subType": "00" + } + } + } + ] + } + ] + }, + { + "description": "rename when file name does not exist", + "operations": [ + { + "name": "renameByName", + "object": "bucket0", + "arguments": { + "filename": "missing-file", + "newFilename": "newfilename" + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/src/test/spec/json/gridfs/renameByName.yml b/src/test/spec/json/gridfs/renameByName.yml new file mode 100644 index 000000000..f628ca9c5 --- /dev/null +++ b/src/test/spec/json/gridfs/renameByName.yml @@ -0,0 +1,113 @@ +description: "gridfs-renameByName" + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name gridfs-tests + - bucket: + id: &bucket0 bucket0 + database: *database0 + - collection: + id: &bucket0_files_collection bucket0_files_collection + database: *database0 + collectionName: &bucket0_files_collectionName fs.files + - collection: + id: &bucket0_chunks_collection bucket0_chunks_collection + database: *database0 + collectionName: &bucket0_chunks_collectionName fs.chunks + +initialData: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - &file1 + _id: { "$oid": "000000000000000000000001" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file2 + _id: { "$oid": "000000000000000000000002" } + length: 0 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file3 + _id: { "$oid": "000000000000000000000003" } + length: 2 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "filename" + metadata: {} + - &file4 + _id: { "$oid": "000000000000000000000004" } + length: 8 + chunkSize: 4 + uploadDate: { "$date": "1970-01-01T00:00:00.000Z" } + filename: "otherfilename" + metadata: {} + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - &file2_chunk0 + _id: { "$oid": "000000000000000000000001" } + files_id: { "$oid": "000000000000000000000002" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file3_chunk0 + _id: { "$oid": "000000000000000000000002" } + files_id: { "$oid": "000000000000000000000003" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file4_chunk0 + _id: { "$oid": "000000000000000000000003" } + files_id: { "$oid": "000000000000000000000004" } + n: 0 + data: { "$binary": { "base64": "", "subType": "00" } } + - &file4_chunk1 + _id: { "$oid": "000000000000000000000004" } + files_id: { "$oid": "000000000000000000000004" } + n: 1 + data: { "$binary": { "base64": "", "subType": "00" } } + +tests: + - description: "rename when multiple revisions of the file exist" + operations: + - name: renameByName + object: *bucket0 + arguments: + filename: filename + newFilename: newfilename + outcome: + - collectionName: *bucket0_files_collectionName + databaseName: *database0Name + documents: + - <<: *file1 + filename: newfilename + - <<: *file2 + filename: newfilename + - <<: *file3 + filename: newfilename + - <<: *file4 + - collectionName: *bucket0_chunks_collectionName + databaseName: *database0Name + documents: + - *file2_chunk0 + - *file3_chunk0 + - *file4_chunk0 + - *file4_chunk1 + - description: "rename when file name does not exist" + operations: + - name: renameByName + object: *bucket0 + arguments: + filename: missing-file + newFilename: newfilename + expectError: { isClientError: true } # FileNotFound diff --git a/src/test/spec/json/gridfs/upload.json b/src/test/spec/json/gridfs/upload.json index 97e18d2bc..3c1644653 100644 --- a/src/test/spec/json/gridfs/upload.json +++ b/src/test/spec/json/gridfs/upload.json @@ -470,75 +470,6 @@ } ] }, - { - "description": "upload when contentType is provided", - "operations": [ - { - "name": "upload", - "object": "bucket0", - "arguments": { - "filename": "filename", - "source": { - "$$hexBytes": "11" - }, - "chunkSizeBytes": 4, - "contentType": "image/jpeg" - }, - "expectResult": { - "$$type": "objectId" - }, - "saveResultAsEntity": "uploadedObjectId" - }, - { - "name": "find", - "object": "bucket0_files_collection", - "arguments": { - "filter": {} - }, - "expectResult": [ - { - "_id": { - "$$matchesEntity": "uploadedObjectId" - }, - "length": 1, - "chunkSize": 4, - "uploadDate": { - "$$type": "date" - }, - "md5": { - "$$unsetOrMatches": "47ed733b8d10be225eceba344d533586" - }, - "filename": "filename", - "contentType": "image/jpeg" - } - ] - }, - { - "name": "find", - "object": "bucket0_chunks_collection", - "arguments": { - "filter": {} - }, - "expectResult": [ - { - "_id": { - "$$type": "objectId" - }, - "files_id": { - "$$matchesEntity": "uploadedObjectId" - }, - "n": 0, - "data": { - "$binary": { - "base64": "EQ==", - "subType": "00" - } - } - } - ] - } - ] - }, { "description": "upload when metadata is provided", "operations": [ diff --git a/src/test/spec/json/gridfs/upload.yml b/src/test/spec/json/gridfs/upload.yml index 27f3186fc..96fc5e986 100644 --- a/src/test/spec/json/gridfs/upload.yml +++ b/src/test/spec/json/gridfs/upload.yml @@ -222,38 +222,6 @@ tests: files_id: { $$matchesEntity: *uploadedObjectId } n: 1 data: { $binary: { base64: "VWZ3iA==", subType: "00" } } # hex 55667788 - - description: "upload when contentType is provided" - operations: - - name: upload - object: *bucket0 - arguments: - filename: "filename" - source: { $$hexBytes: "11" } - chunkSizeBytes: 4 - contentType: "image/jpeg" - expectResult: { $$type: objectId } - saveResultAsEntity: *uploadedObjectId - - name: find - object: *bucket0_files_collection - arguments: - filter: {} - expectResult: - - _id: { $$matchesEntity: *uploadedObjectId } - length: 1 - chunkSize: 4 - uploadDate: { $$type: date } - md5: { $$unsetOrMatches: "47ed733b8d10be225eceba344d533586" } - filename: filename - contentType: "image/jpeg" - - name: find - object: *bucket0_chunks_collection - arguments: - filter: {} - expectResult: - - _id: { $$type: objectId } - files_id: { $$matchesEntity: *uploadedObjectId } - n: 0 - data: { $binary: { base64: "EQ==", subType: "00" } } # hex 11 - description: "upload when metadata is provided" operations: - name: upload diff --git a/src/test/spec/json/index-management/README.md b/src/test/spec/json/index-management/README.md new file mode 100644 index 000000000..2ca3c759c --- /dev/null +++ b/src/test/spec/json/index-management/README.md @@ -0,0 +1,338 @@ +# Index Management Tests + +______________________________________________________________________ + +## Test Plan + +These prose tests are ported from the legacy enumerate-indexes spec. + +### Configurations + +- standalone node +- replica set primary node +- replica set secondary node +- mongos node + +### Preparation + +For each of the configurations: + +- Create a (new) database +- Create a collection +- Create a single column index, a compound index, and a unique index +- Insert at least one document containing all the fields that the above indicated indexes act on + +### Tests + +- Run the driver's method that returns a list of index names, and: + - verify that *all* index names are represented in the result + - verify that there are no duplicate index names + - verify there are no returned indexes that do not exist +- Run the driver's method that returns a list of index information records, and: + - verify all the indexes are represented in the result + - verify the "unique" flags show up for the unique index + - verify there are no duplicates in the returned list + - if the result consists of statically defined index models that include an `ns` field, verify that its value is + accurate + +### Search Index Management Helpers + +These tests are intended to smoke test the search management helpers end-to-end against a live Atlas cluster. + +The search index management commands are asynchronous and mongod/mongos returns before the changes to a clusters' search +indexes have completed. When these prose tests specify "waiting for the changes", drivers should repeatedly poll the +cluster with `listSearchIndexes` until the changes are visible. Each test specifies the condition that is considered +"ready". For example, when creating a new search index, waiting until the inserted index has a status `queryable: true` +indicates that the index was successfully created. + +The commands tested in these prose tests take a while to successfully complete. Drivers should raise the timeout for +each test to avoid timeout errors if the test timeout is too low. 5 minutes is a sufficiently large timeout that any +timeout that occurs indicates a real failure, but this value is not required and can be tweaked per-driver. + +There is a server-side limitation that prevents multiple search indexes from being created with the same name, +definition and collection name. This limitation does not take into account collection uuid. Because these commands are +asynchronous, any cleanup code that may run after a test (cleaning a database or dropping search indexes) may not have +completed by the next iteration of the test (or the next test run, if running locally). To address this issue, each test +uses a randomly generated collection name. Drivers may generate this collection name however they like, but a suggested +implementation is a hex representation of an ObjectId (`new ObjectId().toHexString()` in Node). + +#### Setup + +These tests must run against an Atlas cluster with a 7.0+ server. +[Scripts are available](https://github.com/mongodb-labs/drivers-evergreen-tools/tree/master/.evergreen/atlas) in +drivers-evergreen-tools which can setup and teardown Atlas clusters. To ensure that the Atlas cluster is cleaned up +after each CI run, drivers should configure evergreen to run these tests as a part of a task group. Be sure that the +cluster gets torn down! + +When working locally on these tests, the same Atlas setup and teardown scripts can be used locally to provision a +cluster for development. + +#### Case 1: Driver can successfully create and list search indexes + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + { + name: 'test-search-index', + definition: { + mappings: { dynamic: false } + } + } + ``` + +3. Assert that the command returns the name of the index: `"test-search-index"`. + +4. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following condition is satisfied and store the + value in a variable `index`: + + - An index with the `name` of `test-search-index` is present and the index has a field `queryable` with a value of + `true`. + +5. Assert that `index` has a property `latestDefinition` whose value is `{ 'mappings': { 'dynamic': false } }` + +#### Case 2: Driver can successfully create multiple indexes in batch + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Create two new search indexes on `coll0` with the `createSearchIndexes` helper. Use the following definitions when + creating the indexes. These definitions are referred to as `indexDefinitions`. + + ```typescript + { + name: 'test-search-index-1', + definition: { + mappings: { dynamic: false } + } + } + + { + name: 'test-search-index-2', + definition: { + mappings: { dynamic: false } + } + } + ``` + +3. Assert that the command returns an array containing the new indexes' names: + `["test-search-index-1", "test-search-index-2"]`. + +4. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following conditions are satisfied. + + - An index with the `name` of `test-search-index-1` is present and index has a field `queryable` with the value of + `true`. Store result in `index1`. + - An index with the `name` of `test-search-index-2` is present and index has a field `queryable` with the value of + `true`. Store result in `index2`. + +5. Assert that `index1` and `index2` have the property `latestDefinition` whose value is + `{ "mappings" : { "dynamic" : false } }` + +#### Case 3: Driver can successfully drop search indexes + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Create a new search index on `coll0` with the following definition: + + ```typescript + { + name: 'test-search-index', + definition: { + mappings: { dynamic: false } + } + } + ``` + +3. Assert that the command returns the name of the index: `"test-search-index"`. + +4. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following condition is satisfied: + + - An index with the `name` of `test-search-index` is present and index has a field `queryable` with the value of + `true`. + +5. Run a `dropSearchIndex` on `coll0`, using `test-search-index` for the name. + +6. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until `listSearchIndexes` returns an empty array. + +This test fails if it times out waiting for the deletion to succeed. + +#### Case 4: Driver can update a search index + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Create a new search index on `coll0` with the following definition: + + ```typescript + { + name: 'test-search-index', + definition: { + mappings: { dynamic: false } + } + } + ``` + +3. Assert that the command returns the name of the index: `"test-search-index"`. + +4. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following condition is satisfied: + + - An index with the `name` of `test-search-index` is present and index has a field `queryable` with the value of + `true`. + +5. Run a `updateSearchIndex` on `coll0`, using the following definition. + + ```typescript + { + name: 'test-search-index', + definition: { + mappings: { dynamic: true } + } + } + ``` + +6. Assert that the command does not error and the server responds with a success. + +7. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following conditions are satisfied: + + - An index with the `name` of `test-search-index` is present. This index is referred to as `index`. + - The index has a field `queryable` with a value of `true` and has a field `status` with the value of `READY`. + +8. Assert that an index is present with the name `test-search-index` and the definition has a property + `latestDefinition` whose value is `{ 'mappings': { 'dynamic': true } }`. + +#### Case 5: `dropSearchIndex` suppresses namespace not found errors + +1. Create a driver-side collection object for a randomly generated collection name. Do not create this collection on the + server. +2. Run a `dropSearchIndex` command and assert that no error is thrown. + +#### Case 6: Driver can successfully create and list search indexes with non-default readConcern and writeConcern + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Apply a write concern `WriteConcern(w=1)` and a read concern with `ReadConcern(level="majority")` to `coll0`. + +3. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + { + name: 'test-search-index-case6', + definition: { + mappings: { dynamic: false } + } + } + ``` + +4. Assert that the command returns the name of the index: `"test-search-index-case6"`. + +5. Run `coll0.listSearchIndexes()` repeatedly every 5 seconds until the following condition is satisfied and store the + value in a variable `index`: + + - An index with the `name` of `test-search-index-case6` is present and the index has a field `queryable` with a value + of `true`. + +6. Assert that `index` has a property `latestDefinition` whose value is `{ 'mappings': { 'dynamic': false } }` + +#### Case 7: Driver can successfully handle search index types when creating indexes + +01. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +02. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + + { + name: 'test-search-index-case7-implicit', + definition: { + mappings: { dynamic: false } + } + } + ``` + +03. Assert that the command returns the name of the index: `"test-search-index-case7-implicit"`. + +04. Run `coll0.listSearchIndexes('test-search-index-case7-implicit')` repeatedly every 5 seconds until the following + condition is satisfied and store the value in a variable `index1`: + + - An index with the `name` of `test-search-index-case7-implicit` is present and the index has a field `queryable` + with a value of `true`. + +05. Assert that `index1` has a property `type` whose value is `search`. + +06. Create a new search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + + { + name: 'test-search-index-case7-explicit', + type: 'search', + definition: { + mappings: { dynamic: false } + } + } + ``` + +07. Assert that the command returns the name of the index: `"test-search-index-case7-explicit"`. + +08. Run `coll0.listSearchIndexes('test-search-index-case7-explicit')` repeatedly every 5 seconds until the following + condition is satisfied and store the value in a variable `index2`: + + - An index with the `name` of `test-search-index-case7-explicit` is present and the index has a field `queryable` + with a value of `true`. + +09. Assert that `index2` has a property `type` whose value is `search`. + +10. Create a new vector search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + + { + name: 'test-search-index-case7-vector', + type: 'vectorSearch', + definition: { + fields: [ + { + type: 'vector', + path: 'plot_embedding', + numDimensions: 1536, + similarity: 'euclidean', + }, + ] + } + } + ``` + +11. Assert that the command returns the name of the index: `"test-search-index-case7-vector"`. + +12. Run `coll0.listSearchIndexes('test-search-index-case7-vector')` repeatedly every 5 seconds until the following + condition is satisfied and store the value in a variable `index3`: + + - An index with the `name` of `test-search-index-case7-vector` is present and the index has a field `queryable` with + a value of `true`. + +13. Assert that `index3` has a property `type` whose value is `vectorSearch`. + +#### Case 8: Driver requires explicit type to create a vector search index + +1. Create a collection with the "create" command using a randomly generated name (referred to as `coll0`). + +2. Create a new vector search index on `coll0` with the `createSearchIndex` helper. Use the following definition: + + ```typescript + + { + name: 'test-search-index-case8-error', + definition: { + fields: [ + { + type: 'vector', + path: 'plot_embedding', + numDimensions: 1536, + similarity: 'euclidean', + }, + ] + } + } + ``` + +3. Assert that the command throws an exception containing the string "Attribute mappings missing" due to the `mappings` + field missing. diff --git a/src/test/spec/json/index-management/createSearchIndex.json b/src/test/spec/json/index-management/createSearchIndex.json new file mode 100644 index 000000000..327cb6125 --- /dev/null +++ b/src/test/spec/json/index-management/createSearchIndex.json @@ -0,0 +1,200 @@ +{ + "description": "createSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "no name provided for an index definition", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "name provided for an index definition", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index", + "type": "search" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index", + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "create a vector search index", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/createSearchIndex.yml b/src/test/spec/json/index-management/createSearchIndex.yml new file mode 100644 index 000000000..a32546cac --- /dev/null +++ b/src/test/spec/json/index-management/createSearchIndex.yml @@ -0,0 +1,86 @@ +description: "createSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "no name provided for an index definition" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { mappings: { dynamic: true } } , type: 'search' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, type: 'search'} ] + $db: *database0 + + - description: "name provided for an index definition" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { mappings: { dynamic: true } } , name: 'test index', type: 'search' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'search' } ] + $db: *database0 + + - description: "create a vector search index" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { fields: [ {"type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "euclidean"} ] } + , name: 'test index', type: 'vectorSearch' } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'vectorSearch' } ] + $db: *database0 diff --git a/src/test/spec/json/index-management/createSearchIndexes.json b/src/test/spec/json/index-management/createSearchIndexes.json new file mode 100644 index 000000000..d91d7d9cf --- /dev/null +++ b/src/test/spec/json/index-management/createSearchIndexes.json @@ -0,0 +1,238 @@ +{ + "description": "createSearchIndexes", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "empty index definition array", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "no name provided for an index definition", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "name provided for an index definition", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index", + "type": "search" + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + }, + "name": "test index", + "type": "search" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "create a vector search index", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + ] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "fields": [ + { + "type": "vector", + "path": "plot_embedding", + "numDimensions": 1536, + "similarity": "euclidean" + } + ] + }, + "name": "test index", + "type": "vectorSearch" + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/createSearchIndexes.yml b/src/test/spec/json/index-management/createSearchIndexes.yml new file mode 100644 index 000000000..cac442cb8 --- /dev/null +++ b/src/test/spec/json/index-management/createSearchIndexes.yml @@ -0,0 +1,108 @@ +description: "createSearchIndexes" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "empty index definition array" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [] + $db: *database0 + + + - description: "no name provided for an index definition" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { mappings: { dynamic: true } } , type: 'search' } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, type: 'search'} ] + $db: *database0 + + - description: "name provided for an index definition" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { mappings: { dynamic: true } } , name: 'test index' , type: 'search' } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'search' } ] + $db: *database0 + + - description: "create a vector search index" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [ { definition: &definition { fields: [ {"type": "vector", "path": "plot_embedding", "numDimensions": 1536, "similarity": "euclidean"} ] }, + name: 'test index' , type: 'vectorSearch' } ] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition, name: 'test index', type: 'vectorSearch' } ] + $db: *database0 diff --git a/src/test/spec/json/index-management/dropSearchIndex.json b/src/test/spec/json/index-management/dropSearchIndex.json new file mode 100644 index 000000000..d8957a222 --- /dev/null +++ b/src/test/spec/json/index-management/dropSearchIndex.json @@ -0,0 +1,74 @@ +{ + "description": "dropSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "sends the correct command", + "operations": [ + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "dropSearchIndex": "collection0", + "name": "test index", + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/dropSearchIndex.yml b/src/test/spec/json/index-management/dropSearchIndex.yml new file mode 100644 index 000000000..8a8e82945 --- /dev/null +++ b/src/test/spec/json/index-management/dropSearchIndex.yml @@ -0,0 +1,43 @@ +description: "dropSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "sends the correct command" + operations: + - name: dropSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + dropSearchIndex: *collection0 + name: *indexName + $db: *database0 diff --git a/src/test/spec/json/index-management/listSearchIndexes.json b/src/test/spec/json/index-management/listSearchIndexes.json new file mode 100644 index 000000000..a8cef42f7 --- /dev/null +++ b/src/test/spec/json/index-management/listSearchIndexes.json @@ -0,0 +1,156 @@ +{ + "description": "listSearchIndexes", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "when no name is provided, it does not populate the filter", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": {} + } + ] + } + } + } + ] + } + ] + }, + { + "description": "when a name is provided, it is present in the filter", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": { + "name": "test index" + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + }, + { + "description": "aggregation cursor options are supported", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "arguments": { + "name": "test index", + "aggregationOptions": { + "batchSize": 10 + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "cursor": { + "batchSize": 10 + }, + "pipeline": [ + { + "$listSearchIndexes": { + "name": "test index" + } + } + ], + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/listSearchIndexes.yml b/src/test/spec/json/index-management/listSearchIndexes.yml new file mode 100644 index 000000000..f05a36858 --- /dev/null +++ b/src/test/spec/json/index-management/listSearchIndexes.yml @@ -0,0 +1,88 @@ +description: "listSearchIndexes" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "when no name is provided, it does not populate the filter" + operations: + - name: listSearchIndexes + object: *collection0 + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: {} + + - description: "when a name is provided, it is present in the filter" + operations: + - name: listSearchIndexes + object: *collection0 + arguments: + name: &indexName "test index" + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 + + - description: aggregation cursor options are supported + operations: + - name: listSearchIndexes + object: *collection0 + arguments: + name: &indexName "test index" + aggregationOptions: + batchSize: 10 + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + cursor: { batchSize: 10 } + pipeline: + - $listSearchIndexes: { name: *indexName } + $db: *database0 diff --git a/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.json b/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.json new file mode 100644 index 000000000..edf71b7b7 --- /dev/null +++ b/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.json @@ -0,0 +1,252 @@ +{ + "description": "search index operations ignore read and write concern", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "createSearchIndex ignores read and write concern", + "operations": [ + { + "name": "createSearchIndex", + "object": "collection0", + "arguments": { + "model": { + "definition": { + "mappings": { + "dynamic": true + } + } + } + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [ + { + "definition": { + "mappings": { + "dynamic": true + } + } + } + ], + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "createSearchIndexes ignores read and write concern", + "operations": [ + { + "name": "createSearchIndexes", + "object": "collection0", + "arguments": { + "models": [] + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createSearchIndexes": "collection0", + "indexes": [], + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "dropSearchIndex ignores read and write concern", + "operations": [ + { + "name": "dropSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index" + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "dropSearchIndex": "collection0", + "name": "test index", + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "listSearchIndexes ignores read and write concern", + "operations": [ + { + "name": "listSearchIndexes", + "object": "collection0", + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection0", + "pipeline": [ + { + "$listSearchIndexes": {} + } + ], + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "updateSearchIndex ignores the read and write concern", + "operations": [ + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "updateSearchIndex": "collection0", + "name": "test index", + "definition": {}, + "$db": "database0", + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.yml b/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.yml new file mode 100644 index 000000000..d1a3e9375 --- /dev/null +++ b/src/test/spec/json/index-management/searchIndexIgnoresReadWriteConcern.yml @@ -0,0 +1,147 @@ +description: "search index operations ignore read and write concern" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + # Set a non-default read and write concern. + uriOptions: + readConcernLevel: local + w: 1 + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "createSearchIndex ignores read and write concern" + operations: + - name: createSearchIndex + object: *collection0 + arguments: + model: { definition: &definition { mappings: { dynamic: true } } } + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [ { definition: *definition } ] + $db: *database0 + # Expect no writeConcern or readConcern to be sent. + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + + - description: "createSearchIndexes ignores read and write concern" + operations: + - name: createSearchIndexes + object: *collection0 + arguments: + models: [] + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + createSearchIndexes: *collection0 + indexes: [] + $db: *database0 + # Expect no writeConcern or readConcern to be sent. + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + + - description: "dropSearchIndex ignores read and write concern" + operations: + - name: dropSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + dropSearchIndex: *collection0 + name: *indexName + $db: *database0 + # Expect no writeConcern or readConcern to be sent. + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + + - description: "listSearchIndexes ignores read and write concern" + operations: + - name: listSearchIndexes + object: *collection0 + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + aggregate: *collection0 + pipeline: + - $listSearchIndexes: {} + # Expect no writeConcern or readConcern to be sent. + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + + - description: "updateSearchIndex ignores the read and write concern" + operations: + - name: updateSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + definition: &definition {} + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + updateSearchIndex: *collection0 + name: *indexName + definition: *definition + $db: *database0 + # Expect no writeConcern or readConcern to be sent. + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + diff --git a/src/test/spec/json/index-management/updateSearchIndex.json b/src/test/spec/json/index-management/updateSearchIndex.json new file mode 100644 index 000000000..76a596214 --- /dev/null +++ b/src/test/spec/json/index-management/updateSearchIndex.json @@ -0,0 +1,76 @@ +{ + "description": "updateSearchIndex", + "schemaVersion": "1.4", + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "collection0" + } + } + ], + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "sends the correct command", + "operations": [ + { + "name": "updateSearchIndex", + "object": "collection0", + "arguments": { + "name": "test index", + "definition": {} + }, + "expectError": { + "isError": true, + "errorContains": "Atlas" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "updateSearchIndex": "collection0", + "name": "test index", + "definition": {}, + "$db": "database0" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/index-management/updateSearchIndex.yml b/src/test/spec/json/index-management/updateSearchIndex.yml new file mode 100644 index 000000000..2c56e75ef --- /dev/null +++ b/src/test/spec/json/index-management/updateSearchIndex.yml @@ -0,0 +1,46 @@ +description: "updateSearchIndex" +schemaVersion: "1.4" +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - database: + id: &database0 database0 + client: *client0 + databaseName: *database0 + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: *collection0 + +runOnRequirements: + - minServerVersion: "7.0.0" + topologies: [ replicaset, load-balanced, sharded ] + serverless: forbid + +tests: + - description: "sends the correct command" + operations: + - name: updateSearchIndex + object: *collection0 + arguments: + name: &indexName 'test index' + definition: &definition {} + expectError: + # This test always errors in a non-Atlas environment. The test functions as a unit test by asserting + # that the driver constructs and sends the correct command. + # The expected error message was changed in SERVER-83003. Check for the substring "Atlas" shared by both error messages. + isError: true + errorContains: Atlas + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + updateSearchIndex: *collection0 + name: *indexName + definition: *definition + $db: *database0 + diff --git a/src/test/spec/json/initial-dns-seedlist-discovery/README.md b/src/test/spec/json/initial-dns-seedlist-discovery/README.md new file mode 100644 index 000000000..19e5fdd2e --- /dev/null +++ b/src/test/spec/json/initial-dns-seedlist-discovery/README.md @@ -0,0 +1,182 @@ +# Initial DNS Seedlist Discovery tests + +This directory contains platform-independent tests that drivers can use to prove their conformance to the Initial DNS +Seedlist Discovery spec. + +## Prose Tests + +For the following prose tests, it is assumed drivers are be able to stub DNS results to easily test invalid DNS +resolution results. + +### 1. Allow SRVs with fewer than 3 `.` separated parts + +When running validation on an SRV string before DNS resolution, do not throw a error due to number of SRV parts. + +- `mongodb+srv://localhost` +- `mongodb+srv://mongo.local` + +### 2. Throw when return address does not end with SRV domain + +When given a returned address that does NOT end with the original SRV's domain name, throw a runtime error. + +For this test, run each of the following cases: + +- the SRV `mongodb+srv://localhost` resolving to `localhost.mongodb` +- the SRV `mongodb+srv://mongo.local` resolving to `test_1.evil.local` +- the SRV `mongodb+srv://blogs.mongodb.com` resolving to `blogs.evil.com` + +Remember, the domain of an SRV with one or two `.` separated parts is the SRVs entire hostname. + +### 3. Throw when return address is identical to SRV hostname + +When given a returned address that is identical to the SRV hostname and the SRV hostname has fewer than three `.` +separated parts, throw a runtime error. + +For this test, run each of the following cases: + +- the SRV `mongodb+srv://localhost` resolving to `localhost` +- the SRV `mongodb+srv://mongo.local` resolving to `mongo.local` + +### 4. Throw when return address does not contain `.` separating shared part of domain + +When given a returned address that does NOT share the domain name of the SRV record because it's missing a `.`, throw a +runtime error. + +For this test, run each of the following cases: + +- the SRV `mongodb+srv://localhost` resolving to `test_1.cluster_1localhost` +- the SRV `mongodb+srv://mongo.local` resolving to `test_1.my_hostmongo.local` +- the SRV `mongodb+srv://blogs.mongodb.com` resolving to `cluster.testmongodb.com` + +## Test Setup + +The tests in the `replica-set` directory MUST be executed against a three-node replica set on localhost ports 27017, +27018, and 27019 with replica set name `repl0`. + +The tests in the `load-balanced` directory MUST be executed against a load-balanced sharded cluster with the mongos +servers running on localhost ports 27017 and 27018 and `--loadBalancerPort` 27050 and 27051, respectively (corresponding +to the script in +[drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-load-balancer.sh)). +The load balancers, shard servers, and config servers may run on any open ports. + +The tests in the `sharded` directory MUST be executed against a sharded cluster with the mongos servers running on +localhost ports 27017 and 27018. Shard servers and config servers may run on any open ports. + +In all cases, the clusters MUST be started with SSL enabled. + +To run the tests that accompany this spec, you need to configure the SRV and TXT records with a real name server. The +following records are required for these tests: + +```dns +Record TTL Class Address +localhost.test.build.10gen.cc. 86400 IN A 127.0.0.1 +localhost.sub.test.build.10gen.cc. 86400 IN A 127.0.0.1 + +Record TTL Class Port Target +_mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc. +_mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc. +_mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27019 localhost.test.build.10gen.cc. +_mongodb._tcp.test3.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test5.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test6.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test7.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test8.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test10.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test11.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test12.test.build.10gen.cc. 86400 IN SRV 27017 localhost.build.10gen.cc. +_mongodb._tcp.test13.test.build.10gen.cc. 86400 IN SRV 27017 test.build.10gen.cc. +_mongodb._tcp.test14.test.build.10gen.cc. 86400 IN SRV 27017 localhost.not-test.build.10gen.cc. +_mongodb._tcp.test15.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.not-build.10gen.cc. +_mongodb._tcp.test16.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.not-10gen.cc. +_mongodb._tcp.test17.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.not-cc. +_mongodb._tcp.test18.test.build.10gen.cc. 86400 IN SRV 27017 localhost.sub.test.build.10gen.cc. +_mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.evil.build.10gen.cc. +_mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test20.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test21.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_customname._tcp.test22.test.build.10gen.cc 86400 IN SRV 27017 localhost.test.build.10gen.cc. +_mongodb._tcp.test23.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc. +_mongodb._tcp.test24.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc. + +Record TTL Class Text +test5.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0&authSource=thisDB" +test6.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0" +test6.test.build.10gen.cc. 86400 IN TXT "authSource=otherDB" +test7.test.build.10gen.cc. 86400 IN TXT "ssl=false" +test8.test.build.10gen.cc. 86400 IN TXT "authSource" +test10.test.build.10gen.cc. 86400 IN TXT "socketTimeoutMS=500" +test11.test.build.10gen.cc. 86400 IN TXT "replicaS" "et=rep" "l0" +test20.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true" +test21.test.build.10gen.cc. 86400 IN TXT "loadBalanced=false" +test24.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true" +``` + +Notes: + +- `test4` is omitted deliberately to test what happens with no SRV record. +- `test9` is missing because it was deleted during the development of the tests. +- The missing `test.` sub-domain in the SRV record target for `test12` is deliberate. +- `test22` is used to test a custom service name (`customname`). +- `test23` and `test24` point to port 8000 (HAProxy) and are used for load-balanced tests. + +In our tests we have used `localhost.test.build.10gen.cc` as the domain, and then configured +`localhost.test.build.10gen.cc` to resolve to 127.0.0.1. + +You need to adapt the records shown above to replace `test.build.10gen.cc` with your own domain name, and update the +"uri" field in the YAML or JSON files in this directory with the actual domain. + +## Test Format and Use + +These YAML and JSON files contain the following fields: + +- `uri`: a `mongodb+srv` connection string +- `seeds`: the expected set of initial seeds discovered from the SRV record +- `numSeeds`: the expected number of initial seeds discovered from the SRV record. This is mainly used to test + `srvMaxHosts`, since randomly selected hosts cannot be deterministically asserted. +- `hosts`: the discovered topology's list of hosts once SDAM completes a scan +- `numHosts`: the expected number of hosts discovered once SDAM completes a scan. This is mainly used to test + `srvMaxHosts`, since randomly selected hosts cannot be deterministically asserted. +- `options`: the parsed [URI options](../../uri-options/uri-options.md) as discovered from the + [Connection String](../../connection-string/connection-string-spec.md)'s "Connection Options" component and SRV + resolution (e.g. TXT records, implicit `tls` default). +- `parsed_options`: additional, parsed options from other + [Connection String](../../connection-string/connection-string-spec.md) components. This is mainly used for asserting + `UserInfo` (as `user` and `password`) and `Auth database` (as `auth_database`). +- `error`: indicates that the parsing of the URI, or the resolving or contents of the SRV or TXT records included + errors. +- `comment`: a comment to indicate why a test would fail. +- `ping`: if false, the test runner should not run a "ping" operation. + +For each YAML file: + +- Create a MongoClient initialized with the `mongodb+srv` connection string. +- Run a "ping" operation unless `ping` is false or `error` is true. + +Assertions: + +- If `seeds` is specified, drivers SHOULD verify that the set of hosts in the client's initial seedlist matches the list + in `seeds`. If `numSeeds` is specified, drivers SHOULD verify that the size of that set matches `numSeeds`. + +- If `hosts` is specified, drivers MUST verify that the set of ServerDescriptions in the client's TopologyDescription + eventually matches the list in `hosts`. If `numHosts` is specified, drivers MUST verify that the size of that set + matches `numHosts`. + +- If `options` is specified, drivers MUST verify each of the values under `options` match the MongoClient's parsed value + for that option. There may be other options parsed by the MongoClient as well, which a test does not verify. + +- If `parsed_options` is specified, drivers MUST verify that each of the values under `parsed_options` match the + MongoClient's parsed value for that option. Supported values include, but are not limited to, `user` and `password` + (parsed from `UserInfo`) and `auth_database` (parsed from `Auth database`). + +- If `error` is specified and `true`, drivers MUST verify that initializing the MongoClient throws an error. If `error` + is not specified or is `false`, both initializing the MongoClient and running a ping operation must succeed without + throwing any errors. + +- If `ping` is not specified or `true`, drivers MUST verify that running a "ping" operation using the initialized + MongoClient succeeds. If `ping` is `false`, drivers MUST NOT run a "ping" operation. + + > **Note:** These tests are expected to be run against MongoDB databases with and without authentication enabled. The + > "ping" operation does not require authentication so should succeed with URIs that contain no userinfo (i.e. no + > username and password). Tests with URIs that contain userinfo always set `ping` to `false` because some drivers will + > fail handshake on a connection if userinfo is provided but incorrect. diff --git a/src/test/spec/json/initial-dns-seedlist-discovery/README.rst b/src/test/spec/json/initial-dns-seedlist-discovery/README.rst deleted file mode 100644 index c1f6c5bb4..000000000 --- a/src/test/spec/json/initial-dns-seedlist-discovery/README.rst +++ /dev/null @@ -1,162 +0,0 @@ -==================================== -Initial DNS Seedlist Discovery tests -==================================== - -This directory contains platform-independent tests that drivers can use -to prove their conformance to the Initial DNS Seedlist Discovery spec. - -Test Setup ----------- - -The tests in the ``replica-set`` directory MUST be executed against a -three-node replica set on localhost ports 27017, 27018, and 27019 with -replica set name ``repl0``. - -The tests in the ``load-balanced`` directory MUST be executed against a -load-balanced sharded cluster with the mongos servers running on localhost ports -27017 and 27018 and ``--loadBalancerPort`` 27050 and 27051, respectively -(corresponding to the script in `drivers-evergreen-tools`_). The load balancers, -shard servers, and config servers may run on any open ports. - -.. _`drivers-evergreen-tools`: https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/run-load-balancer.sh - -The tests in the ``sharded`` directory MUST be executed against a sharded -cluster with the mongos servers running on localhost ports 27017 and 27018. -Shard servers and config servers may run on any open ports. - -In all cases, the clusters MUST be started with SSL enabled. - -To run the tests that accompany this spec, you need to configure the SRV and -TXT records with a real name server. The following records are required for -these tests:: - - Record TTL Class Address - localhost.test.build.10gen.cc. 86400 IN A 127.0.0.1 - localhost.sub.test.build.10gen.cc. 86400 IN A 127.0.0.1 - - Record TTL Class Port Target - _mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test1.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc. - _mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27018 localhost.test.build.10gen.cc. - _mongodb._tcp.test2.test.build.10gen.cc. 86400 IN SRV 27019 localhost.test.build.10gen.cc. - _mongodb._tcp.test3.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test5.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test6.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test7.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test8.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test10.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test11.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test12.test.build.10gen.cc. 86400 IN SRV 27017 localhost.build.10gen.cc. - _mongodb._tcp.test13.test.build.10gen.cc. 86400 IN SRV 27017 test.build.10gen.cc. - _mongodb._tcp.test14.test.build.10gen.cc. 86400 IN SRV 27017 localhost.not-test.build.10gen.cc. - _mongodb._tcp.test15.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.not-build.10gen.cc. - _mongodb._tcp.test16.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.not-10gen.cc. - _mongodb._tcp.test17.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.not-cc. - _mongodb._tcp.test18.test.build.10gen.cc. 86400 IN SRV 27017 localhost.sub.test.build.10gen.cc. - _mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.evil.build.10gen.cc. - _mongodb._tcp.test19.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test20.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test21.test.build.10gen.cc. 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _customname._tcp.test22.test.build.10gen.cc 86400 IN SRV 27017 localhost.test.build.10gen.cc. - _mongodb._tcp.test23.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc. - _mongodb._tcp.test24.test.build.10gen.cc. 86400 IN SRV 8000 localhost.test.build.10gen.cc. - - Record TTL Class Text - test5.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0&authSource=thisDB" - test6.test.build.10gen.cc. 86400 IN TXT "replicaSet=repl0" - test6.test.build.10gen.cc. 86400 IN TXT "authSource=otherDB" - test7.test.build.10gen.cc. 86400 IN TXT "ssl=false" - test8.test.build.10gen.cc. 86400 IN TXT "authSource" - test10.test.build.10gen.cc. 86400 IN TXT "socketTimeoutMS=500" - test11.test.build.10gen.cc. 86400 IN TXT "replicaS" "et=rep" "l0" - test20.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true" - test21.test.build.10gen.cc. 86400 IN TXT "loadBalanced=false" - test24.test.build.10gen.cc. 86400 IN TXT "loadBalanced=true" - -Notes: - -- ``test4`` is omitted deliberately to test what happens with no SRV record. -- ``test9`` is missing because it was deleted during the development of the - tests. -- The missing ``test.`` sub-domain in the SRV record target for ``test12`` is - deliberate. -- ``test22`` is used to test a custom service name (``customname``). -- ``test23`` and ``test24`` point to port 8000 (HAProxy) and are used for - load-balanced tests. - -In our tests we have used ``localhost.test.build.10gen.cc`` as the domain, and -then configured ``localhost.test.build.10gen.cc`` to resolve to 127.0.0.1. - -You need to adapt the records shown above to replace ``test.build.10gen.cc`` -with your own domain name, and update the "uri" field in the YAML or JSON files -in this directory with the actual domain. - -Test Format and Use -------------------- - -These YAML and JSON files contain the following fields: - -- ``uri``: a ``mongodb+srv`` connection string -- ``seeds``: the expected set of initial seeds discovered from the SRV record -- ``numSeeds``: the expected number of initial seeds discovered from the SRV - record. This is mainly used to test ``srvMaxHosts``, since randomly selected - hosts cannot be deterministically asserted. -- ``hosts``: the discovered topology's list of hosts once SDAM completes a scan -- ``numHosts``: the expected number of hosts discovered once SDAM completes a - scan. This is mainly used to test ``srvMaxHosts``, since randomly selected - hosts cannot be deterministically asserted. -- ``options``: the parsed `URI options`_ as discovered from the - `Connection String`_'s "Connection Options" component and SRV resolution - (e.g. TXT records, implicit ``tls`` default). -- ``parsed_options``: additional, parsed options from other `Connection String`_ - components. This is mainly used for asserting ``UserInfo`` (as ``user`` and - ``password``) and ``Auth database`` (as ``auth_database``). -- ``error``: indicates that the parsing of the URI, or the resolving or - contents of the SRV or TXT records included errors. -- ``comment``: a comment to indicate why a test would fail. -- ``ping``: if true, the test runner should run a "ping" operation after - initializing a MongoClient. - -.. _`Connection String`: ../../connection-string/connection-string-spec.rst -.. _`URI options`: ../../uri-options/uri-options.rst - -For each YAML file: - -- Create a MongoClient initialized with the ``mongodb+srv`` - connection string. -- Run run a "ping" operation if ``ping`` in the test file is true. - -Assertions: - -- If ``seeds`` is specified, drivers SHOULD verify that the set of hosts in the - client's initial seedlist matches the list in ``seeds``. If ``numSeeds`` is - specified, drivers SHOULD verify that the size of that set matches - ``numSeeds``. - -- If ``hosts`` is specified, drivers MUST verify that the set of - ServerDescriptions in the client's TopologyDescription eventually matches the - list in ``hosts``. If ``numHosts`` is specified, drivers MUST verify that the - size of that set matches ``numHosts``. - -- If ``options`` is specified, drivers MUST verify each of the values under - ``options`` match the MongoClient's parsed value for that option. There may be - other options parsed by the MongoClient as well, which a test does not verify. - -- If ``parsed_options`` is specified, drivers MUST verify that each of the - values under ``parsed_options`` match the MongoClient's parsed value for that - option. Supported values include, but are not limited to, ``user`` and - ``password`` (parsed from ``UserInfo``) and ``auth_database`` (parsed from - ``Auth database``). - -- If ``error`` is specified and ``true``, drivers MUST verify that initializing - the MongoClient throws an error. If ``error`` is not specified or is - ``false``, both initializing the MongoClient and running a ping operation must - succeed without throwing any errors. - -- If ``ping`` is specified and ``true``, drivers MUST verify that running a - "ping" operation using the initialized MongoClient succeeds. If ``ping`` is - not specified or is ``false``, drivers MUST NOT run a "ping" operation. - **Note:** These tests are expected to be run against MongoDB databases with - and without authentication enabled. The "ping" operation does not require - authentication so should succeed, even though the test URIs do not have - correct authentication information. diff --git a/src/test/spec/json/load-balancers/README.md b/src/test/spec/json/load-balancers/README.md new file mode 100644 index 000000000..45f185caa --- /dev/null +++ b/src/test/spec/json/load-balancers/README.md @@ -0,0 +1,49 @@ +# Load Balancer Support Tests + +______________________________________________________________________ + +## Introduction + +This document describes how drivers should create load balanced clusters for testing and how tests should be executed +for such clusters. + +## Testing Requirements + +For each server version that supports load balanced clusters, drivers MUST add two Evergreen tasks: one with a sharded +cluster with both authentication and TLS enabled and one with a sharded cluster with authentication and TLS disabled. In +each task, the sharded cluster MUST be configured with two mongos nodes running on localhost ports 27017 and 27018. The +shard and config servers may run on any free ports. Each task MUST also start up two TCP load balancers operating in +round-robin mode: one fronting both mongos servers and one fronting a single mongos. + +### Load Balancer Configuration + +Drivers MUST use the `run-load-balancer.sh` script in `drivers-evergreen-tools` to start the TCP load balancers for +Evergreen tasks. This script MUST be run after the backing sharded cluster has already been started. The script writes +the URIs of the load balancers to a YAML expansions file, which can be read by drivers via the `expansions.update` +Evergreen command. This will store the URIs into the `SINGLE_MONGOS_LB_URI` and `MULTI_MONGOS_LB_URI` environment +variables. + +### Test Runner Configuration + +If the backing sharded cluster is configured with TLS enabled, drivers MUST add the relevant TLS options to both +`SINGLE_MONGOS_LB_URI` and `MULTI_MONGOS_LB_URI` to ensure that test clients can connect to the cluster. Drivers MUST +use the final URI stored in `SINGLE_MONGOS_LB_URI` (with additional TLS options if required) to configure internal +clients for test runners (e.g. the internal MongoClient described by the +[Unified Test Format spec](../../unified-test-format/unified-test-format.md)). + +In addition to modifying load balancer URIs, drivers MUST also mock server support for returning a `serviceId` field in +`hello` or legacy `hello` command responses when running tests against a load-balanced cluster. This can be done by +using the value of `topologyVersion.processId` to set `serviceId`. This MUST be done for all connections established by +the test runner, including those made by any internal clients. + +## Tests + +The YAML and JSON files in this directory contain platform-independent tests written in the +[Unified Test Format](../../unified-test-format/unified-test-format.md). Drivers MUST run the following test suites +against a load balanced cluster: + +1. All test suites written in the Unified Test Format +2. Retryable Reads +3. Retryable Writes +4. Change Streams +5. Initial DNS Seedlist Discovery diff --git a/src/test/spec/json/load-balancers/README.rst b/src/test/spec/json/load-balancers/README.rst deleted file mode 100644 index 3975e7b0b..000000000 --- a/src/test/spec/json/load-balancers/README.rst +++ /dev/null @@ -1,68 +0,0 @@ -=========================== -Load Balancer Support Tests -=========================== - -.. contents:: - ----- - -Introduction -============ - -This document describes how drivers should create load balanced clusters for -testing and how tests should be executed for such clusters. - -Testing Requirements -==================== - -For each server version that supports load balanced clusters, drivers MUST -add two Evergreen tasks: one with a sharded cluster with both authentication -and TLS enabled and one with a sharded cluster with authentication and TLS -disabled. In each task, the sharded cluster MUST be configured with two -mongos nodes running on localhost ports 27017 and 27018. The shard and config -servers may run on any free ports. Each task MUST also start up two TCP load -balancers operating in round-robin mode: one fronting both mongos servers and -one fronting a single mongos. - -Load Balancer Configuration ---------------------------- - -Drivers MUST use the ``run-load-balancer.sh`` script in -``drivers-evergreen-tools`` to start the TCP load balancers for Evergreen -tasks. This script MUST be run after the backing sharded cluster has already -been started. The script writes the URIs of the load balancers to a YAML -expansions file, which can be read by drivers via the ``expansions.update`` -Evergreen command. This will store the URIs into the ``SINGLE_MONGOS_LB_URI`` -and ``MULTI_MONGOS_LB_URI`` environment variables. - -Test Runner Configuration -------------------------- - -If the backing sharded cluster is configured with TLS enabled, drivers MUST -add the relevant TLS options to both ``SINGLE_MONGOS_LB_URI`` and -``MULTI_MONGOS_LB_URI`` to ensure that test clients can connect to the -cluster. Drivers MUST use the final URI stored in ``SINGLE_MONGOS_LB_URI`` -(with additional TLS options if required) to configure internal clients for -test runners (e.g. the internal MongoClient described by the `Unified Test -Format spec <../../unified-test-format/unified-test-format.rst>`__). - -In addition to modifying load balancer URIs, drivers MUST also mock server -support for returning a ``serviceId`` field in ``hello`` or legacy ``hello`` -command responses when running tests against a load-balanced cluster. This -can be done by using the value of ``topologyVersion.processId`` to set -``serviceId``. This MUST be done for all connections established by the test -runner, including those made by any internal clients. - -Tests -====== - -The YAML and JSON files in this directory contain platform-independent tests -written in the `Unified Test Format -<../../unified-test-format/unified-test-format.rst>`_. Drivers MUST run the -following test suites against a load balanced cluster: - -#. All test suites written in the Unified Test Format -#. Retryable Reads -#. Retryable Writes -#. Change Streams -#. Initial DNS Seedlist Discovery diff --git a/src/test/spec/json/load-balancers/cursors.json b/src/test/spec/json/load-balancers/cursors.json index 6eddc0ebc..b11bf2c6f 100644 --- a/src/test/spec/json/load-balancers/cursors.json +++ b/src/test/spec/json/load-balancers/cursors.json @@ -1,6 +1,6 @@ { "description": "cursors are correctly pinned to connections for load-balanced clusters", - "schemaVersion": "1.3", + "schemaVersion": "1.4", "runOnRequirements": [ { "topologies": [ @@ -222,7 +222,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "firstBatch": { "$$type": "array" @@ -239,7 +242,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, @@ -333,7 +339,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "firstBatch": { "$$type": "array" @@ -475,7 +484,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "firstBatch": { "$$type": "array" @@ -492,7 +504,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, @@ -605,7 +620,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "firstBatch": { "$$type": "array" @@ -750,7 +768,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "firstBatch": { "$$type": "array" @@ -767,7 +788,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, @@ -858,7 +882,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, @@ -950,7 +977,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": { "$$type": "string" @@ -1100,7 +1130,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, diff --git a/src/test/spec/json/load-balancers/cursors.yml b/src/test/spec/json/load-balancers/cursors.yml index b99f4a26b..1b558affe 100644 --- a/src/test/spec/json/load-balancers/cursors.yml +++ b/src/test/spec/json/load-balancers/cursors.yml @@ -1,6 +1,6 @@ description: cursors are correctly pinned to connections for load-balanced clusters -schemaVersion: '1.3' +schemaVersion: '1.4' runOnRequirements: - topologies: [ load-balanced ] @@ -126,14 +126,14 @@ tests: commandSucceededEvent: reply: cursor: - id: { $$type: long } + id: { $$type: [ int, long ] } firstBatch: { $$type: array } ns: { $$type: string } commandName: find - &getMoreStarted commandStartedEvent: command: - getMore: { $$type: long } + getMore: { $$type: [ int, long ] } collection: *collection0Name commandName: getMore - &getMoreSucceeded @@ -386,7 +386,7 @@ tests: # is not equal to *collection0Name as the command is not executed against a collection. - commandStartedEvent: command: - getMore: { $$type: long } + getMore: { $$type: [ int, long ] } collection: { $$type: string } commandName: getMore - *getMoreSucceeded diff --git a/src/test/spec/json/load-balancers/sdam-error-handling.json b/src/test/spec/json/load-balancers/sdam-error-handling.json index 4ab34b1fe..47323fae4 100644 --- a/src/test/spec/json/load-balancers/sdam-error-handling.json +++ b/src/test/spec/json/load-balancers/sdam-error-handling.json @@ -1,6 +1,6 @@ { "description": "state change errors are correctly handled", - "schemaVersion": "1.3", + "schemaVersion": "1.4", "runOnRequirements": [ { "topologies": [ @@ -263,7 +263,7 @@ "description": "errors during the initial connection hello are ignored", "runOnRequirements": [ { - "minServerVersion": "4.9" + "minServerVersion": "4.4.7" } ], "operations": [ diff --git a/src/test/spec/json/load-balancers/sdam-error-handling.yml b/src/test/spec/json/load-balancers/sdam-error-handling.yml index e3d6d6a25..b81d811dc 100644 --- a/src/test/spec/json/load-balancers/sdam-error-handling.yml +++ b/src/test/spec/json/load-balancers/sdam-error-handling.yml @@ -1,6 +1,6 @@ description: state change errors are correctly handled -schemaVersion: '1.3' +schemaVersion: '1.4' runOnRequirements: - topologies: [ load-balanced ] @@ -141,9 +141,8 @@ tests: # to the same mongos on which the failpoint is set. - description: errors during the initial connection hello are ignored runOnRequirements: - # Server version 4.9+ is needed to set a fail point on the initial - # connection handshake with the appName filter due to SERVER-49336. - - minServerVersion: '4.9' + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: '4.4.7' operations: - name: failPoint object: testRunner diff --git a/src/test/spec/json/load-balancers/transactions.json b/src/test/spec/json/load-balancers/transactions.json index 8cf24f4ca..ca9c14521 100644 --- a/src/test/spec/json/load-balancers/transactions.json +++ b/src/test/spec/json/load-balancers/transactions.json @@ -1,6 +1,6 @@ { "description": "transactions are correctly pinned to connections for load-balanced clusters", - "schemaVersion": "1.3", + "schemaVersion": "1.4", "runOnRequirements": [ { "topologies": [ @@ -1616,6 +1616,50 @@ ] } ] + }, + { + "description": "pinned connection is released when session ended", + "operations": [ + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "x": 1 + }, + "session": "session0" + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "endSession", + "object": "session0" + } + ], + "expectEvents": [ + { + "client": "client0", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] } ] } diff --git a/src/test/spec/json/load-balancers/transactions.yml b/src/test/spec/json/load-balancers/transactions.yml index 29cbbee72..4807ff21e 100644 --- a/src/test/spec/json/load-balancers/transactions.yml +++ b/src/test/spec/json/load-balancers/transactions.yml @@ -1,6 +1,6 @@ description: transactions are correctly pinned to connections for load-balanced clusters -schemaVersion: '1.3' +schemaVersion: '1.4' runOnRequirements: - topologies: [ load-balanced ] @@ -596,3 +596,21 @@ tests: - connectionCheckedOutEvent: {} # Events for abortTransaction. - connectionCheckedInEvent: {} + + - description: pinned connection is released when session ended + operations: + - *startTransaction + - *transactionalInsert + - *commitTransaction + - &endSession + name: endSession + object: *session0 + expectEvents: + - client: *client0 + eventType: cmap + events: + # Events for the insert and commitTransaction. + - connectionReadyEvent: {} + - connectionCheckedOutEvent: {} + # Events for endSession. + - connectionCheckedInEvent: {} \ No newline at end of file diff --git a/src/test/spec/json/read-write-concern/README.rst b/src/test/spec/json/read-write-concern/README.rst index 599559013..4c4dd984f 100644 --- a/src/test/spec/json/read-write-concern/README.rst +++ b/src/test/spec/json/read-write-concern/README.rst @@ -1,6 +1,6 @@ -======================= -Connection String Tests -======================= +============================ +Read and Write Concern Tests +============================ The YAML and JSON files in this directory tree are platform-independent tests that drivers can use to prove their conformance to the Read and Write Concern @@ -57,19 +57,7 @@ Operation These tests check that the default write concern is omitted in operations. -The spec test format is an extension of `transactions spec tests `_ with the following additions: - -- ``writeConcern`` in the ``databaseOptions`` or ``collectionOptions`` may be an empty document to indicate a `server default write concern `_. For example, in libmongoc: - - .. code:: c - - /* Create a default write concern, and set on a collection object. */ - mongoc_write_concern_t *wc = mongoc_write_concern_new (); - mongoc_collection_set_write_concern (collection, wc); - - If the driver has no way to explicitly set a default write concern on a database or collection, ignore the empty ``writeConcern`` document and continue with the test. -- The operations ``createIndex``, ``dropIndex`` are introduced. - +The tests utilize the `Unified Test Format <../../unified-test-format/unified-test-format.md>`__. Use as unit tests ================= diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.json b/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.json index c623298cd..0d8f9c98a 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.json +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.json @@ -1,19 +1,55 @@ { - "data": [ + "description": "default-write-concern-2.6", + "schemaVersion": "1.0", + "runOnRequirements": [ { - "_id": 1, - "x": 11 + "minServerVersion": "2.6" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default-write-concern-tests", + "databaseOptions": { + "writeConcern": {} + } + } }, { - "_id": 2, - "x": 22 + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll", + "collectionOptions": { + "writeConcern": {} + } + } } ], - "collection_name": "default_write_concern_coll", - "database_name": "default_write_concern_db", - "runOn": [ + "initialData": [ { - "minServerVersion": "2.6" + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] } ], "tests": [ @@ -22,32 +58,36 @@ "operations": [ { "name": "deleteOne", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": {} }, - "result": { + "expectResult": { "deletedCount": 1 } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "delete": "default_write_concern_coll", - "deletes": [ - { - "q": {}, - "limit": 1 + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "coll", + "deletes": [ + { + "q": {}, + "limit": 1 + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null + } } - } + ] } ] }, @@ -56,32 +96,36 @@ "operations": [ { "name": "deleteMany", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": {} }, - "result": { + "expectResult": { "deletedCount": 2 } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "delete": "default_write_concern_coll", - "deletes": [ - { - "q": {}, - "limit": 0 + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "coll", + "deletes": [ + { + "q": {}, + "limit": 0 + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null + } } - } + ] } ] }, @@ -90,30 +134,24 @@ "operations": [ { "name": "bulkWrite", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "ordered": true, "requests": [ { - "name": "deleteMany", - "arguments": { + "deleteMany": { "filter": {} } }, { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 1 } } }, { - "name": "updateOne", - "arguments": { + "updateOne": { "filter": { "_id": 1 }, @@ -125,16 +163,14 @@ } }, { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 2 } } }, { - "name": "replaceOne", - "arguments": { + "replaceOne": { "filter": { "_id": 1 }, @@ -144,16 +180,14 @@ } }, { - "name": "insertOne", - "arguments": { + "insertOne": { "document": { "_id": 3 } } }, { - "name": "updateMany", - "arguments": { + "updateMany": { "filter": { "_id": 1 }, @@ -165,8 +199,7 @@ } }, { - "name": "deleteOne", - "arguments": { + "deleteOne": { "filter": { "_id": 3 } @@ -176,148 +209,185 @@ } } ], - "outcome": { - "collection": { - "name": "default_write_concern_coll", - "data": [ + "expectEvents": [ + { + "client": "client0", + "events": [ { - "_id": 1, - "x": 3 + "commandStartedEvent": { + "command": { + "delete": "coll", + "deletes": [ + { + "q": {}, + "limit": 0 + } + ], + "writeConcern": { + "$$exists": false + } + } + } }, { - "_id": 2 - } - ] - } - }, - "expectations": [ - { - "command_started_event": { - "command": { - "delete": "default_write_concern_coll", - "deletes": [ - { - "q": {}, - "limit": 0 + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 1 + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "insert": "default_write_concern_coll", - "documents": [ - { - "_id": 1 + } + }, + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$set": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$set": { - "x": 1 + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 2 } + ], + "writeConcern": { + "$$exists": false } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "insert": "default_write_concern_coll", - "documents": [ - { - "_id": 2 + } + }, + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "x": 2 + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "x": 2 + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3 + } + ], + "writeConcern": { + "$$exists": false } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "insert": "default_write_concern_coll", - "documents": [ - { - "_id": 3 + } + }, + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$set": { + "x": 3 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$set": { - "x": 3 + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "coll", + "deletes": [ + { + "q": { + "_id": 3 + }, + "limit": 1 } - }, - "multi": true + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null + } } - } - }, + ] + } + ], + "outcome": [ { - "command_started_event": { - "command": { - "delete": "default_write_concern_coll", - "deletes": [ - { - "q": { - "_id": 3 - }, - "limit": 1 - } - ], - "writeConcern": null + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 3 + }, + { + "_id": 2 } - } + ] } ] }, @@ -326,10 +396,7 @@ "operations": [ { "name": "insertOne", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "document": { "_id": 3 @@ -338,10 +405,7 @@ }, { "name": "insertMany", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "documents": [ { @@ -354,10 +418,51 @@ } } ], - "outcome": { - "collection": { - "name": "default_write_concern_coll", - "data": [ + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3 + } + ], + "writeConcern": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ], + "writeConcern": { + "$$exists": false + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ { "_id": 1, "x": 11 @@ -377,37 +482,6 @@ } ] } - }, - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "default_write_concern_coll", - "documents": [ - { - "_id": 3 - } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "insert": "default_write_concern_coll", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ], - "writeConcern": null - } - } - } ] }, { @@ -415,10 +489,7 @@ "operations": [ { "name": "updateOne", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 1 @@ -432,10 +503,7 @@ }, { "name": "updateMany", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 2 @@ -449,10 +517,7 @@ }, { "name": "replaceOne", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 2 @@ -463,80 +528,107 @@ } } ], - "outcome": { - "collection": { - "name": "default_write_concern_coll", - "data": [ + "expectEvents": [ + { + "client": "client0", + "events": [ { - "_id": 1, - "x": 1 + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$set": { + "x": 1 + } + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "writeConcern": { + "$$exists": false + } + } + } }, { - "_id": 2, - "x": 3 - } - ] - } - }, - "expectations": [ - { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$set": { - "x": 1 + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "$set": { + "x": 2 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } } + ], + "writeConcern": { + "$$exists": false } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 2 - }, - "u": { - "$set": { - "x": 2 + } + }, + { + "commandStartedEvent": { + "command": { + "update": "coll", + "updates": [ + { + "q": { + "_id": 2 + }, + "u": { + "x": 3 + }, + "upsert": { + "$$unsetOrMatches": false + }, + "multi": { + "$$unsetOrMatches": false + } } - }, - "multi": true + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null + } } - } - }, + ] + } + ], + "outcome": [ { - "command_started_event": { - "command": { - "update": "default_write_concern_coll", - "updates": [ - { - "q": { - "_id": 2 - }, - "u": { - "x": 3 - } - } - ], - "writeConcern": null + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 1 + }, + { + "_id": 2, + "x": 3 } - } + ] } ] } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.yml b/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.yml index 725bcfca1..d18962a7f 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.yml +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-2.6.yml @@ -1,215 +1,296 @@ -# Test that setting a default write concern does not add a write concern -# to the command sent over the wire. +# Test that setting a default write concern does not add a write concern to the command sent over the wire. # Test operations that require 2.6+ server. -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} -collection_name: &collection_name default_write_concern_coll -database_name: &database_name default_write_concern_db +description: default-write-concern-2.6 -runOn: - - minServerVersion: "2.6" +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "2.6" + +createEntities: + - + client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name default-write-concern-tests + databaseOptions: + writeConcern: {} + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + collectionOptions: + writeConcern: {} + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } tests: - - description: DeleteOne omits default write concern + - + description: DeleteOne omits default write concern operations: - - name: deleteOne - object: collection - collectionOptions: {writeConcern: {}} + - + name: deleteOne + object: *collection0 arguments: filter: {} - result: + expectResult: deletedCount: 1 - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - {q: {}, limit: 1} - writeConcern: null - - description: DeleteMany omits default write concern + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: [ { q: {}, limit: 1 } ] + writeConcern: { $$exists: false } + - + description: DeleteMany omits default write concern operations: - - name: deleteMany - object: collection - collectionOptions: {writeConcern: {}} + - + name: deleteMany + object: *collection0 arguments: filter: {} - result: + expectResult: deletedCount: 2 - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: [{q: {}, limit: 0}] - writeConcern: null - - description: BulkWrite with all models omits default write concern + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: [ { q: {}, limit: 0 } ] + writeConcern: { $$exists: false } + - + description: BulkWrite with all models omits default write concern operations: - - name: bulkWrite - object: collection - collectionOptions: {writeConcern: {}} + - + name: bulkWrite + object: *collection0 arguments: ordered: true requests: - - name: deleteMany - arguments: + - + deleteMany: filter: {} - - name: insertOne - arguments: - document: {_id: 1} - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - - name: insertOne - arguments: - document: {_id: 2} - - name: replaceOne - arguments: - filter: {_id: 1} - replacement: {x: 2} - - name: insertOne - arguments: - document: {_id: 3} - - name: updateMany - arguments: - filter: {_id: 1} - update: {$set: {x: 3}} - - name: deleteOne - arguments: - filter: {_id: 3} + - + insertOne: + document: { _id: 1 } + - + updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + - + insertOne: + document: { _id: 2 } + - + replaceOne: + filter: { _id: 1 } + replacement: { x: 2 } + - + insertOne: + document: { _id: 3 } + - + updateMany: + filter: { _id: 1 } + update: { $set: { x: 3 } } + - + deleteOne: + filter: { _id: 3 } + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: [ { q: {}, limit: 0 } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + insert: *collection_name + documents: [ { _id: 1 } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $set: { x: 1 } } + upsert: { $$unsetOrMatches: false } + multi: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + insert: *collection_name + documents: [ { _id: 2 } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { x: 2 } + upsert: { $$unsetOrMatches: false } + multi: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + insert: *collection_name + documents: [ { _id: 3 } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $set: { x: 3 } } + multi: true + upsert: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + delete: *collection_name + deletes: [ { q: { _id: 3 }, limit: 1 } ] + writeConcern: { $$exists: false } outcome: - collection: - name: *collection_name - data: - - {_id: 1, x: 3} - - {_id: 2} - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: [{q: {}, limit: 0}] - writeConcern: null - - command_started_event: - command: - insert: *collection_name - documents: - - {_id: 1} - writeConcern: null - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 1}, u: {$set: {x: 1}}} - writeConcern: null - - command_started_event: - command: - insert: *collection_name - documents: - - {_id: 2} - writeConcern: null - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 1}, u: {x: 2}} - writeConcern: null - - command_started_event: - command: - insert: *collection_name - documents: - - {_id: 3} - writeConcern: null - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 1}, u: {$set: {x: 3}}, multi: true} - writeConcern: null - - command_started_event: - command: - delete: *collection_name - deletes: [{q: {_id: 3}, limit: 1}] - writeConcern: null - - description: 'InsertOne and InsertMany omit default write concern' + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 3 } + - { _id: 2 } + - + description: InsertOne and InsertMany omit default write concern operations: - - name: insertOne - object: collection - collectionOptions: {writeConcern: {}} + - + name: insertOne + object: *collection0 arguments: - document: {_id: 3} - - name: insertMany - object: collection - collectionOptions: {writeConcern: {}} + document: { _id: 3 } + - + name: insertMany + object: *collection0 arguments: documents: - - {_id: 4} - - {_id: 5} + - { _id: 4 } + - { _id: 5 } + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: [ { _id: 3 } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + insert: *collection_name + documents: [ { _id: 4 }, { _id: 5 } ] + writeConcern: { $$exists: false } outcome: - collection: - name: *collection_name - data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3} - - {_id: 4} - - {_id: 5} - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - {_id: 3} - writeConcern: null - - command_started_event: - command: - insert: *collection_name - documents: - - {_id: 4} - - {_id: 5} - writeConcern: null - - description: 'UpdateOne, UpdateMany, and ReplaceOne omit default write concern' + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + - + description: UpdateOne, UpdateMany, and ReplaceOne omit default write concern operations: - - name: updateOne - object: collection - collectionOptions: {writeConcern: {}} + - + name: updateOne + object: *collection0 arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - - name: updateMany - object: collection - collectionOptions: {writeConcern: {}} + filter: { _id: 1 } + update: { $set: { x: 1 } } + - + name: updateMany + object: *collection0 arguments: - filter: {_id: 2} - update: {$set: {x: 2}} - - name: replaceOne - object: collection - collectionOptions: {writeConcern: {}} + filter: { _id: 2 } + update: { $set: { x: 2 } } + - + name: replaceOne + object: *collection0 arguments: - filter: {_id: 2} - replacement: {x: 3} + filter: { _id: 2 } + replacement: { x: 3 } + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $set: { x: 1 } } + upsert: { $$unsetOrMatches: false } + multi: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 2 } + u: { $set: { x: 2 } } + multi: true + upsert: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 2 } + u: { x: 3 } + upsert: { $$unsetOrMatches: false } + multi: { $$unsetOrMatches: false } + writeConcern: { $$exists: false } outcome: - collection: - name: *collection_name - data: - - {_id: 1, x: 1} - - {_id: 2, x: 3} - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 1}, u: {$set: {x: 1}}} - writeConcern: null - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 2}, u: {$set: {x: 2}}, multi: true} - writeConcern: null - - command_started_event: - command: - update: *collection_name - updates: - - {q: {_id: 2}, u: {x: 3}} - writeConcern: null \ No newline at end of file + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1 } + - { _id: 2, x: 3 } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.json b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.json index 04dd231f0..166a18491 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.json +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.json @@ -1,19 +1,55 @@ { - "data": [ + "description": "default-write-concern-3.2", + "schemaVersion": "1.0", + "runOnRequirements": [ { - "_id": 1, - "x": 11 + "minServerVersion": "3.2" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } }, { - "_id": 2, - "x": 22 + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default-write-concern-tests", + "databaseOptions": { + "writeConcern": {} + } + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll", + "collectionOptions": { + "writeConcern": {} + } + } } ], - "collection_name": "default_write_concern_coll", - "database_name": "default_write_concern_db", - "runOn": [ + "initialData": [ { - "minServerVersion": "3.2" + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] } ], "tests": [ @@ -22,10 +58,7 @@ "operations": [ { "name": "findOneAndUpdate", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 1 @@ -39,10 +72,7 @@ }, { "name": "findOneAndReplace", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 2 @@ -54,10 +84,7 @@ }, { "name": "findOneAndDelete", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "filter": { "_id": 2 @@ -65,59 +92,71 @@ } } ], - "outcome": { - "collection": { - "name": "default_write_concern_coll", - "data": [ + "expectEvents": [ + { + "client": "client0", + "events": [ { - "_id": 1, - "x": 1 + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + }, + "writeConcern": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": 2 + }, + "update": { + "x": 2 + }, + "writeConcern": { + "$$exists": false + } + } + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "coll", + "query": { + "_id": 2 + }, + "remove": true, + "writeConcern": { + "$$exists": false + } + } + } } ] } - }, - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "default_write_concern_coll", - "query": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - }, - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "default_write_concern_coll", - "query": { - "_id": 2 - }, - "update": { - "x": 2 - }, - "writeConcern": null - } - } - }, + ], + "outcome": [ { - "command_started_event": { - "command": { - "findAndModify": "default_write_concern_coll", - "query": { - "_id": 2 - }, - "remove": true, - "writeConcern": null + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 1 } - } + ] } ] } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.yml b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.yml index dccb7e0d0..eb109cb9f 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.yml +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.2.yml @@ -1,58 +1,91 @@ -# Test that setting a default write concern does not add a write concern -# to the command sent over the wire. -# Test operations that require 3.2+ server, where findAndModify started -# to accept a write concern. +# Test that setting a default write concern does not add a write concern to the command sent over the wire. +# Test operations that require 3.2+ server, where findAndModify started to accept a write concern. -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} -collection_name: &collection_name default_write_concern_coll -database_name: &database_name default_write_concern_db +description: default-write-concern-3.2 -runOn: - - minServerVersion: "3.2" +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "3.2" + +createEntities: + - + client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name default-write-concern-tests + databaseOptions: + writeConcern: {} + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + collectionOptions: + writeConcern: {} + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } tests: - - description: 'findAndModify operations omit default write concern' + - + description: findAndModify operations omit default write concern operations: - - name: findOneAndUpdate - object: collection - collectionOptions: {writeConcern: {}} + - + name: findOneAndUpdate + object: *collection0 arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - - name: findOneAndReplace - object: collection - collectionOptions: {writeConcern: {}} + filter: { _id: 1 } + update: { $set: { x: 1 } } + - + name: findOneAndReplace + object: *collection0 arguments: - filter: {_id: 2} - replacement: {x: 2} - - name: findOneAndDelete - object: collection - collectionOptions: {writeConcern: {}} + filter: { _id: 2 } + replacement: { x: 2 } + - + name: findOneAndDelete + object: *collection0 arguments: - filter: {_id: 2} + filter: { _id: 2 } + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 1 } + update: { $set: { x: 1 } } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 2 } + update: { x: 2 } + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 2 } + remove: true + writeConcern: { $$exists: false } outcome: - collection: - name: *collection_name - data: - - {_id: 1, x: 1} - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 1} - update: {$set: {x: 1}} - writeConcern: null - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 2} - update: {x: 2} - writeConcern: null - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 2} - remove: true - writeConcern: null \ No newline at end of file + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 1 } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.json b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.json index 6519f6f08..e18cdfc0c 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.json +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.json @@ -1,30 +1,68 @@ { - "data": [ + "description": "default-write-concern-3.4", + "schemaVersion": "1.4", + "runOnRequirements": [ { - "_id": 1, - "x": 11 + "minServerVersion": "3.4" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default-write-concern-tests", + "databaseOptions": { + "writeConcern": {} + } + } }, { - "_id": 2, - "x": 22 + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll", + "collectionOptions": { + "writeConcern": {} + } + } } ], - "collection_name": "default_write_concern_coll", - "database_name": "default_write_concern_db", - "runOn": [ + "initialData": [ { - "minServerVersion": "3.4" + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] } ], "tests": [ { "description": "Aggregate with $out omits default write concern", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "name": "aggregate", "arguments": { "pipeline": [ @@ -42,37 +80,45 @@ } } ], - "outcome": { - "collection": { - "name": "other_collection_name", - "data": [ + "expectEvents": [ + { + "client": "client0", + "events": [ { - "_id": 2, - "x": 22 + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_collection_name" + } + ], + "writeConcern": { + "$$exists": false + } + } + } } ] } - }, - "expectations": [ + ], + "outcome": [ { - "command_started_event": { - "command": { - "aggregate": "default_write_concern_coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$out": "other_collection_name" - } - ], - "writeConcern": null + "collectionName": "other_collection_name", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 2, + "x": 22 } - } + ] } ] }, @@ -80,39 +126,43 @@ "description": "RunCommand with a write command omits default write concern (runCommand should never inherit write concern)", "operations": [ { - "object": "database", - "databaseOptions": { - "writeConcern": {} - }, + "object": "database0", "name": "runCommand", - "command_name": "delete", "arguments": { "command": { - "delete": "default_write_concern_coll", + "delete": "coll", "deletes": [ { "q": {}, "limit": 1 } ] - } + }, + "commandName": "delete" } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "delete": "default_write_concern_coll", - "deletes": [ - { - "q": {}, - "limit": 1 + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "coll", + "deletes": [ + { + "q": {}, + "limit": 1 + } + ], + "writeConcern": { + "$$exists": false + } } - ], - "writeConcern": null + } } - } + ] } ] }, @@ -120,10 +170,7 @@ "description": "CreateIndex and dropIndex omits default write concern", "operations": [ { - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "name": "createIndex", "arguments": { "keys": { @@ -132,53 +179,61 @@ } }, { - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "name": "dropIndex", "arguments": { "name": "x_1" } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "createIndexes": "default_write_concern_coll", - "indexes": [ - { - "name": "x_1", - "key": { - "x": 1 + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "createIndexes": "coll", + "indexes": [ + { + "name": "x_1", + "key": { + "x": 1 + } + } + ], + "writeConcern": { + "$$exists": false } } - ], - "writeConcern": null - } - } - }, - { - "command_started_event": { - "command": { - "dropIndexes": "default_write_concern_coll", - "index": "x_1", - "writeConcern": null + } + }, + { + "commandStartedEvent": { + "command": { + "dropIndexes": "coll", + "index": "x_1", + "writeConcern": { + "$$exists": false + } + } + } } - } + ] } ] }, { "description": "MapReduce omits default write concern", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], "operations": [ { "name": "mapReduce", - "object": "collection", - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "arguments": { "map": { "$code": "function inc() { return emit(0, this.x + 1) }" @@ -192,23 +247,30 @@ } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "mapReduce": "default_write_concern_coll", - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - }, - "writeConcern": null + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "mapReduce": "coll", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + }, + "writeConcern": { + "$$exists": false + } + } + } } - } + ] } ] } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.yml b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.yml index c7b586cad..759620886 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.yml +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-3.4.yml @@ -1,95 +1,144 @@ -# Test that setting a default write concern does not add a write concern -# to the command sent over the wire. -# Test operations that require 3.4+ server, where all commands started -# to accept a write concern. +# Test that setting a default write concern does not add a write concern to the command sent over the wire. +# Test operations that require 3.4+ server, where all commands started to accept a write concern. -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} -collection_name: &collection_name default_write_concern_coll -database_name: &database_name default_write_concern_db +description: default-write-concern-3.4 -runOn: - - minServerVersion: "3.4" +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "3.4" + +createEntities: + - + client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name default-write-concern-tests + databaseOptions: + writeConcern: {} + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + collectionOptions: + writeConcern: {} + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } tests: - - description: Aggregate with $out omits default write concern + - + description: Aggregate with $out omits default write concern + # Serverless does not support $out stage + runOnRequirements: + - serverless: forbid operations: - - object: collection - collectionOptions: {writeConcern: {}} + - + object: *collection0 name: aggregate arguments: pipeline: &out_pipeline - - $match: {_id: {$gt: 1}} + - $match: { _id: { $gt: 1 } } - $out: &other_collection_name "other_collection_name" + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: *out_pipeline + writeConcern: { $$exists: false } outcome: - collection: - name: *other_collection_name - data: - - {_id: 2, x: 22} - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: *out_pipeline - writeConcern: null - - description: RunCommand with a write command omits default write concern (runCommand should never inherit write concern) + - + collectionName: *other_collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: RunCommand with a write command omits default write concern (runCommand should never inherit write concern) operations: - - object: database - databaseOptions: {writeConcern: {}} + - + object: *database0 name: runCommand - command_name: delete arguments: command: delete: *collection_name - deletes: - - {q: {}, limit: 1} - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - {q: {}, limit: 1} - writeConcern: null - - description: CreateIndex and dropIndex omits default write concern + deletes: [ { q: {}, limit: 1 } ] + commandName: delete + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: [ { q: {}, limit: 1 } ] + writeConcern: { $$exists: false } + - + description: CreateIndex and dropIndex omits default write concern operations: - - object: collection - collectionOptions: {writeConcern: {}} + - + object: *collection0 name: createIndex arguments: - keys: {x: 1} - - object: collection - collectionOptions: {writeConcern: {}} + keys: { x: 1 } + - + object: *collection0 name: dropIndex arguments: name: x_1 - expectations: - - command_started_event: - command: - createIndexes: *collection_name - indexes: - - name: x_1 - key: {x: 1} - writeConcern: null - - command_started_event: - command: - dropIndexes: *collection_name - index: x_1 - writeConcern: null - - description: MapReduce omits default write concern + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + createIndexes: *collection_name + indexes: [ { name: "x_1", key: { x: 1 } } ] + writeConcern: { $$exists: false } + - + commandStartedEvent: + command: + dropIndexes: *collection_name + index: x_1 + writeConcern: { $$exists: false } + - + description: MapReduce omits default write concern + # Serverless does not support mapReduce operation + runOnRequirements: + - serverless: forbid operations: - - name: mapReduce - object: collection - collectionOptions: {writeConcern: {}} + - + name: mapReduce + object: *collection0 arguments: - map: { $code: 'function inc() { return emit(0, this.x + 1) }' } - reduce: { $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' } + map: { $code: "function inc() { return emit(0, this.x + 1) }" } + reduce: { $code: "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" } out: { inline: 1 } - expectations: - - command_started_event: - command: - mapReduce: *collection_name - map: { $code: 'function inc() { return emit(0, this.x + 1) }' } - reduce: { $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' } - out: { inline: 1 } - writeConcern: null \ No newline at end of file + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + mapReduce: *collection_name + map: { $code: "function inc() { return emit(0, this.x + 1) }" } + reduce: { $code: "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" } + out: { inline: 1 } + writeConcern: { $$exists: false } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.json b/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.json index fef192d1a..e8bb78d91 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.json +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.json @@ -1,19 +1,55 @@ { - "data": [ + "description": "default-write-concern-4.2", + "schemaVersion": "1.0", + "runOnRequirements": [ { - "_id": 1, - "x": 11 + "minServerVersion": "4.2" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } }, { - "_id": 2, - "x": 22 + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default-write-concern-tests", + "databaseOptions": { + "writeConcern": {} + } + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll", + "collectionOptions": { + "writeConcern": {} + } + } } ], - "collection_name": "default_write_concern_coll", - "database_name": "default_write_concern_db", - "runOn": [ + "initialData": [ { - "minServerVersion": "4.2" + "collectionName": "coll", + "databaseName": "default-write-concern-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] } ], "tests": [ @@ -21,13 +57,7 @@ "description": "Aggregate with $merge omits default write concern", "operations": [ { - "object": "collection", - "databaseOptions": { - "writeConcern": {} - }, - "collectionOptions": { - "writeConcern": {} - }, + "object": "collection0", "name": "aggregate", "arguments": { "pipeline": [ @@ -47,41 +77,49 @@ } } ], - "expectations": [ + "expectEvents": [ { - "command_started_event": { - "command": { - "aggregate": "default_write_concern_coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_collection_name" + } } - } - }, - { - "$merge": { - "into": "other_collection_name" + ], + "writeConcern": { + "$$exists": false } } - ], - "writeConcern": null + } } - } + ] } ], - "outcome": { - "collection": { - "name": "other_collection_name", - "data": [ + "outcome": [ + { + "collectionName": "other_collection_name", + "databaseName": "default-write-concern-tests", + "documents": [ { "_id": 2, "x": 22 } ] } - } + ] } ] } diff --git a/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.yml b/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.yml index 6039b5581..3ce742e65 100644 --- a/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.yml +++ b/src/test/spec/json/read-write-concern/operation/default-write-concern-4.2.yml @@ -1,36 +1,66 @@ -# Test that setting a default write concern does not add a write concern -# to the command sent over the wire. +# Test that setting a default write concern does not add a write concern to the command sent over the wire. # Test operations that require 4.2+ server. -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} -collection_name: &collection_name default_write_concern_coll -database_name: &database_name default_write_concern_db +description: default-write-concern-4.2 -runOn: - - minServerVersion: "4.2" +schemaVersion: "1.0" + +runOnRequirements: + - minServerVersion: "4.2" + +createEntities: + - + client: + id: &client0 client0 + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name default-write-concern-tests + databaseOptions: + writeConcern: {} + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + collectionOptions: + writeConcern: {} + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } tests: - - description: Aggregate with $merge omits default write concern + - + description: Aggregate with $merge omits default write concern operations: - - object: collection - databaseOptions: {writeConcern: {}} - collectionOptions: {writeConcern: {}} + - + object: *collection0 name: aggregate arguments: pipeline: &merge_pipeline - - $match: {_id: {$gt: 1}} - - $merge: {into: &other_collection_name "other_collection_name" } - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: *merge_pipeline - # "null" fields will be checked for non-existence - writeConcern: null + - $match: { _id: { $gt: 1 } } + - $merge: { into: &other_collection_name "other_collection_name" } + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: *merge_pipeline + writeConcern: { $$exists: false } outcome: - collection: - name: *other_collection_name - data: - - {_id: 2, x: 22} \ No newline at end of file + - + collectionName: *other_collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-reads/README.md b/src/test/spec/json/retryable-reads/README.md new file mode 100644 index 000000000..1ae4dedc9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/README.md @@ -0,0 +1,153 @@ +# Retryable Reads Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +retryable reads. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +## Prose Tests + +### 1. PoolClearedError Retryability Test + +This test will be used to ensure drivers properly retry after encountering PoolClearedErrors. It MUST be implemented by +any driver that implements the CMAP specification. This test requires MongoDB 4.2.9+ for `blockConnection` support in +the failpoint. + +1. Create a client with maxPoolSize=1 and retryReads=true. If testing against a sharded deployment, be sure to connect + to only a single mongos. + +2. Enable the following failpoint: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["find"], + errorCode: 91, + blockConnection: true, + blockTimeMS: 1000 + } + } + ``` + +3. Start two threads and attempt to perform a `findOne` simultaneously on both. + +4. Verify that both `findOne` attempts succeed. + +5. Via CMAP monitoring, assert that the first check out succeeds. + +6. Via CMAP monitoring, assert that a PoolClearedEvent is then emitted. + +7. Via CMAP monitoring, assert that the second check out then fails due to a connection error. + +8. Via Command Monitoring, assert that exactly three `find` CommandStartedEvents were observed in total. + +9. Disable the failpoint. + +### 2. Retrying Reads in a Sharded Cluster + +These tests will be used to ensure drivers properly retry reads on a different mongos. + +Note: this test cannot reliably distinguish "retry on a different mongos due to server deprioritization" (the behavior +intended to be tested) from "retry on a different mongos due to normal SDAM behavior of randomized suitable server +selection". Verify relevant code paths are correctly executed by the tests using external means such as a logging, +debugger, code coverage tool, etc. + +#### 2.1 Retryable Reads Are Retried on a Different mongos When One is Available + +This test MUST be executed against a sharded cluster that has at least two mongos instances, supports `retryReads=true`, +and has enabled the `configureFailPoint` command (MongoDB 4.2+). + +1. Create two clients `s0` and `s1` that each connect to a single mongos from the sharded cluster. They must not connect + to the same mongos. + +2. Configure the following fail point for both `s0` and `s1`: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["find"], + errorCode: 6 + } + } + ``` + +3. Create a client `client` with `retryReads=true` that connects to the cluster using the same two mongoses as `s0` and + `s1`. + +4. Enable failed command event monitoring for `client`. + +5. Execute a `find` command with `client`. Assert that the command failed. + +6. Assert that two failed command events occurred. Assert that both events occurred on different mongoses. + +7. Disable the fail point on both `s0` and `s1`. + +#### 2.2 Retryable Reads Are Retried on the Same mongos When No Others are Available + +This test MUST be executed against a sharded cluster that supports `retryReads=true` and has enabled the +`configureFailPoint` command (MongoDB 4.2+). + +1. Create a client `s0` that connects to a single mongos from the cluster. + +2. Configure the following fail point for `s0`: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["find"], + errorCode: 6 + } + } + ``` + +3. Create a client `client` with `directConnection=false` (when not set by default) and `retryReads=true` that connects + to the cluster using the same single mongos as `s0`. + +4. Enable succeeded and failed command event monitoring for `client`. + +5. Execute a `find` command with `client`. Assert that the command succeeded. + +6. Assert that exactly one failed command event and one succeeded command event occurred. Assert that both events + occurred on the same mongos. + +7. Disable the fail point on `s0`. + +## Changelog + +- 2024-04-30: Migrated from reStructuredText to Markdown. + +- 2024-03-06: Convert legacy retryable reads tests to unified format. + +- 2024-02-21: Update mongos redirection prose tests to workaround SDAM behavior preventing execution of deprioritization + code paths. + +- 2023-08-26: Add prose tests for retrying in a sharded cluster. + +- 2022-04-22: Clarifications to `serverless` and `useMultipleMongoses`. + +- 2022-01-10: Create legacy and unified subdirectories for new unified tests + +- 2021-08-27: Clarify behavior of `useMultipleMongoses` for `LoadBalanced` topologies. + +- 2019-03-19: Add top-level `runOn` field to denote server version and/or topology requirements requirements for the + test file. Removes the `minServerVersion` and `topology` top-level fields, which are now expressed within `runOn` + elements. + + Add test-level `useMultipleMongoses` field. + +- 2020-09-16: Suggest lowering heartbeatFrequencyMS in addition to minHeartbeatFrequencyMS. + +- 2021-03-23: Add prose test for retrying PoolClearedErrors + +- 2021-04-29: Add `load-balanced` to test topology requirements. diff --git a/src/test/spec/json/retryable-reads/README.rst b/src/test/spec/json/retryable-reads/README.rst deleted file mode 100644 index 59e09e69b..000000000 --- a/src/test/spec/json/retryable-reads/README.rst +++ /dev/null @@ -1,256 +0,0 @@ -===================== -Retryable Reads Tests -===================== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in the ``legacy`` and ``unified`` sub-directories are platform-independent tests -that drivers can use to prove their conformance to the Retryable Reads spec. Tests in the -``unified`` directory are written using the `Unified Test Format <../../unified-test-format/unified-test-format.rst>`_. -Tests in the ``legacy`` directory are written using the format described below. - -Prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Tests will require a MongoClient created with options defined in the tests. -Integration tests will require a running MongoDB cluster with server versions -4.0 or later. - -N.B. The spec specifies 3.6 as the minimum server version: however, -``failCommand`` is not supported on 3.6, so for now, testing requires MongoDB -4.0. Once `DRIVERS-560`_ is resolved, we will attempt to adapt its live failure -integration tests to test Retryable Reads on MongoDB 3.6. - -.. _DRIVERS-560: https://jira.mongodb.org/browse/DRIVERS-560 - -Server Fail Point -================= - -See: `Server Fail Point`_ in the Transactions spec test suite. - -.. _Server Fail Point: ../../transactions/tests#server-fail-point - -Disabling Fail Point after Test Execution ------------------------------------------ - -After each test that configures a fail point, drivers should disable the -``failCommand`` fail point to avoid spurious failures in -subsequent tests. The fail point may be disabled like so:: - - db.runCommand({ - configureFailPoint: "failCommand", - mode: "off" - }); - -Network Error Tests -=================== - -Network error tests are expressed in YAML and should be run against a standalone, -shard cluster, or single-node replica set. - - -Test Format ------------ - -Each YAML file has the following keys: - -- ``runOn`` (optional): An array of server version and/or topology requirements - for which the tests can be run. If the test environment satisfies one or more - of these requirements, the tests may be executed; otherwise, this file should - be skipped. If this field is omitted, the tests can be assumed to have no - particular requirements and should be executed. Each element will have some or - all of the following fields: - - - ``minServerVersion`` (optional): The minimum server version (inclusive) - required to successfully run the tests. If this field is omitted, it should - be assumed that there is no lower bound on the required server version. - - - ``maxServerVersion`` (optional): The maximum server version (inclusive) - against which the tests can be run successfully. If this field is omitted, - it should be assumed that there is no upper bound on the required server - version. - - - ``topology`` (optional): An array of server topologies against which the - tests can be run successfully. Valid topologies are "single", - "replicaset", "sharded", and "load-balanced". If this field is omitted, - the default is all topologies (i.e. ``["single", "replicaset", "sharded", - "load-balanced"]``). - - - ``serverless``: (optional): Whether or not the test should be run on Atlas - Serverless instances. Valid values are "require", "forbid", and "allow". If - "require", the test MUST only be run on Atlas Serverless instances. If - "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted - or "allow", this option has no effect. - - The test runner MUST be informed whether or not Atlas Serverless is being - used in order to determine if this requirement is met (e.g. through an - environment variable or configuration option). - - Note: the Atlas Serverless proxy imitates mongos, so the test runner is not - capable of determining if Atlas Serverless is in use by issuing commands - such as ``buildInfo`` or ``hello``. Furthermore, connections to Atlas - Serverless use a load balancer, so the topology will appear as - "load-balanced". - -- ``database_name`` and ``collection_name``: Optional. The database and - collection to use for testing. - -- ``bucket_name``: Optional. The GridFS bucket name to use for testing. - -- ``data``: The data that should exist in the collection(s) under test before - each test run. This will typically be an array of documents to be inserted - into the collection under test (i.e. ``collection_name``); however, this field - may also be an object mapping collection names to arrays of documents to be - inserted into the specified collection. - -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some or all of the following fields: - - - ``description``: The name of the test. - - - ``clientOptions``: Optional, parameters to pass to MongoClient(). - - - ``useMultipleMongoses`` (optional): If ``true``, and the topology type is - ``Sharded``, the MongoClient for this test should be initialized with multiple - mongos seed addresses. If ``false`` or omitted, only a single mongos address - should be specified. - - If ``true``, the topology type is ``LoadBalanced``, and Atlas Serverless is - not being used, the MongoClient for this test should be initialized with the - URI of the load balancer fronting multiple servers. If ``false`` or omitted, - the MongoClient for this test should be initialized with the URI of the load - balancer fronting a single server. - - ``useMultipleMongoses`` only affects ``Sharded`` and ``LoadBalanced`` - topologies (excluding Atlas Serverless). - - - ``skipReason``: Optional, string describing why this test should be skipped. - - - ``failPoint``: Optional, a server fail point to enable, expressed as the - configureFailPoint command to run on the admin database. - - - ``operations``: An array of documents describing an operation to be - executed. Each document has the following fields: - - - ``name``: The name of the operation on ``object``. - - - ``object``: The name of the object to perform the operation on. Can be - "database", "collection", "client", or "gridfsbucket." - - - ``arguments``: Optional, the names and values of arguments. - - - ``result``: Optional. The return value from the operation, if any. This - field may be a scalar (e.g. in the case of a count), a single document, or - an array of documents in the case of a multi-document read. - - - ``error``: Optional. If ``true``, the test should expect an error or - exception. - - - ``expectations``: Optional list of command-started events. - -GridFS Tests ------------- - -GridFS tests are denoted by when the YAML file contains ``bucket_name``. -The ``data`` field will also be an object, which maps collection names -(e.g. ``fs.files``) to an array of documents that should be inserted into -the specified collection. - -``fs.files`` and ``fs.chunks`` should be created in the database -specified by ``database_name``. This could be done via inserts or by -creating GridFSBuckets—using the GridFS ``bucketName`` (see -`GridFSBucket spec`_) specified by ``bucket_name`` field in the YAML -file—and calling ``upload_from_stream_with_id`` with the appropriate -data. - -``Download`` tests should be tested against ``GridFS.download_to_stream``. -``DownloadByName`` tests should be tested against -``GridFS.download_to_stream_by_name``. - - -.. _GridFSBucket spec: https://github.com/mongodb/specifications/blob/master/source/gridfs/gridfs-spec.rst#configurable-gridfsbucket-class - - -Speeding Up Tests ------------------ - -Drivers can greatly reduce the execution time of tests by setting `heartbeatFrequencyMS`_ -and `minHeartbeatFrequencyMS`_ (internally) to a small value (e.g. 5ms), below what -is normally permitted in the SDAM spec. If a test specifies an explicit value for -heartbeatFrequencyMS (e.g. client or URI options), drivers MUST use that value. - -.. _minHeartbeatFrequencyMS: ../../server-discovery-and-monitoring/server-discovery-and-monitoring.rst#minheartbeatfrequencyms -.. _heartbeatFrequencyMS: ../../server-discovery-and-monitoring/server-discovery-and-monitoring.rst#heartbeatfrequencyms - -Optional Enumeration Commands -============================= - -A driver only needs to test the optional enumeration commands it has chosen to -implement (e.g. ``Database.listCollectionNames()``). - -PoolClearedError Retryability Test -================================== - -This test will be used to ensure drivers properly retry after encountering PoolClearedErrors. -It MUST be implemented by any driver that implements the CMAP specification. -This test requires MongoDB 4.2.9+ for ``blockConnection`` support in the failpoint. - -1. Create a client with maxPoolSize=1 and retryReads=true. If testing against a - sharded deployment, be sure to connect to only a single mongos. - -2. Enable the following failpoint:: - - { - configureFailPoint: "failCommand", - mode: { times: 1 }, - data: { - failCommands: ["find"], - errorCode: 91, - blockConnection: true, - blockTimeMS: 1000 - } - } - -3. Start two threads and attempt to perform a ``findOne`` simultaneously on both. - -4. Verify that both ``findOne`` attempts succeed. - -5. Via CMAP monitoring, assert that the first check out succeeds. - -6. Via CMAP monitoring, assert that a PoolClearedEvent is then emitted. - -7. Via CMAP monitoring, assert that the second check out then fails due to a - connection error. - -8. Via Command Monitoring, assert that exactly three ``find`` CommandStartedEvents - were observed in total. - -9. Disable the failpoint. - - -Changelog -========= - -:2022-04-22: Clarifications to ``serverless`` and ``useMultipleMongoses``. - -:2022-01-10: Create legacy and unified subdirectories for new unified tests - -:2021-08-27: Clarify behavior of ``useMultipleMongoses`` for ``LoadBalanced`` topologies. - -:2019-03-19: Add top-level ``runOn`` field to denote server version and/or - topology requirements requirements for the test file. Removes the - ``minServerVersion`` and ``topology`` top-level fields, which are - now expressed within ``runOn`` elements. - - Add test-level ``useMultipleMongoses`` field. - -:2020-09-16: Suggest lowering heartbeatFrequencyMS in addition to minHeartbeatFrequencyMS. - -:2021-03-23: Add prose test for retrying PoolClearedErrors - -:2021-04-29: Add ``load-balanced`` to test topology requirements. diff --git a/src/test/spec/json/retryable-reads/etc/templates/handshakeError.yml.template b/src/test/spec/json/retryable-reads/etc/templates/handshakeError.yml.template new file mode 100644 index 000000000..f759e42df --- /dev/null +++ b/src/test/spec/json/retryable-reads/etc/templates/handshakeError.yml.template @@ -0,0 +1,159 @@ +# Tests in this file are generated from handshakeError.yml.template. + +description: "retryable reads handshake failures" + +# 1.4 is required for "serverless: forbid". +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "4.2" + topologies: [replicaset, sharded, load-balanced] + auth: true + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - connectionCheckOutStartedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + - database: + id: &database database + client: *client + databaseName: &databaseName retryable-reads-handshake-tests + - collection: + id: &collection collection + database: *database + collectionName: &collectionName coll + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + # Because setting a failPoint creates a connection in the connection pool, run + # a ping operation that fails immediately after the failPoint operation in + # order to discard the connection before running the actual operation to be + # tested. The saslContinue command is used to avoid SDAM errors. + # + # Description of events: + # - Failpoint operation. + # - Creates a connection in the connection pool that must be closed. + # - Ping operation. + # - Triggers failpoint (first time). + # - Closes the connection made by the fail point operation. + # - Test operation. + # - New connection is created. + # - Triggers failpoint (second time). + # - Tests whether operation successfully retries the handshake and succeeds. +{% for operation in operations %} + - description: "{{operation.object}}.{{operation.operation_name}} succeeds after retryable handshake network error" + {%- if ((operation.operation_name == 'createChangeStream') or + (operation.operation_name == 'aggregate' and operation.object == 'database')) %} + runOnRequirements: + - serverless: forbid + {%- endif %} + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: {{operation.operation_name}} + object: *{{operation.object}} + {%- if operation.arguments|length > 0 %} + arguments: + {%- for arg in operation.arguments %} + {{arg}} + {%- endfor -%} + {%- endif %} + {%- if operation.operation_name == "createChangeStream" %} + saveResultAsEntity: changeStream + {%- endif %} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: {{operation.command_name}} + - commandSucceededEvent: + commandName: {{operation.command_name}} + + - description: "{{operation.object}}.{{operation.operation_name}} succeeds after retryable handshake server error (ShutdownInProgress)" + {%- if ((operation.operation_name == 'createChangeStream') or + (operation.operation_name == 'aggregate' and operation.object == 'database')) %} + runOnRequirements: + - serverless: forbid + {%- endif %} + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: {{operation.operation_name}} + object: *{{operation.object}} + {%- if operation.arguments|length > 0 %} + arguments: + {%- for arg in operation.arguments %} + {{arg}} + {%- endfor -%} + {%- endif %} + {%- if operation.operation_name == "createChangeStream" %} + saveResultAsEntity: changeStream + {%- endif %} + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: {{operation.command_name}} + - commandSucceededEvent: + commandName: {{operation.command_name}} +{% endfor -%} diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate-merge.json b/src/test/spec/json/retryable-reads/legacy/aggregate-merge.json deleted file mode 100644 index b401d741b..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate-merge.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.11" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Aggregate with $merge does not retry", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "object": "collection", - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - }, - { - "$merge": { - "into": "output-collection" - } - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - }, - { - "$merge": { - "into": "output-collection" - } - } - ] - }, - "command_name": "aggregate", - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate-merge.yml b/src/test/spec/json/retryable-reads/legacy/aggregate-merge.yml deleted file mode 100644 index f9913348f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate-merge.yml +++ /dev/null @@ -1,39 +0,0 @@ -runOn: - - - minServerVersion: "4.1.11" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Aggregate with $merge does not retry" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [aggregate] - closeConnection: true - operations: - - - object: collection - name: aggregate - arguments: - pipeline: &pipeline - - $match: {_id: {$gt: 1}} - - $sort: {x: 1} - - $merge: { into: "output-collection" } - error: true - expectations: - - - command_started_event: - command: - aggregate: *collection_name - pipeline: *pipeline - command_name: aggregate - database_name: *database_name diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.json deleted file mode 100644 index 1155f808d..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.json +++ /dev/null @@ -1,1208 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Aggregate succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.yml deleted file mode 100644 index bf9fc0189..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate-serverErrors.yml +++ /dev/null @@ -1,157 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Aggregate succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [aggregate], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: aggregate - object: collection - arguments: - pipeline: - - $match: - _id: {$gt: 1} - - $sort: {x: 1} - result: - - {_id: 2, x: 22} - - {_id: 3, x: 33} - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - pipeline: [{$match: {_id: {$gt: 1}}}, {$sort: {x: 1}}] - database_name: *database_name - - *retryable_command_started_event - - - description: "Aggregate succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [aggregate], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate.json b/src/test/spec/json/retryable-reads/legacy/aggregate.json deleted file mode 100644 index f23d5c679..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate.json +++ /dev/null @@ -1,406 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Aggregate succeeds on first attempt", - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Aggregate with $out does not retry", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - }, - { - "$out": "output-collection" - } - ] - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$sort": { - "x": 1 - } - }, - { - "$out": "output-collection" - } - ] - }, - "command_name": "aggregate", - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/aggregate.yml b/src/test/spec/json/retryable-reads/legacy/aggregate.yml deleted file mode 100644 index de9c0d706..000000000 --- a/src/test/spec/json/retryable-reads/legacy/aggregate.yml +++ /dev/null @@ -1,87 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Aggregate succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: aggregate - object: collection - arguments: - pipeline: - - $match: {_id: {$gt: 1}} - - $sort: {x: 1} - result: - - {_id: 2, x: 22} - - {_id: 3, x: 33} - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - pipeline: [{$match: {_id: {$gt: 1}}}, {$sort: {x: 1}}] - database_name: *database_name - - - description: "Aggregate succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [aggregate] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "Aggregate fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Aggregate with $out does not retry" - failPoint: *failCommand_failPoint - operations: - - <<: *retryable_operation_fails - arguments: - pipeline: - - $match: {_id: {$gt: 1}} - - $sort: {x: 1} - - $out: "output-collection" - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: [{$match: {_id: {$gt: 1}}}, {$sort: {x: 1}}, {$out: 'output-collection'}] - command_name: aggregate - database_name: *database_name diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.json deleted file mode 100644 index 73dbfee91..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.json +++ /dev/null @@ -1,740 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "client.watch succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.yml deleted file mode 100644 index a1f106964..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch-serverErrors.yml +++ /dev/null @@ -1,150 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "client.watch succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [aggregate], errorCode: 11600 } - operations: - - &retryable_operation - name: watch - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: 1 - cursor: {} - pipeline: [ { $changeStream: { allChangesForCluster: true } } ] - database_name: admin - - *retryable_command_started_event - - - description: "client.watch succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [aggregate], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.json deleted file mode 100644 index 30a53037a..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.json +++ /dev/null @@ -1,209 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "client.watch succeeds on first attempt", - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - }, - { - "description": "client.watch fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": { - "allChangesForCluster": true - } - } - ] - }, - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.yml deleted file mode 100644 index ea7f7e069..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-client.watch.yml +++ /dev/null @@ -1,62 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - -tests: - - - description: "client.watch succeeds on first attempt" - operations: - - &retryable_operation - name: watch - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: 1 - cursor: {} - pipeline: [ { $changeStream: { "allChangesForCluster": true } } ] - database_name: admin - - - description: "client.watch succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [aggregate] - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "client.watch fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "client.watch fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.json deleted file mode 100644 index 77b3af04f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.json +++ /dev/null @@ -1,690 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "db.coll.watch succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.yml deleted file mode 100644 index 4e4bb4a1b..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch-serverErrors.yml +++ /dev/null @@ -1,150 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "db.coll.watch succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [aggregate], errorCode: 11600 } - operations: - - &retryable_operation - name: watch - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - cursor: {} - pipeline: [ { $changeStream: { } } ] - database_name: *database_name - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [aggregate], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.json deleted file mode 100644 index 27f6105a4..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "db.coll.watch succeeds on first attempt", - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.coll.watch fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.yml deleted file mode 100644 index c8334b1a9..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.coll.watch.yml +++ /dev/null @@ -1,66 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - -tests: - - - description: "db.coll.watch succeeds on first attempt" - operations: - - &retryable_operation - name: watch - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - cursor: {} - pipeline: [ { $changeStream: { } } ] - database_name: *database_name - - - description: "db.coll.watch succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - aggregate - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.coll.watch fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "db.coll.watch fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.json deleted file mode 100644 index 7a8753450..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.json +++ /dev/null @@ -1,690 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "db.watch succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "watch", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.yml deleted file mode 100644 index a527935ba..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch-serverErrors.yml +++ /dev/null @@ -1,154 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "db.watch succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [aggregate], errorCode: 11600 } - operations: - - &retryable_operation - name: watch - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: 1 - cursor: {} - pipeline: [ { $changeStream: { } } ] - database_name: *database_name - - *retryable_command_started_event - - - description: "db.watch succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [aggregate], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - - - diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.json b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.json deleted file mode 100644 index e6b0b9b78..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "db.watch succeeds on first attempt", - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "db.watch fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "watch", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": 1, - "cursor": {}, - "pipeline": [ - { - "$changeStream": {} - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.yml b/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.yml deleted file mode 100644 index e2ceacbb6..000000000 --- a/src/test/spec/json/retryable-reads/legacy/changeStreams-db.watch.yml +++ /dev/null @@ -1,62 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - -tests: - - - description: "db.watch succeeds on first attempt" - operations: - - &retryable_operation - name: watch - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: 1 - cursor: {} - pipeline: [ { $changeStream: { } } ] - database_name: *database_name - - - description: "db.watch succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [aggregate] - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "db.watch fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "db.watch fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/count-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/count-serverErrors.json deleted file mode 100644 index 36a0c17ca..000000000 --- a/src/test/spec/json/retryable-reads/legacy/count-serverErrors.json +++ /dev/null @@ -1,586 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "Count succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/count-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/count-serverErrors.yml deleted file mode 100644 index 48ceaea68..000000000 --- a/src/test/spec/json/retryable-reads/legacy/count-serverErrors.yml +++ /dev/null @@ -1,150 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "Count succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [count], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: count - object: collection - arguments: { filter: { } } - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - count: *collection_name - database_name: *database_name - - *retryable_command_started_event - - - description: "Count succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [count], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/count.json b/src/test/spec/json/retryable-reads/legacy/count.json deleted file mode 100644 index 139a54513..000000000 --- a/src/test/spec/json/retryable-reads/legacy/count.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "Count succeeds on first attempt", - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Count fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "count", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/count.yml b/src/test/spec/json/retryable-reads/legacy/count.yml deleted file mode 100644 index c9c3936b6..000000000 --- a/src/test/spec/json/retryable-reads/legacy/count.yml +++ /dev/null @@ -1,64 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "Count succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: count - object: collection - arguments: { filter: { } } - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - count: *collection_name - database_name: *database_name - - - description: "Count succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [count] - closeConnection: true - - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Count fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "Count fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.json deleted file mode 100644 index 782ea5e4f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.json +++ /dev/null @@ -1,911 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "CountDocuments succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.yml deleted file mode 100644 index 4ffb9a05b..000000000 --- a/src/test/spec/json/retryable-reads/legacy/countDocuments-serverErrors.yml +++ /dev/null @@ -1,150 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "CountDocuments succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [aggregate], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: countDocuments - object: collection - arguments: { filter: { } } - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - pipeline: [{'$match': {}}, {'$group': {'_id': 1, 'n': {'$sum': 1}}}] - database_name: *database_name - - *retryable_command_started_event - - - description: "CountDocuments succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [aggregate], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [aggregate], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/countDocuments.json b/src/test/spec/json/retryable-reads/legacy/countDocuments.json deleted file mode 100644 index 57a64e45b..000000000 --- a/src/test/spec/json/retryable-reads/legacy/countDocuments.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "CountDocuments succeeds on first attempt", - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "CountDocuments fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": {} - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "coll", - "pipeline": [ - { - "$match": {} - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ] - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/countDocuments.yml b/src/test/spec/json/retryable-reads/legacy/countDocuments.yml deleted file mode 100644 index 9e2565129..000000000 --- a/src/test/spec/json/retryable-reads/legacy/countDocuments.yml +++ /dev/null @@ -1,64 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "CountDocuments succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: countDocuments - object: collection - arguments: { filter: { } } - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - aggregate: *collection_name - pipeline: [{'$match': {}}, {'$group': {'_id': 1, 'n': {'$sum': 1}}}] - database_name: *database_name - - - description: "CountDocuments succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [aggregate] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "CountDocuments fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "CountDocuments fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.json deleted file mode 100644 index d7c6018a6..000000000 --- a/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.json +++ /dev/null @@ -1,838 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Distinct succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.yml deleted file mode 100644 index d4bc118ff..000000000 --- a/src/test/spec/json/retryable-reads/legacy/distinct-serverErrors.yml +++ /dev/null @@ -1,156 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Distinct succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [distinct], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: distinct - object: collection - arguments: { fieldName: "x", filter: { _id: { $gt: 1 } } } - result: - - 22 - - 33 - expectations: - - &retryable_command_started_event - command_started_event: - command: - distinct: *collection_name - key: "x" - query: - _id: {$gt: 1} - database_name: *database_name - - *retryable_command_started_event - - - description: "Distinct succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [distinct], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [distinct], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/distinct.json b/src/test/spec/json/retryable-reads/legacy/distinct.json deleted file mode 100644 index 1fd415da8..000000000 --- a/src/test/spec/json/retryable-reads/legacy/distinct.json +++ /dev/null @@ -1,245 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Distinct succeeds on first attempt", - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "result": [ - 22, - 33 - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Distinct fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "distinct" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "coll", - "key": "x", - "query": { - "_id": { - "$gt": 1 - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/distinct.yml b/src/test/spec/json/retryable-reads/legacy/distinct.yml deleted file mode 100644 index 8ca2ac831..000000000 --- a/src/test/spec/json/retryable-reads/legacy/distinct.yml +++ /dev/null @@ -1,71 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - -tests: - - - description: "Distinct succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: distinct - object: collection - arguments: { fieldName: "x", filter: { _id: { $gt: 1 } } } - result: - - 22 - - 33 - expectations: - - &retryable_command_started_event - command_started_event: - command: - distinct: *collection_name - key: "x" - query: - _id: {$gt: 1} - database_name: *database_name - - - description: "Distinct succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [distinct] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Distinct fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - - description: "Distinct fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.json deleted file mode 100644 index 6bb128f5f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.json +++ /dev/null @@ -1,546 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "EstimatedDocumentCount succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.yml deleted file mode 100644 index aefb89ca6..000000000 --- a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount-serverErrors.yml +++ /dev/null @@ -1,148 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "EstimatedDocumentCount succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [count], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: estimatedDocumentCount - object: collection - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - count: *collection_name - database_name: *database_name - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [count], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [count], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.json b/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.json deleted file mode 100644 index 8dfa15a2c..000000000 --- a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.json +++ /dev/null @@ -1,166 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "EstimatedDocumentCount succeeds on first attempt", - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "result": 2 - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "EstimatedDocumentCount fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "count" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "estimatedDocumentCount", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "count": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.yml b/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.yml deleted file mode 100644 index de03e483b..000000000 --- a/src/test/spec/json/retryable-reads/legacy/estimatedDocumentCount.yml +++ /dev/null @@ -1,62 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "EstimatedDocumentCount succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: estimatedDocumentCount - object: collection - result: 2 - expectations: - - &retryable_command_started_event - command_started_event: - command: - count: *collection_name - database_name: *database_name - - - description: "EstimatedDocumentCount succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [count] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "EstimatedDocumentCount fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "EstimatedDocumentCount fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/find-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/find-serverErrors.json deleted file mode 100644 index f6b96c6dc..000000000 --- a/src/test/spec/json/retryable-reads/legacy/find-serverErrors.json +++ /dev/null @@ -1,962 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "Find succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/find-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/find-serverErrors.yml deleted file mode 100644 index e122f3e2c..000000000 --- a/src/test/spec/json/retryable-reads/legacy/find-serverErrors.yml +++ /dev/null @@ -1,160 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - {_id: 5, x: 55} - -tests: - - - description: "Find succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [find], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: find - object: collection - arguments: { filter: {}, sort: { _id: 1 }, limit: 4 } - result: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: *collection_name - filter: {} - sort: {_id: 1} - limit: 4 - database_name: *database_name - - *retryable_command_started_event - - - description: "Find succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [find], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/find.json b/src/test/spec/json/retryable-reads/legacy/find.json deleted file mode 100644 index 00d419c0d..000000000 --- a/src/test/spec/json/retryable-reads/legacy/find.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "Find succeeds on first attempt", - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds on second attempt with explicit clientOptions", - "clientOptions": { - "retryReads": true - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Find fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "find", - "object": "collection", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4 - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/find.yml b/src/test/spec/json/retryable-reads/legacy/find.yml deleted file mode 100644 index b685c07b6..000000000 --- a/src/test/spec/json/retryable-reads/legacy/find.yml +++ /dev/null @@ -1,86 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - {_id: 5, x: 55} - -tests: - - - description: "Find succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: find - object: collection - arguments: - filter: {} - sort: {_id: 1} - limit: 4 - result: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: *collection_name - filter: {} - sort: {_id: 1} - limit: 4 - database_name: *database_name - - - description: "Find succeeds on second attempt with explicit clientOptions" - clientOptions: - retryReads: true - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [find] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find succeeds on second attempt" - failPoint: *failCommand_failPoint - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Find fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "Find fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.json deleted file mode 100644 index d039ef247..000000000 --- a/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.json +++ /dev/null @@ -1,732 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "FindOne succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.yml deleted file mode 100644 index b6e765740..000000000 --- a/src/test/spec/json/retryable-reads/legacy/findOne-serverErrors.yml +++ /dev/null @@ -1,154 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - {_id: 5, x: 55} - -tests: - - - description: "FindOne succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [find], errorCode: 11600 } - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: findOne - object: collection - arguments: - filter: {_id: 1} - result: {_id: 1, x: 11} - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: *collection_name - filter: {_id: 1} - database_name: *database_name - - *retryable_command_started_event - - - description: "FindOne succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 11602 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13435 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13436 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 189 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 91 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 7 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 6 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 89 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 9001 } - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [find], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/findOne.json b/src/test/spec/json/retryable-reads/legacy/findOne.json deleted file mode 100644 index b9deb73d2..000000000 --- a/src/test/spec/json/retryable-reads/legacy/findOne.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "FindOne succeeds on first attempt", - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": { - "_id": 1, - "x": 11 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "FindOne fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "findOne", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "coll", - "filter": { - "_id": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/findOne.yml b/src/test/spec/json/retryable-reads/legacy/findOne.yml deleted file mode 100644 index 911314e98..000000000 --- a/src/test/spec/json/retryable-reads/legacy/findOne.yml +++ /dev/null @@ -1,68 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 11} - - {_id: 2, x: 22} - - {_id: 3, x: 33} - - {_id: 4, x: 44} - - {_id: 5, x: 55} - -tests: - - - description: "FindOne succeeds on first attempt" - operations: - - &retryable_operation_succeeds - <<: &retryable_operation - name: findOne - object: collection - arguments: {filter: {_id: 1 }} - result: {_id: 1, x: 11} - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: *collection_name - filter: {_id: 1} - database_name: *database_name - - - description: "FindOne succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [find] - closeConnection: true - operations: [*retryable_operation_succeeds] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "FindOne fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "FindOne fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.json deleted file mode 100644 index cec3a5016..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.json +++ /dev/null @@ -1,925 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "bucket_name": "fs", - "data": { - "fs.files": [ - { - "_id": { - "$oid": "000000000000000000000001" - }, - "length": 1, - "chunkSize": 4, - "uploadDate": { - "$date": "1970-01-01T00:00:00.000Z" - }, - "filename": "abc", - "metadata": {} - } - ], - "fs.chunks": [ - { - "_id": { - "$oid": "000000000000000000000002" - }, - "files_id": { - "$oid": "000000000000000000000001" - }, - "n": 0, - "data": { - "$binary": { - "base64": "EQ==", - "subType": "00" - } - } - } - ] - }, - "tests": [ - { - "description": "Download succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.yml deleted file mode 100644 index e120c162f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-download-serverErrors.yml +++ /dev/null @@ -1,173 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -bucket_name: "fs" - -data: - fs.files: - - - _id: { $oid : "000000000000000000000001" } - length: 1 - chunkSize: 4 - uploadDate: { $date : "1970-01-01T00:00:00.000Z" } - filename: abc - metadata: {} - fs.chunks: - - { _id: { $oid: "000000000000000000000002" }, files_id: { $oid: "000000000000000000000001" }, n: 0, data: { $binary: { base64: "EQ==", subType: "00" } } } - -tests: - - - description: "Download succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [find], errorCode: 11600 } - operations: - - &retryable_operation - name: download - object: gridfsbucket - arguments: { id: { "$oid" : "000000000000000000000001" } } - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: fs.files - filter: { _id: {$oid : "000000000000000000000001" }} - database_name: *database_name - - *retryable_command_started_event - - &find_chunks_command_started_event - command_started_event: - command: - find: fs.chunks - filter: { files_id: {$oid : "000000000000000000000001" }} - sort: { n: 1 } - database_name: *database_name - - - description: "Download succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [find], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "Download fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-download.json b/src/test/spec/json/retryable-reads/legacy/gridfs-download.json deleted file mode 100644 index 4d0d5a17e..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-download.json +++ /dev/null @@ -1,270 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "bucket_name": "fs", - "data": { - "fs.files": [ - { - "_id": { - "$oid": "000000000000000000000001" - }, - "length": 1, - "chunkSize": 4, - "uploadDate": { - "$date": "1970-01-01T00:00:00.000Z" - }, - "filename": "abc", - "metadata": {} - } - ], - "fs.chunks": [ - { - "_id": { - "$oid": "000000000000000000000002" - }, - "files_id": { - "$oid": "000000000000000000000001" - }, - "n": 0, - "data": { - "$binary": { - "base64": "EQ==", - "subType": "00" - } - } - } - ] - }, - "tests": [ - { - "description": "Download succeeds on first attempt", - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "Download fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download", - "object": "gridfsbucket", - "arguments": { - "id": { - "$oid": "000000000000000000000001" - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "_id": { - "$oid": "000000000000000000000001" - } - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-download.yml b/src/test/spec/json/retryable-reads/legacy/gridfs-download.yml deleted file mode 100644 index a71c719d9..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-download.yml +++ /dev/null @@ -1,79 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -bucket_name: "fs" - -data: - fs.files: - - - _id: { $oid : "000000000000000000000001" } - length: 1 - chunkSize: 4 - uploadDate: { $date : "1970-01-01T00:00:00.000Z" } - filename: abc - metadata: {} - fs.chunks: - - { _id: { $oid: "000000000000000000000002" }, files_id: { $oid: "000000000000000000000001" }, n: 0, data: { $binary: { base64: "EQ==", subType: "00" } } } - -tests: - - - description: "Download succeeds on first attempt" - operations: - - &retryable_operation - name: download - object: gridfsbucket - arguments: { id: { "$oid" : "000000000000000000000001" } } - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: fs.files - filter: { _id: {$oid : "000000000000000000000001" }} - database_name: *database_name - - &find_chunks_command_started_event - command_started_event: - command: - find: fs.chunks - filter: { files_id: {$oid : "000000000000000000000001" }} - sort: { n: 1 } - database_name: *database_name - - - description: "Download succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [find] - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "Download fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "Download fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.json deleted file mode 100644 index a64230d38..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.json +++ /dev/null @@ -1,849 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "bucket_name": "fs", - "data": { - "fs.files": [ - { - "_id": { - "$oid": "000000000000000000000001" - }, - "length": 1, - "chunkSize": 4, - "uploadDate": { - "$date": "1970-01-01T00:00:00.000Z" - }, - "filename": "abc", - "metadata": {} - } - ], - "fs.chunks": [ - { - "_id": { - "$oid": "000000000000000000000002" - }, - "files_id": { - "$oid": "000000000000000000000001" - }, - "n": 0, - "data": { - "$binary": { - "base64": "EQ==", - "subType": "00" - } - } - } - ] - }, - "tests": [ - { - "description": "DownloadByName succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.yml deleted file mode 100644 index 704492135..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName-serverErrors.yml +++ /dev/null @@ -1,174 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -bucket_name: "fs" - -data: - fs.files: - - - _id: { $oid : "000000000000000000000001" } - length: 1 - chunkSize: 4 - uploadDate: { $date : "1970-01-01T00:00:00.000Z" } - filename: abc - metadata: {} - fs.chunks: - - { _id: { $oid: "000000000000000000000002" }, files_id: { $oid: "000000000000000000000001" }, n: 0, data: { $binary: { base64: "EQ==", subType: "00" } } } - -tests: - - - description: "DownloadByName succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [find], errorCode: 11600 } - operations: - - &retryable_operation - name: download_by_name - object: gridfsbucket - arguments: - filename: abc - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: fs.files - filter: { filename : "abc" } - database_name: *database_name - - *retryable_command_started_event - - &find_chunks_command_started_event - command_started_event: - command: - find: fs.chunks - filter: { files_id: { $oid : "000000000000000000000001" }} - sort: { n: 1 } - database_name: *database_name - - - description: "DownloadByName succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [find], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "DownloadByName fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [find], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.json b/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.json deleted file mode 100644 index 48f2168cf..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.json +++ /dev/null @@ -1,250 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "bucket_name": "fs", - "data": { - "fs.files": [ - { - "_id": { - "$oid": "000000000000000000000001" - }, - "length": 1, - "chunkSize": 4, - "uploadDate": { - "$date": "1970-01-01T00:00:00.000Z" - }, - "filename": "abc", - "metadata": {} - } - ], - "fs.chunks": [ - { - "_id": { - "$oid": "000000000000000000000002" - }, - "files_id": { - "$oid": "000000000000000000000001" - }, - "n": 0, - "data": { - "$binary": { - "base64": "EQ==", - "subType": "00" - } - } - } - ] - }, - "tests": [ - { - "description": "DownloadByName succeeds on first attempt", - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.chunks", - "filter": { - "files_id": { - "$oid": "000000000000000000000001" - } - }, - "sort": { - "n": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "DownloadByName fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "download_by_name", - "object": "gridfsbucket", - "arguments": { - "filename": "abc" - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "fs.files", - "filter": { - "filename": "abc" - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.yml b/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.yml deleted file mode 100644 index e5586954f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/gridfs-downloadByName.yml +++ /dev/null @@ -1,79 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -bucket_name: "fs" - -data: - fs.files: - - - _id: { $oid : "000000000000000000000001" } - length: 1 - chunkSize: 4 - uploadDate: { $date : "1970-01-01T00:00:00.000Z" } - filename: abc - metadata: {} - fs.chunks: - - { _id: { $oid: "000000000000000000000002" }, files_id: { $oid: "000000000000000000000001" }, n: 0, data: { $binary: { base64: "EQ==", subType: "00" } } } - -tests: - - - description: "DownloadByName succeeds on first attempt" - operations: - - &retryable_operation - name: download_by_name - object: gridfsbucket - arguments: { filename: "abc" } - expectations: - - &retryable_command_started_event - command_started_event: - command: - find: fs.files - filter: { filename : "abc" } - database_name: *database_name - - &find_chunks_command_started_event - command_started_event: - command: - find: fs.chunks - filter: { files_id: {$oid : "000000000000000000000001"} } - sort: { n: 1 } - database_name: *database_name - - - description: "DownloadByName succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [find] - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - *find_chunks_command_started_event - - - description: "DownloadByName fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "DownloadByName fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.json deleted file mode 100644 index bbdce625a..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollectionNames succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.yml deleted file mode 100644 index b99bddf82..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionNames-serverErrors.yml +++ /dev/null @@ -1,143 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollectionNames succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listCollections], errorCode: 11600 } - operations: - - &retryable_operation - name: listCollectionNames - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listCollections], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionNames.json b/src/test/spec/json/retryable-reads/legacy/listCollectionNames.json deleted file mode 100644 index 73d96a3cf..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionNames.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollectionNames succeeds on first attempt", - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionNames fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionNames", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionNames.yml b/src/test/spec/json/retryable-reads/legacy/listCollectionNames.yml deleted file mode 100644 index 434adfbdd..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionNames.yml +++ /dev/null @@ -1,59 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollectionNames succeeds on first attempt" - operations: - - &retryable_operation - name: listCollectionNames - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - - description: "ListCollectionNames succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listCollections - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionNames fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListCollectionNames fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.json deleted file mode 100644 index ab469dfe3..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollectionObjects succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.yml deleted file mode 100644 index b2ff9ee83..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects-serverErrors.yml +++ /dev/null @@ -1,148 +0,0 @@ -# listCollectionObjects returns an array of MongoCollection objects. -# Not all drivers support this functionality. For more details, see: -# https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst#returning-a-list-of-collection-objects - -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollectionObjects succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listCollections], errorCode: 11600 } - operations: - - &retryable_operation - name: listCollectionObjects - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listCollections], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.json b/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.json deleted file mode 100644 index 1fb0f1843..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollectionObjects succeeds on first attempt", - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollectionObjects fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollectionObjects", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.yml b/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.yml deleted file mode 100644 index 431569485..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollectionObjects.yml +++ /dev/null @@ -1,63 +0,0 @@ -# listCollectionObjects returns an array of MongoCollection objects. -# Not all drivers support this functionality. For more details, see: -# https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst#returning-a-list-of-collection-objects - -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollectionObjects succeeds on first attempt" - operations: - - &retryable_operation - name: listCollectionObjects - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - - description: "ListCollectionObjects succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listCollections - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollectionObjects fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListCollectionObjects fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.json deleted file mode 100644 index def9ac459..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollections succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.yml deleted file mode 100644 index 94a9495e5..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollections-serverErrors.yml +++ /dev/null @@ -1,143 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollections succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listCollections], errorCode: 11600 } - operations: - - &retryable_operation - name: listCollections - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - *retryable_command_started_event - - - description: "ListCollections succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listCollections], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listCollections], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/listCollections.json b/src/test/spec/json/retryable-reads/legacy/listCollections.json deleted file mode 100644 index 242788362..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollections.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListCollections succeeds on first attempt", - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - }, - { - "description": "ListCollections fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listCollections" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listCollections", - "object": "database", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listCollections": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listCollections.yml b/src/test/spec/json/retryable-reads/legacy/listCollections.yml deleted file mode 100644 index 378ff924f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listCollections.yml +++ /dev/null @@ -1,59 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListCollections succeeds on first attempt" - operations: - - &retryable_operation - name: listCollections - object: database - expectations: - - &retryable_command_started_event - command_started_event: - command: - listCollections: 1 - - - description: "ListCollections succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listCollections - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListCollections fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListCollections fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.json deleted file mode 100644 index 1dd8e4415..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabaseNames succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.yml deleted file mode 100644 index ca6b15094..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames-serverErrors.yml +++ /dev/null @@ -1,143 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabaseNames succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listDatabases], errorCode: 11600 } - operations: - - &retryable_operation - name: listDatabaseNames - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.json b/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.json deleted file mode 100644 index b431f5701..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabaseNames succeeds on first attempt", - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseNames fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseNames", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.yml b/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.yml deleted file mode 100644 index 13e01a48e..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseNames.yml +++ /dev/null @@ -1,59 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabaseNames succeeds on first attempt" - operations: - - &retryable_operation - name: listDatabaseNames - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - - description: "ListDatabaseNames succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listDatabases - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseNames fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListDatabaseNames fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.json deleted file mode 100644 index bc497bb08..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabaseObjects succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.yml deleted file mode 100644 index adc8214a3..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects-serverErrors.yml +++ /dev/null @@ -1,148 +0,0 @@ -# listDatabaseObjects returns an array of MongoDatabase objects. -# Not all drivers support this functionality. For more details, see: -# https://github.com/mongodb/specifications/blob/master/source/enumerate-databases.rst#enumerating-mongodatabase-objects - -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabaseObjects succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listDatabases], errorCode: 11600 } - operations: - - &retryable_operation - name: listDatabaseObjects - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.json b/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.json deleted file mode 100644 index 267fe921c..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabaseObjects succeeds on first attempt", - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabaseObjects fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabaseObjects", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.yml b/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.yml deleted file mode 100644 index 9ed2c216a..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabaseObjects.yml +++ /dev/null @@ -1,63 +0,0 @@ -# listDatabaseObjects returns an array of MongoDatabase objects. -# Not all drivers support this functionality. For more details, see: -# https://github.com/mongodb/specifications/blob/master/source/enumerate-databases.rst#enumerating-mongodatabase-objects - -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabaseObjects succeeds on first attempt" - operations: - - &retryable_operation - name: listDatabaseObjects - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - - description: "ListDatabaseObjects succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listDatabases - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabaseObjects fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListDatabaseObjects fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.json deleted file mode 100644 index ed7bcbc39..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabases succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.yml deleted file mode 100644 index ac904701d..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabases-serverErrors.yml +++ /dev/null @@ -1,144 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabases succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listDatabases], errorCode: 11600 } - operations: - - &retryable_operation - name: listDatabases - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - *retryable_command_started_event - - - description: "ListDatabases succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listDatabases], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabases.json b/src/test/spec/json/retryable-reads/legacy/listDatabases.json deleted file mode 100644 index 69ef9788f..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabases.json +++ /dev/null @@ -1,150 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListDatabases succeeds on first attempt", - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - }, - { - "description": "ListDatabases fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listDatabases" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listDatabases", - "object": "client", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - }, - { - "command_started_event": { - "command": { - "listDatabases": 1 - } - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listDatabases.yml b/src/test/spec/json/retryable-reads/legacy/listDatabases.yml deleted file mode 100644 index 3eaed913a..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listDatabases.yml +++ /dev/null @@ -1,59 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListDatabases succeeds on first attempt" - operations: - - &retryable_operation - name: listDatabases - object: client - expectations: - - &retryable_command_started_event - command_started_event: - command: - listDatabases: 1 - - - description: "ListDatabases succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listDatabases - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListDatabases fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListDatabases fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.json deleted file mode 100644 index 2d3265ec8..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.json +++ /dev/null @@ -1,527 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListIndexNames succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.yml deleted file mode 100644 index 6fb7e30cb..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexNames-serverErrors.yml +++ /dev/null @@ -1,144 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListIndexNames succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listIndexes], errorCode: 11600 } - operations: - - &retryable_operation - name: listIndexNames - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - listIndexes: *collection_name - database_name: *database_name - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexNames.json b/src/test/spec/json/retryable-reads/legacy/listIndexNames.json deleted file mode 100644 index fbdb420f8..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexNames.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListIndexNames succeeds on first attempt", - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexNames fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexNames", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexNames.yml b/src/test/spec/json/retryable-reads/legacy/listIndexNames.yml deleted file mode 100644 index 3a73b51e1..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexNames.yml +++ /dev/null @@ -1,60 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListIndexNames succeeds on first attempt" - operations: - - &retryable_operation - name: listIndexNames - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - listIndexes: *collection_name - database_name: *database_name - - - description: "ListIndexNames succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listIndexes - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexNames fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListIndexNames fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.json b/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.json deleted file mode 100644 index 25c5b0e44..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.json +++ /dev/null @@ -1,527 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListIndexes succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 11600 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 11602 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 13435 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 13436 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 189 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 91 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 7 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 6 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 89 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 9001 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes fails after two NotWritablePrimary errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes fails after NotWritablePrimary when retryReads is false", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.yml b/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.yml deleted file mode 100644 index 23f2768e9..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexes-serverErrors.yml +++ /dev/null @@ -1,145 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListIndexes succeeds after InterruptedAtShutdown" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: { failCommands: [listIndexes], errorCode: 11600 } - operations: - - &retryable_operation - name: listIndexes - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - listIndexes: *collection_name - database_name: *database_name - - *retryable_command_started_event - - - description: "ListIndexes succeeds after InterruptedDueToReplStateChange" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 11602 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after NotWritablePrimary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after NotPrimaryNoSecondaryOk" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 13435 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after NotPrimaryOrSecondary" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 13436 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after PrimarySteppedDown" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 189 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after ShutdownInProgress" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 91 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after HostNotFound" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 7 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after HostUnreachable" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 6 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after NetworkTimeout" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 89 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes succeeds after SocketException" - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 9001 } - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes fails after two NotWritablePrimary errors" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes fails after NotWritablePrimary when retryReads is false" - clientOptions: - retryReads: false - failPoint: - <<: *failCommand_failPoint - data: { failCommands: [listIndexes], errorCode: 10107 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexes.json b/src/test/spec/json/retryable-reads/legacy/listIndexes.json deleted file mode 100644 index 5cb620ae4..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexes.json +++ /dev/null @@ -1,156 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [], - "tests": [ - { - "description": "ListIndexes succeeds on first attempt", - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes succeeds on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes fails on first attempt", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "ListIndexes fails on second attempt", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "listIndexes" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "listIndexes", - "object": "collection", - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - }, - { - "command_started_event": { - "command": { - "listIndexes": "coll" - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/listIndexes.yml b/src/test/spec/json/retryable-reads/legacy/listIndexes.yml deleted file mode 100644 index 84ba7242a..000000000 --- a/src/test/spec/json/retryable-reads/legacy/listIndexes.yml +++ /dev/null @@ -1,60 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: [] - -tests: - - - description: "ListIndexes succeeds on first attempt" - operations: - - &retryable_operation - name: listIndexes - object: collection - expectations: - - &retryable_command_started_event - command_started_event: - command: - listIndexes: *collection_name - database_name: *database_name - - - description: "ListIndexes succeeds on second attempt" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: - - listIndexes - closeConnection: true - operations: [*retryable_operation] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - - - description: "ListIndexes fails on first attempt" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: - - &retryable_operation_fails - <<: *retryable_operation - error: true - expectations: - - *retryable_command_started_event - - - description: "ListIndexes fails on second attempt" - failPoint: - <<: *failCommand_failPoint - mode: { times: 2 } - operations: [*retryable_operation_fails] - expectations: - - *retryable_command_started_event - - *retryable_command_started_event - diff --git a/src/test/spec/json/retryable-reads/legacy/mapReduce.json b/src/test/spec/json/retryable-reads/legacy/mapReduce.json deleted file mode 100644 index 9327a2305..000000000 --- a/src/test/spec/json/retryable-reads/legacy/mapReduce.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "single", - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ], - "serverless": "forbid" - } - ], - "database_name": "retryable-reads-tests", - "collection_name": "coll", - "data": [ - { - "_id": 1, - "x": 0 - }, - { - "_id": 2, - "x": 1 - }, - { - "_id": 3, - "x": 2 - } - ], - "tests": [ - { - "description": "MapReduce succeeds with retry on", - "operations": [ - { - "name": "mapReduce", - "object": "collection", - "arguments": { - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "result": [ - { - "_id": 0, - "value": 6 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "mapReduce": "coll", - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "MapReduce fails with retry on", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "mapReduce" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "mapReduce", - "object": "collection", - "arguments": { - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "mapReduce": "coll", - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - }, - { - "description": "MapReduce fails with retry off", - "clientOptions": { - "retryReads": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "mapReduce" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "mapReduce", - "object": "collection", - "arguments": { - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "error": true - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "mapReduce": "coll", - "map": { - "$code": "function inc() { return emit(0, this.x + 1) }" - }, - "reduce": { - "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" - }, - "out": { - "inline": 1 - } - }, - "database_name": "retryable-reads-tests" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/retryable-reads/legacy/mapReduce.yml b/src/test/spec/json/retryable-reads/legacy/mapReduce.yml deleted file mode 100644 index def8b3748..000000000 --- a/src/test/spec/json/retryable-reads/legacy/mapReduce.yml +++ /dev/null @@ -1,62 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["single", "replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - # serverless proxy does not support mapReduce operation - serverless: "forbid" - -database_name: &database_name "retryable-reads-tests" -collection_name: &collection_name "coll" - -data: - - {_id: 1, x: 0} - - {_id: 2, x: 1} - - {_id: 3, x: 2} - -tests: - - - description: "MapReduce succeeds with retry on" - operations: - - &operation_succeeds - <<: &operation - name: mapReduce - object: collection - arguments: - map: { $code: "function inc() { return emit(0, this.x + 1) }" } - reduce: { $code: "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" } - out: { inline: 1 } - result: [ { "_id" : 0, "value" : 6 } ] - expectations: - - &command_started_event - command_started_event: - command: - mapReduce: *collection_name - map: { $code: "function inc() { return emit(0, this.x + 1) }" } - reduce: { $code: "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" } - out: { inline: 1 } - database_name: *database_name - - - description: "MapReduce fails with retry on" - failPoint: &failCommand_failPoint - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: [mapReduce] - closeConnection: true - operations: - - &operation_fails - <<: *operation - error: true - expectations: - - *command_started_event - - - description: "MapReduce fails with retry off" - clientOptions: - retryReads: false - failPoint: *failCommand_failPoint - operations: [*operation_fails] - expectations: - - *command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/aggregate-merge.json b/src/test/spec/json/retryable-reads/unified/aggregate-merge.json new file mode 100644 index 000000000..96bbd0fc3 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate-merge.json @@ -0,0 +1,143 @@ +{ + "description": "aggregate-merge", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate with $merge does not retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + }, + { + "$merge": { + "into": "output-collection" + } + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + }, + { + "$merge": { + "into": "output-collection" + } + } + ] + }, + "commandName": "aggregate", + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/aggregate-merge.yml b/src/test/spec/json/retryable-reads/unified/aggregate-merge.yml new file mode 100644 index 000000000..0add82b66 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate-merge.yml @@ -0,0 +1,86 @@ +description: aggregate-merge + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: 4.1.11 + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + +tests: + - + description: 'Aggregate with $merge does not retry' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - + object: *collection0 + name: aggregate + arguments: + pipeline: &pipeline + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + - + $merge: + into: output-collection + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: *pipeline + commandName: aggregate + databaseName: *database_name diff --git a/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.json b/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.json new file mode 100644 index 000000000..d39835a5d --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.json @@ -0,0 +1,1430 @@ +{ + "description": "aggregate-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.yml new file mode 100644 index 000000000..66a3f2cbb --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate-serverErrors.yml @@ -0,0 +1,411 @@ +description: aggregate-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + +tests: + - + description: 'Aggregate succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: aggregate + arguments: + pipeline: + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + expectResult: + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - + $match: + _id: { $gt: 1 } + - + $sort: + x: 1 + databaseName: *database_name + - *retryable_command_started_event + - + description: 'Aggregate succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: aggregate + arguments: + pipeline: + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/aggregate.json b/src/test/spec/json/retryable-reads/unified/aggregate.json new file mode 100644 index 000000000..2b504c8d4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate.json @@ -0,0 +1,527 @@ +{ + "description": "aggregate", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Aggregate succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Aggregate with $out does not retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + }, + { + "$out": "output-collection" + } + ] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$sort": { + "x": 1 + } + }, + { + "$out": "output-collection" + } + ] + }, + "commandName": "aggregate", + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/aggregate.yml b/src/test/spec/json/retryable-reads/unified/aggregate.yml new file mode 100644 index 000000000..fa25d1f99 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/aggregate.yml @@ -0,0 +1,227 @@ +description: aggregate + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + +tests: + - + description: 'Aggregate succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: aggregate + arguments: + pipeline: + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + expectResult: + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - + $match: + _id: { $gt: 1 } + - + $sort: + x: 1 + databaseName: *database_name + - + description: 'Aggregate succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: aggregate + arguments: + pipeline: + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'Aggregate fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Aggregate with $out does not retry' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: *failCommand_failPoint + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - + $match: + _id: + $gt: 1 + - + $sort: + x: 1 + - + $out: output-collection + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - + $match: + _id: { $gt: 1 } + - + $sort: + x: 1 + - + $out: output-collection + commandName: aggregate + databaseName: *database_name diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.json b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.json new file mode 100644 index 000000000..47375974d --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.json @@ -0,0 +1,959 @@ +{ + "description": "changeStreams-client.watch-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + } + ], + "tests": [ + { + "description": "client.watch succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.yml new file mode 100644 index 000000000..7b6b8bbdf --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch-serverErrors.yml @@ -0,0 +1,359 @@ +description: changeStreams-client.watch-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + +tests: + - + description: 'client.watch succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11600 + - &retryable_operation + object: *client0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: 1 + cursor: { } + pipeline: + - + $changeStream: + allChangesForCluster: true + databaseName: admin + - *retryable_command_started_event + - + description: 'client.watch succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + errorCode: 10107 + - &retryable_operation_fails + object: *client0 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - <<: *retryable_operation_fails + object: *client1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.json b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.json new file mode 100644 index 000000000..95ddaf921 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.json @@ -0,0 +1,294 @@ +{ + "description": "changeStreams-client.watch", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + } + ], + "tests": [ + { + "description": "client.watch succeeds on first attempt", + "operations": [ + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "client.watch fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": { + "allChangesForCluster": true + } + } + ] + }, + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.yml new file mode 100644 index 000000000..2fa3a300b --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-client.watch.yml @@ -0,0 +1,131 @@ +description: changeStreams-client.watch + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + +tests: + - + description: 'client.watch succeeds on first attempt' + operations: + - &retryable_operation + object: *client0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: 1 + cursor: { } + pipeline: + - + $changeStream: + allChangesForCluster: true + databaseName: admin + - + description: 'client.watch succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'client.watch fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *client1 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'client.watch fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + closeConnection: true + - <<: *retryable_operation_fails + object: *client0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.json b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.json new file mode 100644 index 000000000..589d0a3c3 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.json @@ -0,0 +1,944 @@ +{ + "description": "changeStreams-db.coll.watch-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "db.coll.watch succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.yml new file mode 100644 index 000000000..8dc05560b --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch-serverErrors.yml @@ -0,0 +1,382 @@ +description: changeStreams-db.coll.watch-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'db.coll.watch succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11600 + - &retryable_operation + object: *collection0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + cursor: { } + pipeline: + - + $changeStream: { } + databaseName: *database_name + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.json b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.json new file mode 100644 index 000000000..bbea2ffe4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.json @@ -0,0 +1,314 @@ +{ + "description": "changeStreams-db.coll.watch", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "db.coll.watch succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.coll.watch fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.yml new file mode 100644 index 000000000..810638228 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.coll.watch.yml @@ -0,0 +1,152 @@ +description: changeStreams-db.coll.watch + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'db.coll.watch succeeds on first attempt' + operations: + - &retryable_operation + object: *collection0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + cursor: { } + pipeline: + - + $changeStream: { } + databaseName: *database_name + - + description: 'db.coll.watch succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.coll.watch fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'db.coll.watch fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.json b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.json new file mode 100644 index 000000000..6c12d7ddd --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.json @@ -0,0 +1,930 @@ +{ + "description": "changeStreams-db.watch-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "db.watch succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.yml new file mode 100644 index 000000000..a90a802d9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch-serverErrors.yml @@ -0,0 +1,373 @@ +description: changeStreams-db.watch-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'db.watch succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11600 + - &retryable_operation + object: *database0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: 1 + cursor: { } + pipeline: + - + $changeStream: { } + databaseName: *database_name + - *retryable_command_started_event + - + description: 'db.watch succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + errorCode: 10107 + - &retryable_operation_fails + object: *database0 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - <<: *retryable_operation_fails + object: *database1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.json b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.json new file mode 100644 index 000000000..1b6d911c7 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.json @@ -0,0 +1,303 @@ +{ + "description": "changeStreams-db.watch", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "db.watch succeeds on first attempt", + "operations": [ + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "ignoreCommandMonitoringEvents": [ + "killCursors" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database1", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "db.watch fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "createChangeStream", + "arguments": { + "pipeline": [] + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": 1, + "cursor": {}, + "pipeline": [ + { + "$changeStream": {} + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.yml b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.yml new file mode 100644 index 000000000..47cbc0457 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/changeStreams-db.watch.yml @@ -0,0 +1,145 @@ +description: changeStreams-db.watch + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.7 + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'db.watch succeeds on first attempt' + operations: + - &retryable_operation + object: *database0 + name: createChangeStream + arguments: + pipeline: [] + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: 1 + cursor: { } + pipeline: + - + $changeStream: { } + databaseName: *database_name + - + description: 'db.watch succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'db.watch fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + ignoreCommandMonitoringEvents: + - killCursors + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *database1 + name: createChangeStream + arguments: + pipeline: [] + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'db.watch fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + closeConnection: true + - <<: *retryable_operation_fails + object: *database0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/count-serverErrors.json b/src/test/spec/json/retryable-reads/unified/count-serverErrors.json new file mode 100644 index 000000000..c52edfdb9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/count-serverErrors.json @@ -0,0 +1,808 @@ +{ + "description": "count-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "Count succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "count", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/count-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/count-serverErrors.yml new file mode 100644 index 000000000..a433aaa87 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/count-serverErrors.yml @@ -0,0 +1,381 @@ +description: count-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + +tests: + - + description: 'Count succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: count + arguments: + filter: { } + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + count: *collection_name + databaseName: *database_name + - *retryable_command_started_event + - + description: 'Count succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - count + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: count + arguments: + filter: { } + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/count.json b/src/test/spec/json/retryable-reads/unified/count.json new file mode 100644 index 000000000..d5c9a343a --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/count.json @@ -0,0 +1,286 @@ +{ + "description": "count", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "Count succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "count", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Count fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/count.yml b/src/test/spec/json/retryable-reads/unified/count.yml new file mode 100644 index 000000000..912f7578b --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/count.yml @@ -0,0 +1,153 @@ +description: count + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + +tests: + - + description: 'Count succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: count + arguments: + filter: { } + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + count: *collection_name + databaseName: *database_name + - + description: 'Count succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Count fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: count + arguments: + filter: { } + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'Count fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - count + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.json b/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.json new file mode 100644 index 000000000..fd028b114 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.json @@ -0,0 +1,1133 @@ +{ + "description": "countDocuments-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "CountDocuments succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.yml new file mode 100644 index 000000000..2bcca604f --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/countDocuments-serverErrors.yml @@ -0,0 +1,386 @@ +description: countDocuments-serverErrors +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 +tests: + - + description: 'CountDocuments succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: countDocuments + arguments: + filter: { } + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - + $match: { } + - + $group: + _id: 1 + 'n': { $sum: 1 } + databaseName: *database_name + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: countDocuments + arguments: + filter: { } + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/countDocuments.json b/src/test/spec/json/retryable-reads/unified/countDocuments.json new file mode 100644 index 000000000..e06e89c1a --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/countDocuments.json @@ -0,0 +1,364 @@ +{ + "description": "countDocuments", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "CountDocuments succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "CountDocuments fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": {} + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "coll", + "pipeline": [ + { + "$match": {} + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ] + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/countDocuments.yml b/src/test/spec/json/retryable-reads/unified/countDocuments.yml new file mode 100644 index 000000000..725137dee --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/countDocuments.yml @@ -0,0 +1,158 @@ +description: countDocuments +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 +tests: + - + description: 'CountDocuments succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: countDocuments + arguments: + filter: { } + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - + $match: { } + - + $group: + _id: 1 + 'n': { $sum: 1 } + databaseName: *database_name + - + description: 'CountDocuments succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - aggregate + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'CountDocuments fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: countDocuments + arguments: + filter: { } + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'CountDocuments fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - aggregate + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.json b/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.json new file mode 100644 index 000000000..79d2d5fc3 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.json @@ -0,0 +1,1060 @@ +{ + "description": "distinct-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Distinct succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.yml new file mode 100644 index 000000000..999e0b3fa --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/distinct-serverErrors.yml @@ -0,0 +1,396 @@ +description: distinct-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + +tests: + - + description: 'Distinct succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: distinct + arguments: + fieldName: x + filter: + _id: + $gt: 1 + expectResult: + - 22 + - 33 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + distinct: *collection_name + key: x + query: + _id: + $gt: 1 + databaseName: *database_name + - *retryable_command_started_event + - + description: 'Distinct succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - distinct + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: distinct + arguments: + fieldName: x + filter: + _id: + $gt: 1 + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/distinct.json b/src/test/spec/json/retryable-reads/unified/distinct.json new file mode 100644 index 000000000..81f1f66e9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/distinct.json @@ -0,0 +1,352 @@ +{ + "description": "distinct", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Distinct succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectResult": [ + 22, + 33 + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Distinct fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "coll", + "key": "x", + "query": { + "_id": { + "$gt": 1 + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/distinct.yml b/src/test/spec/json/retryable-reads/unified/distinct.yml new file mode 100644 index 000000000..9b6f1648d --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/distinct.yml @@ -0,0 +1,168 @@ +description: distinct + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + +tests: + - + description: 'Distinct succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: distinct + arguments: + fieldName: x + filter: + _id: + $gt: 1 + expectResult: + - 22 + - 33 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + distinct: *collection_name + key: x + query: + _id: + $gt: 1 + databaseName: *database_name + - + description: 'Distinct succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - distinct + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Distinct fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: distinct + arguments: + fieldName: x + filter: + _id: + $gt: 1 + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'Distinct fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - distinct + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.json b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.json new file mode 100644 index 000000000..ba983c6cd --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.json @@ -0,0 +1,768 @@ +{ + "description": "estimatedDocumentCount-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "EstimatedDocumentCount succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "estimatedDocumentCount", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.yml new file mode 100644 index 000000000..98fa5ec9f --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount-serverErrors.yml @@ -0,0 +1,376 @@ +description: estimatedDocumentCount-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + +tests: + - + description: 'EstimatedDocumentCount succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: estimatedDocumentCount + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + count: *collection_name + databaseName: *database_name + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - count + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: estimatedDocumentCount + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.json b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.json new file mode 100644 index 000000000..2ee29f679 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.json @@ -0,0 +1,273 @@ +{ + "description": "estimatedDocumentCount", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "EstimatedDocumentCount succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectResult": 2 + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "estimatedDocumentCount", + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "EstimatedDocumentCount fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "count" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "estimatedDocumentCount", + "expectError": { + "isClientError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "count": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.yml b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.yml new file mode 100644 index 000000000..13ad4061e --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/estimatedDocumentCount.yml @@ -0,0 +1,148 @@ +description: estimatedDocumentCount + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + +tests: + - + description: 'EstimatedDocumentCount succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: estimatedDocumentCount + expectResult: 2 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + count: *collection_name + databaseName: *database_name + - + description: 'EstimatedDocumentCount succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - count + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: estimatedDocumentCount + expectError: + isClientError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'EstimatedDocumentCount fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - count + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.json b/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.json new file mode 100644 index 000000000..8d090bbe3 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.json @@ -0,0 +1,147 @@ +{ + "description": "ExceededTimeLimit is a retryable read", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "exceededtimelimit-test" + } + } + ], + "initialData": [ + { + "collectionName": "exceededtimelimit-test", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Find succeeds on second attempt after ExceededTimeLimit", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 262 + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "object": "collection0", + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "exceededtimelimit-test", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "commandName": "find", + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "exceededtimelimit-test", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "commandName": "find", + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.yml b/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.yml new file mode 100644 index 000000000..1912fa7bb --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/exceededTimeLimit.yml @@ -0,0 +1,68 @@ +description: "ExceededTimeLimit is a retryable read" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "4.0" + topologies: [single, replicaset] + - minServerVersion: "4.1.7" + topologies: [sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + # Ensure the `configureFailpoint` and `find` commands are run on the same mongos + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name "retryable-reads-tests" + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name "exceededtimelimit-test" + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: "Find succeeds on second attempt after ExceededTimeLimit" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "find" ] + errorCode: 262 # ExceededTimeLimit + - name: find + arguments: + filter: { _id: { $gt: 1 } } + object: *collection0 + expectResult: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 1 } } + commandName: find + databaseName: *database0Name + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 1 } } + commandName: find + databaseName: *database0Name diff --git a/src/test/spec/json/retryable-reads/unified/find-serverErrors.json b/src/test/spec/json/retryable-reads/unified/find-serverErrors.json new file mode 100644 index 000000000..ab3dbe45f --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/find-serverErrors.json @@ -0,0 +1,1184 @@ +{ + "description": "find-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "Find succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/find-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/find-serverErrors.yml new file mode 100644 index 000000000..039cf25d9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/find-serverErrors.yml @@ -0,0 +1,412 @@ +description: find-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + - + _id: 5 + x: 55 + +tests: + - + description: 'Find succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: find + arguments: + filter: { } + sort: + _id: 1 + limit: 4 + expectResult: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *collection_name + filter: { } + sort: + _id: 1 + limit: 4 + databaseName: *database_name + - *retryable_command_started_event + - + description: 'Find succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: find + arguments: + filter: { } + sort: + _id: 1 + limit: 4 + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/find.json b/src/test/spec/json/retryable-reads/unified/find.json new file mode 100644 index 000000000..30c4c5e47 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/find.json @@ -0,0 +1,498 @@ +{ + "description": "find", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "Find succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds on second attempt with explicit clientOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": true + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Find fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4 + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/find.yml b/src/test/spec/json/retryable-reads/unified/find.yml new file mode 100644 index 000000000..bcd98684c --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/find.yml @@ -0,0 +1,221 @@ +description: find + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + - + _id: 5 + x: 55 + +tests: + - + description: 'Find succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: find + arguments: + filter: { } + sort: + _id: 1 + limit: 4 + expectResult: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *collection_name + filter: { } + sort: + _id: 1 + limit: 4 + databaseName: *database_name + - + description: 'Find succeeds on second attempt with explicit clientOptions' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: true + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + - <<: *retryable_operation_succeeds + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: *failCommand_failPoint + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Find fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: find + arguments: + filter: { } + sort: + _id: 1 + limit: 4 + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'Find fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.json b/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.json new file mode 100644 index 000000000..7adda1e32 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.json @@ -0,0 +1,954 @@ +{ + "description": "findOne-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "FindOne succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.yml new file mode 100644 index 000000000..48e461f11 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/findOne-serverErrors.yml @@ -0,0 +1,396 @@ +description: findOne-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + - + _id: 5 + x: 55 + +tests: + - + description: 'FindOne succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11600 + - &retryable_operation_succeeds + object: *collection0 + name: findOne + arguments: + filter: + _id: 1 + expectResult: + _id: 1 + x: 11 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *collection_name + filter: + _id: 1 + databaseName: *database_name + - *retryable_command_started_event + - + description: 'FindOne succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11602 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13435 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13436 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 189 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 91 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 7 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 6 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 89 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 9001 + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: findOne + arguments: + filter: + _id: 1 + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/findOne.json b/src/test/spec/json/retryable-reads/unified/findOne.json new file mode 100644 index 000000000..4314a19e4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/findOne.json @@ -0,0 +1,330 @@ +{ + "description": "findOne", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "FindOne succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "FindOne fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "findOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "coll", + "filter": { + "_id": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/findOne.yml b/src/test/spec/json/retryable-reads/unified/findOne.yml new file mode 100644 index 000000000..e4de9d77f --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/findOne.yml @@ -0,0 +1,168 @@ +description: findOne + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 11 + - + _id: 2 + x: 22 + - + _id: 3 + x: 33 + - + _id: 4 + x: 44 + - + _id: 5 + x: 55 + +tests: + - + description: 'FindOne succeeds on first attempt' + operations: + - &retryable_operation_succeeds + object: *collection0 + name: findOne + arguments: + filter: + _id: 1 + expectResult: + _id: 1 + x: 11 + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *collection_name + filter: + _id: 1 + databaseName: *database_name + - + description: 'FindOne succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + - *retryable_operation_succeeds + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'FindOne fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: findOne + arguments: + filter: + _id: 1 + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'FindOne fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.json b/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.json new file mode 100644 index 000000000..5bb7eee0b --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.json @@ -0,0 +1,1092 @@ +{ + "description": "gridfs-download-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 1, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "abc", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "EQ==", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "Download succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket1", + "database": "database1" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket1", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.yml new file mode 100644 index 000000000..b7942f6f5 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-download-serverErrors.yml @@ -0,0 +1,421 @@ +description: gridfs-download-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + bucket: + id: &bucket0 bucket0 + database: *database0 + +initialData: + - + collectionName: &files_collection_name fs.files + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000001' + length: 1 + chunkSize: 4 + uploadDate: + $date: '1970-01-01T00:00:00.000Z' + filename: abc + metadata: { } + - + collectionName: &chunks_collection_name fs.chunks + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000002' + files_id: + $oid: '000000000000000000000001' + 'n': 0 + data: + $binary: + base64: EQ== # hex: 11 + subType: '00' + +tests: + - + description: 'Download succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11600 + - &retryable_operation + object: *bucket0 + name: download + arguments: + id: + $oid: '000000000000000000000001' + expectResult: + $$matchesHexBytes: "11" + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *files_collection_name + filter: + _id: + $oid: '000000000000000000000001' + databaseName: *database_name + - *retryable_command_started_event + - &find_chunks_command_started_event + commandStartedEvent: + command: + find: *chunks_collection_name + # Avoid checking additional fields since the exact query may + # differ among drivers. expectResult is sufficient to assert + # correctness. + databaseName: *database_name + - + description: 'Download succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + errorCode: 10107 + - &retryable_operation_fails + object: *bucket0 + name: download + arguments: + id: + $oid: '000000000000000000000001' + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'Download fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + bucket: + id: &bucket1 bucket1 + database: *database1 + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - <<: *retryable_operation_fails + object: *bucket1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-download.json b/src/test/spec/json/retryable-reads/unified/gridfs-download.json new file mode 100644 index 000000000..69fe8ff7c --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-download.json @@ -0,0 +1,367 @@ +{ + "description": "gridfs-download", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 1, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "abc", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "EQ==", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "Download succeeds on first attempt", + "operations": [ + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket1", + "database": "database1" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket1", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "Download fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket0", + "name": "download", + "arguments": { + "id": { + "$oid": "000000000000000000000001" + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "_id": { + "$oid": "000000000000000000000001" + } + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-download.yml b/src/test/spec/json/retryable-reads/unified/gridfs-download.yml new file mode 100644 index 000000000..8164e7e82 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-download.yml @@ -0,0 +1,184 @@ +description: gridfs-download + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + bucket: + id: &bucket0 bucket0 + database: *database0 + +initialData: + - + collectionName: &files_collection_name fs.files + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000001' + length: 1 + chunkSize: 4 + uploadDate: + $date: '1970-01-01T00:00:00.000Z' + filename: abc + metadata: { } + - + collectionName: &chunks_collection_name fs.chunks + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000002' + files_id: + $oid: '000000000000000000000001' + 'n': 0 + data: + $binary: + base64: EQ== # hex: 11 + subType: '00' + +tests: + - + description: 'Download succeeds on first attempt' + operations: + - &retryable_operation + object: *bucket0 + name: download + arguments: + id: + $oid: '000000000000000000000001' + expectResult: + $$matchesHexBytes: "11" + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *files_collection_name + filter: + _id: + $oid: '000000000000000000000001' + databaseName: *database_name + - &find_chunks_command_started_event + commandStartedEvent: + command: + find: *chunks_collection_name + # Avoid checking additional fields since the exact query may + # differ among drivers. expectResult is sufficient to assert + # correctness. + databaseName: *database_name + - + description: 'Download succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'Download fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + bucket: + id: &bucket1 bucket1 + database: *database1 + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *bucket1 + name: download + arguments: + id: + $oid: '000000000000000000000001' + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'Download fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + closeConnection: true + - <<: *retryable_operation_fails + object: *bucket0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.json b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.json new file mode 100644 index 000000000..35f7e1e56 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.json @@ -0,0 +1,1016 @@ +{ + "description": "gridfs-downloadByName-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 1, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "abc", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "EQ==", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "DownloadByName succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket1", + "database": "database1" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "bucket1", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.yml new file mode 100644 index 000000000..04d1c809d --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName-serverErrors.yml @@ -0,0 +1,418 @@ +description: gridfs-downloadByName-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + bucket: + id: &bucket0 bucket0 + database: *database0 + +initialData: + - + collectionName: &files_collection_name fs.files + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000001' + length: 1 + chunkSize: 4 + uploadDate: + $date: '1970-01-01T00:00:00.000Z' + filename: abc + metadata: { } + - + collectionName: &chunks_collection_name fs.chunks + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000002' + files_id: + $oid: '000000000000000000000001' + 'n': 0 + data: + $binary: + base64: EQ== # hex: 11 + subType: '00' + +tests: + - + description: 'DownloadByName succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11600 + - &retryable_operation + object: *bucket0 + name: downloadByName + arguments: + filename: abc + expectResult: + $$matchesHexBytes: "11" + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *files_collection_name + filter: + filename: abc + databaseName: *database_name + - *retryable_command_started_event + - &find_chunks_command_started_event + commandStartedEvent: + command: + find: *chunks_collection_name + # Avoid checking additional fields since the exact query may + # differ among drivers. expectResult is sufficient to assert + # correctness. + databaseName: *database_name + - + description: 'DownloadByName succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + errorCode: 10107 + - &retryable_operation_fails + object: *bucket0 + name: downloadByName + arguments: + filename: abc + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'DownloadByName fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + bucket: + id: &bucket1 bucket1 + database: *database1 + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + errorCode: 10107 + - <<: *retryable_operation_fails + object: *bucket1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.json b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.json new file mode 100644 index 000000000..c3fa87339 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.json @@ -0,0 +1,347 @@ +{ + "description": "gridfs-downloadByName", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket0", + "database": "database0" + } + } + ], + "initialData": [ + { + "collectionName": "fs.files", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000001" + }, + "length": 1, + "chunkSize": 4, + "uploadDate": { + "$date": "1970-01-01T00:00:00.000Z" + }, + "filename": "abc", + "metadata": {} + } + ] + }, + { + "collectionName": "fs.chunks", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": { + "$oid": "000000000000000000000002" + }, + "files_id": { + "$oid": "000000000000000000000001" + }, + "n": 0, + "data": { + "$binary": { + "base64": "EQ==", + "subType": "00" + } + } + } + ] + } + ], + "tests": [ + { + "description": "DownloadByName succeeds on first attempt", + "operations": [ + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectResult": { + "$$matchesHexBytes": "11" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.chunks" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "bucket": { + "id": "bucket1", + "database": "database1" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket1", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "DownloadByName fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "object": "bucket0", + "name": "downloadByName", + "arguments": { + "filename": "abc" + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "fs.files", + "filter": { + "filename": "abc" + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.yml b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.yml new file mode 100644 index 000000000..403c4c29c --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/gridfs-downloadByName.yml @@ -0,0 +1,180 @@ +description: gridfs-downloadByName + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + bucket: + id: &bucket0 bucket0 + database: *database0 + +initialData: + - + collectionName: &files_collection_name fs.files + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000001' + length: 1 + chunkSize: 4 + uploadDate: + $date: '1970-01-01T00:00:00.000Z' + filename: abc + metadata: { } + - + collectionName: &chunks_collection_name fs.chunks + databaseName: *database_name + documents: + - + _id: + $oid: '000000000000000000000002' + files_id: + $oid: '000000000000000000000001' + 'n': 0 + data: + $binary: + base64: EQ== # hex: 11 + subType: '00' +tests: + - + description: 'DownloadByName succeeds on first attempt' + operations: + - &retryable_operation + object: *bucket0 + name: downloadByName + arguments: + filename: abc + expectResult: + $$matchesHexBytes: "11" + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + find: *files_collection_name + filter: + filename: abc + databaseName: *database_name + - &find_chunks_command_started_event + commandStartedEvent: + command: + find: *chunks_collection_name + # Avoid checking additional fields since the exact query may + # differ among drivers. expectResult is sufficient to assert + # correctness. + databaseName: *database_name + - + description: 'DownloadByName succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - *find_chunks_command_started_event + - + description: 'DownloadByName fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + bucket: + id: &bucket1 bucket1 + database: *database1 + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *bucket1 + name: downloadByName + arguments: + filename: abc + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'DownloadByName fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - find + closeConnection: true + - <<: *retryable_operation_fails + object: *bucket0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.json new file mode 100644 index 000000000..162dd4cee --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.json @@ -0,0 +1,710 @@ +{ + "description": "listCollectionNames-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollectionNames succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database1", + "name": "listCollectionNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.yml new file mode 100644 index 000000000..bba0407d9 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionNames-serverErrors.yml @@ -0,0 +1,360 @@ +description: listCollectionNames-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollectionNames succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11600 + - &retryable_operation + object: *database0 + name: listCollectionNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + errorCode: 10107 + - &retryable_operation_fails + object: *database0 + name: listCollectionNames + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - <<: *retryable_operation_fails + object: *database1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionNames.json b/src/test/spec/json/retryable-reads/unified/listCollectionNames.json new file mode 100644 index 000000000..0fe575f7a --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionNames.json @@ -0,0 +1,243 @@ +{ + "description": "listCollectionNames", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollectionNames succeeds on first attempt", + "operations": [ + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database1", + "name": "listCollectionNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionNames fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollectionNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionNames.yml b/src/test/spec/json/retryable-reads/unified/listCollectionNames.yml new file mode 100644 index 000000000..7522f1d09 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionNames.yml @@ -0,0 +1,132 @@ +description: listCollectionNames + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollectionNames succeeds on first attempt' + operations: + - &retryable_operation + object: *database0 + name: listCollectionNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - + description: 'ListCollectionNames succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionNames fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *database1 + name: listCollectionNames + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListCollectionNames fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + closeConnection: true + - <<: *retryable_operation_fails + object: *database0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.json new file mode 100644 index 000000000..8b9d582c1 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.json @@ -0,0 +1,710 @@ +{ + "description": "listCollectionObjects-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollectionObjects succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database1", + "name": "listCollectionObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.yml new file mode 100644 index 000000000..0489c890f --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionObjects-serverErrors.yml @@ -0,0 +1,364 @@ +# listCollectionObjects returns an array of MongoCollection objects. +# Not all drivers support this functionality. For more details, see: +# https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.md#returning-a-list-of-collection-objects + +description: listCollectionObjects-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollectionObjects succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11600 + - &retryable_operation + object: *database0 + name: listCollectionObjects + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + errorCode: 10107 + - &retryable_operation_fails + object: *database0 + name: listCollectionObjects + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - <<: *retryable_operation_fails + object: *database1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionObjects.json b/src/test/spec/json/retryable-reads/unified/listCollectionObjects.json new file mode 100644 index 000000000..9cdbb6927 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionObjects.json @@ -0,0 +1,243 @@ +{ + "description": "listCollectionObjects", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollectionObjects succeeds on first attempt", + "operations": [ + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database1", + "name": "listCollectionObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollectionObjects fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollectionObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollectionObjects.yml b/src/test/spec/json/retryable-reads/unified/listCollectionObjects.yml new file mode 100644 index 000000000..739ad65a4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollectionObjects.yml @@ -0,0 +1,136 @@ +# listCollectionObjects returns an array of MongoCollection objects. +# Not all drivers support this functionality. For more details, see: +# https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.md#returning-a-list-of-collection-objects + +description: listCollectionObjects + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollectionObjects succeeds on first attempt' + operations: + - &retryable_operation + object: *database0 + name: listCollectionObjects + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - + description: 'ListCollectionObjects succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollectionObjects fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *database1 + name: listCollectionObjects + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListCollectionObjects fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + closeConnection: true + - <<: *retryable_operation_fails + object: *database0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event \ No newline at end of file diff --git a/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.json new file mode 100644 index 000000000..171fe7457 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.json @@ -0,0 +1,710 @@ +{ + "description": "listCollections-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollections succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database0", + "name": "listCollections", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "database1", + "name": "listCollections", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.yml new file mode 100644 index 000000000..12fb42f02 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollections-serverErrors.yml @@ -0,0 +1,360 @@ +description: listCollections-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollections succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11600 + - &retryable_operation + object: *database0 + name: listCollections + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - *retryable_command_started_event + - + description: 'ListCollections succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + errorCode: 10107 + - &retryable_operation_fails + object: *database0 + name: listCollections + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + errorCode: 10107 + - <<: *retryable_operation_fails + object: *database1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listCollections.json b/src/test/spec/json/retryable-reads/unified/listCollections.json new file mode 100644 index 000000000..b6152f9ce --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollections.json @@ -0,0 +1,243 @@ +{ + "description": "listCollections", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListCollections succeeds on first attempt", + "operations": [ + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollections" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database1", + "name": "listCollections", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListCollections fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listCollections" + ], + "closeConnection": true + } + } + } + }, + { + "object": "database0", + "name": "listCollections", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listCollections": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listCollections.yml b/src/test/spec/json/retryable-reads/unified/listCollections.yml new file mode 100644 index 000000000..1dcfe1ef4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listCollections.yml @@ -0,0 +1,132 @@ +description: listCollections + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + +initialData: + - + collectionName: &collection_name coll + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListCollections succeeds on first attempt' + operations: + - &retryable_operation + object: *database0 + name: listCollections + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listCollections: 1 + - + description: 'ListCollections succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listCollections + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListCollections fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *database1 + name: listCollections + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListCollections fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listCollections + closeConnection: true + - <<: *retryable_operation_fails + object: *database0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.json new file mode 100644 index 000000000..489ff0ad5 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.json @@ -0,0 +1,696 @@ +{ + "description": "listDatabaseNames-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabaseNames succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client1", + "name": "listDatabaseNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.yml new file mode 100644 index 000000000..36db2a190 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseNames-serverErrors.yml @@ -0,0 +1,351 @@ +description: listDatabaseNames-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabaseNames succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11600 + - &retryable_operation + object: *client0 + name: listDatabaseNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + errorCode: 10107 + - &retryable_operation_fails + object: *client0 + name: listDatabaseNames + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - <<: *retryable_operation_fails + object: *client1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseNames.json b/src/test/spec/json/retryable-reads/unified/listDatabaseNames.json new file mode 100644 index 000000000..5590f39a5 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseNames.json @@ -0,0 +1,229 @@ +{ + "description": "listDatabaseNames", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabaseNames succeeds on first attempt", + "operations": [ + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client1", + "name": "listDatabaseNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseNames fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseNames.yml b/src/test/spec/json/retryable-reads/unified/listDatabaseNames.yml new file mode 100644 index 000000000..d80e7d6eb --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseNames.yml @@ -0,0 +1,123 @@ +description: listDatabaseNames + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabaseNames succeeds on first attempt' + operations: + - &retryable_operation + object: *client0 + name: listDatabaseNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - + description: 'ListDatabaseNames succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseNames fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *client1 + name: listDatabaseNames + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListDatabaseNames fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + closeConnection: true + - <<: *retryable_operation_fails + object: *client0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.json new file mode 100644 index 000000000..56f9f3623 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.json @@ -0,0 +1,696 @@ +{ + "description": "listDatabaseObjects-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabaseObjects succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client1", + "name": "listDatabaseObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.yml new file mode 100644 index 000000000..6704febfa --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects-serverErrors.yml @@ -0,0 +1,355 @@ +# listDatabaseObjects returns an array of MongoDatabase objects. +# Not all drivers support this functionality. For more details, see: +# https://github.com/mongodb/specifications/blob/master/source/enumerate-databases.md#enumerating-mongodatabase-objects + +description: listDatabaseObjects-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabaseObjects succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11600 + - &retryable_operation + object: *client0 + name: listDatabaseObjects + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + errorCode: 10107 + - &retryable_operation_fails + object: *client0 + name: listDatabaseObjects + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - <<: *retryable_operation_fails + object: *client1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.json b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.json new file mode 100644 index 000000000..46b1511d4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.json @@ -0,0 +1,229 @@ +{ + "description": "listDatabaseObjects", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabaseObjects succeeds on first attempt", + "operations": [ + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client1", + "name": "listDatabaseObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabaseObjects fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabaseObjects", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.yml b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.yml new file mode 100644 index 000000000..a7d1e7eb5 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabaseObjects.yml @@ -0,0 +1,127 @@ +# listDatabaseObjects returns an array of MongoDatabase objects. +# Not all drivers support this functionality. For more details, see: +# https://github.com/mongodb/specifications/blob/master/source/enumerate-databases.md#enumerating-mongodatabase-objects + +description: listDatabaseObjects + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabaseObjects succeeds on first attempt' + operations: + - &retryable_operation + object: *client0 + name: listDatabaseObjects + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - + description: 'ListDatabaseObjects succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabaseObjects fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *client1 + name: listDatabaseObjects + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListDatabaseObjects fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + closeConnection: true + - <<: *retryable_operation_fails + object: *client0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event \ No newline at end of file diff --git a/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.json new file mode 100644 index 000000000..09b935a59 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.json @@ -0,0 +1,696 @@ +{ + "description": "listDatabases-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabases succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client0", + "name": "listDatabases", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "client1", + "name": "listDatabases", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.yml new file mode 100644 index 000000000..1a236bde3 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabases-serverErrors.yml @@ -0,0 +1,351 @@ +description: listDatabases-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabases succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11600 + - &retryable_operation + object: *client0 + name: listDatabases + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + errorCode: 10107 + - &retryable_operation_fails + object: *client0 + name: listDatabases + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + errorCode: 10107 + - <<: *retryable_operation_fails + object: *client1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listDatabases.json b/src/test/spec/json/retryable-reads/unified/listDatabases.json new file mode 100644 index 000000000..4cf5eccc7 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabases.json @@ -0,0 +1,229 @@ +{ + "description": "listDatabases", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListDatabases succeeds on first attempt", + "operations": [ + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabases" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client1", + "name": "listDatabases", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + }, + { + "description": "ListDatabases fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listDatabases" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "listDatabases", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + }, + { + "commandStartedEvent": { + "command": { + "listDatabases": 1 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listDatabases.yml b/src/test/spec/json/retryable-reads/unified/listDatabases.yml new file mode 100644 index 000000000..743e4a483 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listDatabases.yml @@ -0,0 +1,123 @@ +description: listDatabases + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + +initialData: + - + collectionName: &collection_name coll + databaseName: &database_name retryable-reads-tests + documents: [] + +tests: + - + description: 'ListDatabases succeeds on first attempt' + operations: + - &retryable_operation + object: *client0 + name: listDatabases + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listDatabases: 1 + - + description: 'ListDatabases succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listDatabases + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListDatabases fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *client1 + name: listDatabases + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListDatabases fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listDatabases + closeConnection: true + - <<: *retryable_operation_fails + object: *client0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event \ No newline at end of file diff --git a/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.json new file mode 100644 index 000000000..7b9811148 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.json @@ -0,0 +1,749 @@ +{ + "description": "listIndexNames-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListIndexNames succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "listIndexNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.yml new file mode 100644 index 000000000..aa217bbdc --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexNames-serverErrors.yml @@ -0,0 +1,370 @@ +description: listIndexNames-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListIndexNames succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 11600 + - &retryable_operation + object: *collection0 + name: listIndexNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listIndexes: *collection_name + databaseName: *database_name + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listIndexes + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: listIndexNames + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listIndexNames.json b/src/test/spec/json/retryable-reads/unified/listIndexNames.json new file mode 100644 index 000000000..c5fe967ff --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexNames.json @@ -0,0 +1,263 @@ +{ + "description": "listIndexNames", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListIndexNames succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "listIndexNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexNames fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "listIndexNames", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listIndexNames.yml b/src/test/spec/json/retryable-reads/unified/listIndexNames.yml new file mode 100644 index 000000000..f18c223c4 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexNames.yml @@ -0,0 +1,142 @@ +description: listIndexNames + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListIndexNames succeeds on first attempt' + operations: + - &retryable_operation + object: *collection0 + name: listIndexNames + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listIndexes: *collection_name + databaseName: *database_name + - + description: 'ListIndexNames succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexNames fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: listIndexNames + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListIndexNames fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listIndexes + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.json b/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.json new file mode 100644 index 000000000..0110a0acd --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.json @@ -0,0 +1,749 @@ +{ + "description": "listIndexes-serverErrors", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListIndexes succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 11600 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 11602 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 13435 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 13436 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 189 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 91 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 7 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 6 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 89 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 9001 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes fails after two NotWritablePrimary errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes fails after NotWritablePrimary when retryReads is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "collection1", + "name": "listIndexes", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.yml b/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.yml new file mode 100644 index 000000000..964def3d1 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexes-serverErrors.yml @@ -0,0 +1,370 @@ +description: listIndexes-serverErrors + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListIndexes succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 11600 + - &retryable_operation + object: *collection0 + name: listIndexes + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listIndexes: *collection_name + databaseName: *database_name + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 11602 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 10107 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 13435 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 13436 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 189 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 91 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 7 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 6 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 89 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 9001 + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes fails after two NotWritablePrimary errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listIndexes + errorCode: 10107 + - &retryable_operation_fails + object: *collection0 + name: listIndexes + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes fails after NotWritablePrimary when retryReads is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + errorCode: 10107 + - <<: *retryable_operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/listIndexes.json b/src/test/spec/json/retryable-reads/unified/listIndexes.json new file mode 100644 index 000000000..2560e4961 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexes.json @@ -0,0 +1,263 @@ +{ + "description": "listIndexes", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "ListIndexes succeeds on first attempt", + "operations": [ + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes succeeds on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes fails on first attempt", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "listIndexes", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "ListIndexes fails on second attempt", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "listIndexes" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "listIndexes", + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "listIndexes": "coll" + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/listIndexes.yml b/src/test/spec/json/retryable-reads/unified/listIndexes.yml new file mode 100644 index 000000000..9f3932a93 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/listIndexes.yml @@ -0,0 +1,142 @@ +description: listIndexes + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'ListIndexes succeeds on first attempt' + operations: + - &retryable_operation + object: *collection0 + name: listIndexes + expectEvents: + - + client: *client0 + events: + - &retryable_command_started_event + commandStartedEvent: + command: + listIndexes: *collection_name + databaseName: *database_name + - + description: 'ListIndexes succeeds on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - listIndexes + closeConnection: true + - *retryable_operation + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event + - + description: 'ListIndexes fails on first attempt' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - &retryable_operation_fails + object: *collection1 + name: listIndexes + expectError: + isError: true + expectEvents: + - + client: *client1 + events: + - *retryable_command_started_event + - + description: 'ListIndexes fails on second attempt' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: + - listIndexes + closeConnection: true + - <<: *retryable_operation_fails + object: *collection0 + expectEvents: + - + client: *client0 + events: + - *retryable_command_started_event + - *retryable_command_started_event \ No newline at end of file diff --git a/src/test/spec/json/retryable-reads/unified/mapReduce.json b/src/test/spec/json/retryable-reads/unified/mapReduce.json new file mode 100644 index 000000000..745c0ef00 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/mapReduce.json @@ -0,0 +1,284 @@ +{ + "description": "mapReduce", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 0 + }, + { + "_id": 2, + "x": 1 + }, + { + "_id": 3, + "x": 2 + } + ] + } + ], + "tests": [ + { + "description": "MapReduce succeeds with retry on", + "operations": [ + { + "object": "collection0", + "name": "mapReduce", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "expectResult": [ + { + "_id": 0, + "value": 6 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "mapReduce": "coll", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "MapReduce fails with retry on", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "mapReduce" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "mapReduce", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "mapReduce": "coll", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + }, + { + "description": "MapReduce fails with retry off", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryReads": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "mapReduce" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "mapReduce", + "arguments": { + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "mapReduce": "coll", + "map": { + "$code": "function inc() { return emit(0, this.x + 1) }" + }, + "reduce": { + "$code": "function sum(key, values) { return values.reduce((acc, x) => acc + x); }" + }, + "out": { + "inline": 1 + } + }, + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/mapReduce.yml b/src/test/spec/json/retryable-reads/unified/mapReduce.yml new file mode 100644 index 000000000..10e88e916 --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/mapReduce.yml @@ -0,0 +1,153 @@ +description: mapReduce + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - single + - replicaset + - + minServerVersion: 4.1.7 + # serverless proxy does not support mapReduce operation + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-reads-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - + _id: 1 + x: 0 + - + _id: 2 + x: 1 + - + _id: 3 + x: 2 + +tests: + - + description: 'MapReduce succeeds with retry on' + operations: + - &operation_succeeds + object: *collection0 + name: mapReduce + arguments: + map: + $code: 'function inc() { return emit(0, this.x + 1) }' + reduce: + $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' + out: + inline: 1 + expectResult: + - + _id: 0 + value: 6 + expectEvents: + - + client: *client0 + events: + - &command_started_event + commandStartedEvent: + command: + mapReduce: *collection_name + map: + $code: 'function inc() { return emit(0, this.x + 1) }' + reduce: + $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' + out: + inline: 1 + databaseName: *database_name + - + description: 'MapReduce fails with retry on' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: &failCommand_failPoint + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - mapReduce + closeConnection: true + - &operation_fails + object: *collection0 + name: mapReduce + arguments: + map: + $code: 'function inc() { return emit(0, this.x + 1) }' + reduce: + $code: 'function sum(key, values) { return values.reduce((acc, x) => acc + x); }' + out: + inline: 1 + expectError: + isError: true + expectEvents: + - + client: *client0 + events: + - *command_started_event + - + description: 'MapReduce fails with retry off' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryReads: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: *failCommand_failPoint + - <<: *operation_fails + object: *collection1 + expectEvents: + - + client: *client1 + events: + - *command_started_event diff --git a/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.json b/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.json new file mode 100644 index 000000000..8aa6a6b5e --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.json @@ -0,0 +1,147 @@ +{ + "description": "ReadConcernMajorityNotAvailableYet is a retryable read", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-reads-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "readconcernmajoritynotavailableyet_test" + } + } + ], + "initialData": [ + { + "collectionName": "readconcernmajoritynotavailableyet_test", + "databaseName": "retryable-reads-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "tests": [ + { + "description": "Find succeeds on second attempt after ReadConcernMajorityNotAvailableYet", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 134 + } + } + } + }, + { + "name": "find", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "object": "collection0", + "expectResult": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "readconcernmajoritynotavailableyet_test", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "commandName": "find", + "databaseName": "retryable-reads-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "readconcernmajoritynotavailableyet_test", + "filter": { + "_id": { + "$gt": 1 + } + } + }, + "commandName": "find", + "databaseName": "retryable-reads-tests" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.yml b/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.yml new file mode 100644 index 000000000..707a62acd --- /dev/null +++ b/src/test/spec/json/retryable-reads/unified/readConcernMajorityNotAvailableYet.yml @@ -0,0 +1,68 @@ +description: "ReadConcernMajorityNotAvailableYet is a retryable read" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "4.0" + topologies: [single, replicaset] + - minServerVersion: "4.1.7" + topologies: [sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + # Ensure the `configureFailpoint` and `find` commands are run on the same mongos + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name "retryable-reads-tests" + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name "readconcernmajoritynotavailableyet_test" + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +tests: + - description: "Find succeeds on second attempt after ReadConcernMajorityNotAvailableYet" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "find" ] + errorCode: 134 # ReadConcernMajorityNotAvailableYet + - name: find + arguments: + filter: { _id: { $gt: 1 } } + object: *collection0 + expectResult: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 1 } } + commandName: find + databaseName: *database0Name + - commandStartedEvent: + command: + find: *collection0Name + filter: { _id: { $gt: 1 } } + commandName: find + databaseName: *database0Name diff --git a/src/test/spec/json/retryable-writes/README.md b/src/test/spec/json/retryable-writes/README.md new file mode 100644 index 000000000..d9c8890a2 --- /dev/null +++ b/src/test/spec/json/retryable-writes/README.md @@ -0,0 +1,303 @@ +# Retryable Write Tests + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +retryable writes. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +Tests will require a MongoClient created with options defined in the tests. Integration tests will require a running +MongoDB cluster with server versions 3.6.0 or later. The `{setFeatureCompatibilityVersion: 3.6}` admin command will also +need to have been executed to enable support for retryable writes on the cluster. Some tests may have more stringent +version requirements depending on the fail points used. + +## Use as Integration Tests + +Integration tests are expressed in YAML and can be run against a replica set or sharded cluster as denoted by the +top-level `runOn` field. Tests that rely on the `onPrimaryTransactionalWrite` fail point cannot be run against a sharded +cluster because the fail point is not supported by mongos. + +The tests exercise the following scenarios: + +- Single-statement write operations + - Each test expecting a write result will encounter at-most one network error for the write command. Retry attempts + should return without error and allow operation to succeed. Observation of the collection state will assert that + the write occurred at-most once. + - Each test expecting an error will encounter successive network errors for the write command. Observation of the + collection state will assert that the write was never committed on the server. +- Multi-statement write operations + - Each test expecting a write result will encounter at-most one network error for some write command(s) in the batch. + Retry attempts should return without error and allow the batch to ultimately succeed. Observation of the + collection state will assert that each write occurred at-most once. + - Each test expecting an error will encounter successive network errors for some write command in the batch. The batch + will ultimately fail with an error, but observation of the collection state will assert that the failing write was + never committed on the server. We may observe that earlier writes in the batch occurred at-most once. + +We cannot test a scenario where the first and second attempts both encounter network errors but the write does actually +commit during one of those attempts. This is because (1) the fail point only triggers when a write would be committed +and (2) the skip and times options are mutually exclusive. That said, such a test would mainly assert the server's +correctness for at-most once semantics and is not essential to assert driver correctness. + +## Split Batch Tests + +The YAML tests specify bulk write operations that are split by command type (e.g. sequence of insert, update, and delete +commands). Multi-statement write operations may also be split due to `maxWriteBatchSize`, `maxBsonObjectSize`, or +`maxMessageSizeBytes`. + +For instance, an insertMany operation with five 10 MiB documents executed using OP_MSG payload type 0 (i.e. entire +command in one document) would be split into five insert commands in order to respect the 16 MiB `maxBsonObjectSize` +limit. The same insertMany operation executed using OP_MSG payload type 1 (i.e. command arguments pulled out into a +separate payload vector) would be split into two insert commands in order to respect the 48 MB `maxMessageSizeBytes` +limit. + +Noting when a driver might split operations, the `onPrimaryTransactionalWrite` fail point's `skip` option may be used to +control when the fail point first triggers. Once triggered, the fail point will transition to the `alwaysOn` state until +disabled. Driver authors should also note that the server attempts to process all documents in a single insert command +within a single commit (i.e. one insert command with five documents may only trigger the fail point once). This behavior +is unique to insert commands (each statement in an update and delete command is processed independently). + +If testing an insert that is split into two commands, a `skip` of one will allow the fail point to trigger on the second +insert command (because all documents in the first command will be processed in the same commit). When testing an update +or delete that is split into two commands, the `skip` should be set to the number of statements in the first command to +allow the fail point to trigger on the second command. + +## ~~Command Construction Tests~~ + +The command construction prose tests have been removed in favor of command event assertions in the unified format tests. + +## Prose Tests + +The following tests ensure that retryable writes work properly with replica sets and sharded clusters. + +### 1. Test that retryable writes raise an exception when using the MMAPv1 storage engine. + +For this test, execute a write operation, such as `insertOne`, which should generate an exception. Assert that the error +message is the replacement error message: + +```text +This MongoDB deployment does not support retryable writes. Please add +retryWrites=false to your connection string. +``` + +and the error code is 20. + +> [!NOTE] +> Drivers that rely on `serverStatus` to determine the storage engine in use MAY skip this test for sharded clusters, +> since `mongos` does not report this information in its `serverStatus` response. + +### 2. Test that drivers properly retry after encountering PoolClearedErrors. + +This test MUST be implemented by any driver that implements the CMAP specification. + +This test requires MongoDB 4.3.4+ for both the `errorLabels` and `blockConnection` fail point options. + +1. Create a client with maxPoolSize=1 and retryWrites=true. If testing against a sharded deployment, be sure to connect + to only a single mongos. + +2. Enable the following failpoint: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["insert"], + errorCode: 91, + blockConnection: true, + blockTimeMS: 1000, + errorLabels: ["RetryableWriteError"] + } + } + ``` + +3. Start two threads and attempt to perform an `insertOne` simultaneously on both. + +4. Verify that both `insertOne` attempts succeed. + +5. Via CMAP monitoring, assert that the first check out succeeds. + +6. Via CMAP monitoring, assert that a PoolClearedEvent is then emitted. + +7. Via CMAP monitoring, assert that the second check out then fails due to a connection error. + +8. Via Command Monitoring, assert that exactly three `insert` CommandStartedEvents were observed in total. + +9. Disable the failpoint. + +### 3. Test that drivers return the original error after encountering a WriteConcernError with a RetryableWriteError label. + +This test MUST: + +- be implemented by any driver that implements the Command Monitoring specification, +- only run against replica sets as mongos does not propagate the NoWritesPerformed label to the drivers. +- be run against server versions 6.0 and above. + +Additionally, this test requires drivers to set a fail point after an `insertOne` operation but before the subsequent +retry. Drivers that are unable to set a failCommand after the CommandSucceededEvent SHOULD use mocking or write a unit +test to cover the same sequence of events. + +1. Create a client with `retryWrites=true`. + +2. Configure a fail point with error code `91` (ShutdownInProgress): + + ```javascript + { + configureFailPoint: "failCommand", + mode: {times: 1}, + data: { + failCommands: ["insert"], + errorLabels: ["RetryableWriteError"], + writeConcernError: { code: 91 } + } + } + ``` + +3. Via the command monitoring CommandSucceededEvent, configure a fail point with error code `10107` (NotWritablePrimary) + and a NoWritesPerformed label: + + ```javascript + { + configureFailPoint: "failCommand", + mode: {times: 1}, + data: { + failCommands: ["insert"], + errorCode: 10107, + errorLabels: ["RetryableWriteError", "NoWritesPerformed"] + } + } + ``` + + Drivers SHOULD only configure the `10107` fail point command if the the succeeded event is for the `91` error + configured in step 2. + +4. Attempt an `insertOne` operation on any record for any database and collection. For the resulting error, assert that + the associated error code is `91`. + +5. Disable the fail point: + + ```javascript + { + configureFailPoint: "failCommand", + mode: "off" + } + ``` + +### 4. Test that in a sharded cluster writes are retried on a different mongos when one is available. + +This test MUST be executed against a sharded cluster that has at least two mongos instances, supports +`retryWrites=true`, has enabled the `configureFailPoint` command, and supports the `errorLabels` field (MongoDB 4.3.1+). + +> [!NOTE] +> This test cannot reliably distinguish "retry on a different mongos due to server deprioritization" (the behavior +> intended to be tested) from "retry on a different mongos due to normal SDAM randomized suitable server selection". +> Verify relevant code paths are correctly executed by the tests using external means such as a logging, debugger, code +> coverage tool, etc. + +1. Create two clients `s0` and `s1` that each connect to a single mongos from the sharded cluster. They must not connect + to the same mongos. + +2. Configure the following fail point for both `s0` and `s1`: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["insert"], + errorCode: 6, + errorLabels: ["RetryableWriteError"] + } + } + ``` + +3. Create a client `client` with `retryWrites=true` that connects to the cluster using the same two mongoses as `s0` and + `s1`. + +4. Enable failed command event monitoring for `client`. + +5. Execute an `insert` command with `client`. Assert that the command failed. + +6. Assert that two failed command events occurred. Assert that the failed command events occurred on different mongoses. + +7. Disable the fail points on both `s0` and `s1`. + +### 5. Test that in a sharded cluster writes are retried on the same mongos when no others are available. + +This test MUST be executed against a sharded cluster that supports `retryWrites=true`, has enabled the +`configureFailPoint` command, and supports the `errorLabels` field (MongoDB 4.3.1+). + +Note: this test cannot reliably distinguish "retry on a different mongos due to server deprioritization" (the behavior +intended to be tested) from "retry on a different mongos due to normal SDAM behavior of randomized suitable server +selection". Verify relevant code paths are correctly executed by the tests using external means such as a logging, +debugger, code coverage tool, etc. + +1. Create a client `s0` that connects to a single mongos from the cluster. + +2. Configure the following fail point for `s0`: + + ```javascript + { + configureFailPoint: "failCommand", + mode: { times: 1 }, + data: { + failCommands: ["insert"], + errorCode: 6, + errorLabels: ["RetryableWriteError"], + closeConnection: true + } + } + ``` + +3. Create a client `client` with `directConnection=false` (when not set by default) and `retryWrites=true` that connects + to the cluster using the same single mongos as `s0`. + +4. Enable succeeded and failed command event monitoring for `client`. + +5. Execute an `insert` command with `client`. Assert that the command succeeded. + +6. Assert that exactly one failed command event and one succeeded command event occurred. Assert that both events + occurred on the same mongos. + +7. Disable the fail point on `s0`. + +## Changelog + +- 2024-10-29: Convert command construction tests to unified format. + +- 2024-05-30: Migrated from reStructuredText to Markdown. + +- 2024-02-27: Convert legacy retryable writes tests to unified format. + +- 2024-02-21: Update prose test 4 and 5 to workaround SDAM behavior preventing execution of deprioritization code paths. + +- 2024-01-05: Fix typo in prose test title. + +- 2024-01-03: Note server version requirements for fail point options and revise tests to specify the `errorLabels` + option at the top-level instead of within `writeConcernError`. + +- 2023-08-26: Add prose tests for retrying in a sharded cluster. + +- 2022-08-30: Add prose test verifying correct error handling for errors with the NoWritesPerformed label, which is to + return the original error. + +- 2022-04-22: Clarifications to `serverless` and `useMultipleMongoses`. + +- 2021-08-27: Add `serverless` to `runOn`. Clarify behavior of `useMultipleMongoses` for `LoadBalanced` topologies. + +- 2021-04-23: Add `load-balanced` to test topology requirements. + +- 2021-03-24: Add prose test verifying `PoolClearedErrors` are retried. + +- 2019-10-21: Add `errorLabelsContain` and `errorLabelsContain` fields to `result` + +- 2019-08-07: Add Prose Tests section + +- 2019-06-07: Mention $merge stage for aggregate alongside $out + +- 2019-03-01: Add top-level `runOn` field to denote server version and/or topology requirements requirements for the + test file. Removes the `minServerVersion` and `maxServerVersion` top-level fields, which are now expressed within + `runOn` elements. + + Add test-level `useMultipleMongoses` field. diff --git a/src/test/spec/json/retryable-writes/README.rst b/src/test/spec/json/retryable-writes/README.rst deleted file mode 100644 index 5ddfb6a2d..000000000 --- a/src/test/spec/json/retryable-writes/README.rst +++ /dev/null @@ -1,487 +0,0 @@ -===================== -Retryable Write Tests -===================== - -.. contents:: - ----- - -Introduction -============ - -Tests in this directory are platform-independent tests that drivers can use to -prove their conformance to the Retryable Writes specification. - -Tests in the ``unified`` directory are implemented in the -`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__. - -Tests in the ``legacy`` directory should be executed as described below. - -Several prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Tests will require a MongoClient created with options defined in the tests. -Integration tests will require a running MongoDB cluster with server versions -3.6.0 or later. The ``{setFeatureCompatibilityVersion: 3.6}`` admin command -will also need to have been executed to enable support for retryable writes on -the cluster. Some tests may have more stringent version requirements depending -on the fail points used. - -Server Fail Point -================= - -onPrimaryTransactionalWrite ---------------------------- - -Some tests depend on a server fail point, ``onPrimaryTransactionalWrite``, which -allows us to force a network error before the server would return a write result -to the client. The fail point also allows control whether the server will -successfully commit the write via its ``failBeforeCommitExceptionCode`` option. -Keep in mind that the fail point only triggers for transaction writes (i.e. write -commands including ``txnNumber`` and ``lsid`` fields). See `SERVER-29606`_ for -more information. - -.. _SERVER-29606: https://jira.mongodb.org/browse/SERVER-29606 - -The fail point may be configured like so:: - - db.runCommand({ - configureFailPoint: "onPrimaryTransactionalWrite", - mode: , - data: - }); - -``mode`` is a generic fail point option and may be assigned a string or document -value. The string values ``"alwaysOn"`` and ``"off"`` may be used to enable or -disable the fail point, respectively. A document may be used to specify either -``times`` or ``skip``, which are mutually exclusive: - -- ``{ times: }`` may be used to limit the number of times the fail - point may trigger before transitioning to ``"off"``. -- ``{ skip: }`` may be used to defer the first trigger of a fail - point, after which it will transition to ``"alwaysOn"``. - -The ``data`` option is a document that may be used to specify options that -control the fail point's behavior. As noted in `SERVER-29606`_, -``onPrimaryTransactionalWrite`` supports the following ``data`` options, which -may be combined if desired: - -- ``closeConnection``: Boolean option, which defaults to ``true``. If ``true``, - the connection on which the write is executed will be closed before a result - can be returned. -- ``failBeforeCommitExceptionCode``: Integer option, which is unset by default. - If set, the specified exception code will be thrown and the write will not be - committed. If unset, the write will be allowed to commit. - -failCommand ------------ - -Some tests depend on a server fail point, ``failCommand``, which allows the -client to force the server to return an error. Unlike -``onPrimaryTransactionalWrite``, ``failCommand`` does not allow the client to -directly control whether the server will commit the operation (execution of the -write depends on whether the ``closeConnection`` and/or ``errorCode`` options -are specified). See: `failCommand <../../transactions/tests#failcommand>`_ in -the Transactions spec test suite for more information. - -Disabling Fail Points after Test Execution ------------------------------------------- - -After each test that configures a fail point, drivers should disable the fail -point to avoid spurious failures in subsequent tests. The fail point may be -disabled like so:: - - db.runCommand({ - configureFailPoint: , - mode: "off" - }); - -Speeding Up Tests -================= - -See `Speeding Up Tests <../../retryable-reads/tests/README.rst#speeding-up-tests>`_ in the retryable reads spec tests. - -Use as Integration Tests -======================== - -Integration tests are expressed in YAML and can be run against a replica set or -sharded cluster as denoted by the top-level ``runOn`` field. Tests that rely on -the ``onPrimaryTransactionalWrite`` fail point cannot be run against a sharded -cluster because the fail point is not supported by mongos. - -The tests exercise the following scenarios: - -- Single-statement write operations - - - Each test expecting a write result will encounter at-most one network error - for the write command. Retry attempts should return without error and allow - operation to succeed. Observation of the collection state will assert that - the write occurred at-most once. - - - Each test expecting an error will encounter successive network errors for - the write command. Observation of the collection state will assert that the - write was never committed on the server. - -- Multi-statement write operations - - - Each test expecting a write result will encounter at-most one network error - for some write command(s) in the batch. Retry attempts should return without - error and allow the batch to ultimately succeed. Observation of the - collection state will assert that each write occurred at-most once. - - - Each test expecting an error will encounter successive network errors for - some write command in the batch. The batch will ultimately fail with an - error, but observation of the collection state will assert that the failing - write was never committed on the server. We may observe that earlier writes - in the batch occurred at-most once. - -We cannot test a scenario where the first and second attempts both encounter -network errors but the write does actually commit during one of those attempts. -This is because (1) the fail point only triggers when a write would be committed -and (2) the skip and times options are mutually exclusive. That said, such a -test would mainly assert the server's correctness for at-most once semantics and -is not essential to assert driver correctness. - -Test Format ------------ - -Each YAML file has the following keys: - -- ``runOn`` (optional): An array of server version and/or topology requirements - for which the tests can be run. If the test environment satisfies one or more - of these requirements, the tests may be executed; otherwise, this file should - be skipped. If this field is omitted, the tests can be assumed to have no - particular requirements and should be executed. Each element will have some or - all of the following fields: - - - ``minServerVersion`` (optional): The minimum server version (inclusive) - required to successfully run the tests. If this field is omitted, it should - be assumed that there is no lower bound on the required server version. - - - ``maxServerVersion`` (optional): The maximum server version (inclusive) - against which the tests can be run successfully. If this field is omitted, - it should be assumed that there is no upper bound on the required server - version. - - - ``topology`` (optional): An array of server topologies against which the - tests can be run successfully. Valid topologies are "single", - "replicaset", "sharded", and "load-balanced". If this field is omitted, - the default is all topologies (i.e. ``["single", "replicaset", "sharded", - "load-balanced"]``). - - - ``serverless``: (optional): Whether or not the test should be run on Atlas - Serverless instances. Valid values are "require", "forbid", and "allow". If - "require", the test MUST only be run on Atlas Serverless instances. If - "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted - or "allow", this option has no effect. - - The test runner MUST be informed whether or not Atlas Serverless is being - used in order to determine if this requirement is met (e.g. through an - environment variable or configuration option). - - Note: the Atlas Serverless proxy imitates mongos, so the test runner is not - capable of determining if Atlas Serverless is in use by issuing commands - such as ``buildInfo`` or ``hello``. Furthermore, connections to Atlas - Serverless use a load balancer, so the topology will appear as - "load-balanced". - -- ``data``: The data that should exist in the collection under test before each - test run. - -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some or all of the following fields: - - - ``description``: The name of the test. - - - ``clientOptions``: Parameters to pass to MongoClient(). - - - ``useMultipleMongoses`` (optional): If ``true``, and the topology type is - ``Sharded``, the MongoClient for this test should be initialized with multiple - mongos seed addresses. If ``false`` or omitted, only a single mongos address - should be specified. - - If ``true``, the topology type is ``LoadBalanced``, and Atlas Serverless is - not being used, the MongoClient for this test should be initialized with the - URI of the load balancer fronting multiple servers. If ``false`` or omitted, - the MongoClient for this test should be initialized with the URI of the load - balancer fronting a single server. - - ``useMultipleMongoses`` only affects ``Sharded`` and ``LoadBalanced`` - topologies (excluding Atlas Serverless). - - - ``failPoint`` (optional): The ``configureFailPoint`` command document to run - to configure a fail point on the primary server. Drivers must ensure that - ``configureFailPoint`` is the first field in the command. This option and - ``useMultipleMongoses: true`` are mutually exclusive. - - - ``operation``: Document describing the operation to be executed. The - operation should be executed through a collection object derived from a - client that has been created with ``clientOptions``. The operation will have - some or all of the following fields: - - - ``name``: The name of the operation as defined in the CRUD specification. - - - ``arguments``: The names and values of arguments from the CRUD - specification. - - - ``outcome``: Document describing the return value and/or expected state of - the collection after the operation is executed. This will have some or all - of the following fields: - - - ``error``: If ``true``, the test should expect an error or exception. Note - that some drivers may report server-side errors as a write error within a - write result object. - - - ``result``: The return value from the operation. This will correspond to - an operation's result object as defined in the CRUD specification. This - field may be omitted if ``error`` is ``true``. If this field is present - and ``error`` is ``true`` (generally for multi-statement tests), the - result reports information about operations that succeeded before an - unrecoverable failure. In that case, drivers may choose to check the - result object if their BulkWriteException (or equivalent) provides access - to a write result object. - - - ``errorLabelsContain``: A list of error label strings that the - error is expected to have. - - - ``errorLabelsOmit``: A list of error label strings that the - error is expected not to have. - - - ``collection``: - - - ``name`` (optional): The name of the collection to verify. If this isn't - present then use the collection under test. - - - ``data``: The data that should exist in the collection after the - operation has been run. - -Split Batch Tests -================= - -The YAML tests specify bulk write operations that are split by command type -(e.g. sequence of insert, update, and delete commands). Multi-statement write -operations may also be split due to ``maxWriteBatchSize``, -``maxBsonObjectSize``, or ``maxMessageSizeBytes``. - -For instance, an insertMany operation with five 10 MiB documents executed using -OP_MSG payload type 0 (i.e. entire command in one document) would be split into -five insert commands in order to respect the 16 MiB ``maxBsonObjectSize`` limit. -The same insertMany operation executed using OP_MSG payload type 1 (i.e. command -arguments pulled out into a separate payload vector) would be split into two -insert commands in order to respect the 48 MB ``maxMessageSizeBytes`` limit. - -Noting when a driver might split operations, the ``onPrimaryTransactionalWrite`` -fail point's ``skip`` option may be used to control when the fail point first -triggers. Once triggered, the fail point will transition to the ``alwaysOn`` -state until disabled. Driver authors should also note that the server attempts -to process all documents in a single insert command within a single commit (i.e. -one insert command with five documents may only trigger the fail point once). -This behavior is unique to insert commands (each statement in an update and -delete command is processed independently). - -If testing an insert that is split into two commands, a ``skip`` of one will -allow the fail point to trigger on the second insert command (because all -documents in the first command will be processed in the same commit). When -testing an update or delete that is split into two commands, the ``skip`` should -be set to the number of statements in the first command to allow the fail point -to trigger on the second command. - -Command Construction Tests -========================== - -Drivers should also assert that command documents are properly constructed with -or without a transaction ID, depending on whether the write operation is -supported. `Command Logging and Monitoring`_ may be used to check for the presence of a -``txnNumber`` field in the command document. Note that command documents may -always include an ``lsid`` field per the `Driver Session`_ specification. - -.. _Command Logging and Monitoring: ../../command-logging-and-monitoring/command-logging-and-monitoring.rst -.. _Driver Session: ../../sessions/driver-sessions.rst - -These tests may be run against both a replica set and shard cluster. - -Drivers should test that transaction IDs are never included in commands for -unsupported write operations: - -* Write commands with unacknowledged write concerns (e.g. ``{w: 0}``) - -* Unsupported single-statement write operations - - - ``updateMany()`` - - ``deleteMany()`` - -* Unsupported multi-statement write operations - - - ``bulkWrite()`` that includes ``UpdateMany`` or ``DeleteMany`` - -* Unsupported write commands - - - ``aggregate`` with write stage (e.g. ``$out``, ``$merge``) - -Drivers should test that transactions IDs are always included in commands for -supported write operations: - -* Supported single-statement write operations - - - ``insertOne()`` - - ``updateOne()`` - - ``replaceOne()`` - - ``deleteOne()`` - - ``findOneAndDelete()`` - - ``findOneAndReplace()`` - - ``findOneAndUpdate()`` - -* Supported multi-statement write operations - - - ``insertMany()`` with ``ordered=true`` - - ``insertMany()`` with ``ordered=false`` - - ``bulkWrite()`` with ``ordered=true`` (no ``UpdateMany`` or ``DeleteMany``) - - ``bulkWrite()`` with ``ordered=false`` (no ``UpdateMany`` or ``DeleteMany``) - -Prose Tests -=========== - -The following tests ensure that retryable writes work properly with replica sets -and sharded clusters. - -#. Test that retryable writes raise an exception when using the MMAPv1 storage - engine. For this test, execute a write operation, such as ``insertOne``, - which should generate an exception. Assert that the error message is the - replacement error message:: - - This MongoDB deployment does not support retryable writes. Please add - retryWrites=false to your connection string. - - and the error code is 20. - - **Note**: Drivers that rely on ``serverStatus`` to determine the storage engine - in use MAY skip this test for sharded clusters, since ``mongos`` does not report - this information in its ``serverStatus`` response. - -#. Test that drivers properly retry after encountering PoolClearedErrors. This - test MUST be implemented by any driver that implements the CMAP - specification. This test requires MongoDB 4.2.9+ for ``blockConnection`` support in the failpoint. - - 1. Create a client with maxPoolSize=1 and retryWrites=true. If testing - against a sharded deployment, be sure to connect to only a single mongos. - - 2. Enable the following failpoint:: - - { - configureFailPoint: "failCommand", - mode: { times: 1 }, - data: { - failCommands: ["insert"], - errorCode: 91, - blockConnection: true, - blockTimeMS: 1000, - errorLabels: ["RetryableWriteError"] - } - } - - 3. Start two threads and attempt to perform an ``insertOne`` simultaneously on both. - - 4. Verify that both ``insertOne`` attempts succeed. - - 5. Via CMAP monitoring, assert that the first check out succeeds. - - 6. Via CMAP monitoring, assert that a PoolClearedEvent is then emitted. - - 7. Via CMAP monitoring, assert that the second check out then fails due to a - connection error. - - 8. Via Command Monitoring, assert that exactly three ``insert`` - CommandStartedEvents were observed in total. - - 9. Disable the failpoint. - -#. Test that drivers return the original error after encountering a - WriteConcernError with a RetryableWriteError label. This test MUST - - 1. be implemented by any driver that implements the Command Monitoring - specification, - - 2. only run against replica sets as mongos does not propagate the - NoWritesPerformed label to the drivers. - - 3. be run against server versions 6.0 and above. - - Additionally, this test requires drivers to set a fail point after an - ``insertOne`` operation but before the subsequent retry. Drivers that are - unable to set a failCommand after the CommandSucceededEvent SHOULD use - mocking or write a unit test to cover the same sequence of events. - - - 1. Create a client with ``retryWrites=true``. - - 2. Configure a fail point with error code ``91`` (ShutdownInProgress):: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: {times: 1}, - data: { - writeConcernError: { - code: 91, - errorLabels: ["RetryableWriteError"], - }, - failCommands: ["insert"], - }, - }); - - 3. Via the command monitoring CommandSucceededEvent, configure a fail point - with error code ``10107`` (NotWritablePrimary) and a NoWritesPerformed - label:: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: {times: 1}, - data: { - errorCode: 10107, - errorLabels: ["RetryableWriteError", "NoWritesPerformed"], - failCommands: ["insert"], - }, - }); - - Drivers SHOULD only configure the ``10107`` fail point command if the the - succeeded event is for the ``91`` error configured in step 2. - - 4. Attempt an ``insertOne`` operation on any record for any database and - collection. For the resulting error, assert that the associated error code - is ``91``. - - 5. Disable the fail point:: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: "off", - }) - -Changelog -========= - -:2022-08-30: Add prose test verifying correct error handling for errors with - the NoWritesPerformed label, which is to return the original - error. - -:2022-04-22: Clarifications to ``serverless`` and ``useMultipleMongoses``. - -:2021-08-27: Add ``serverless`` to ``runOn``. Clarify behavior of - ``useMultipleMongoses`` for ``LoadBalanced`` topologies. - -:2021-04-23: Add ``load-balanced`` to test topology requirements. - -:2021-03-24: Add prose test verifying ``PoolClearedErrors`` are retried. - -:2019-10-21: Add ``errorLabelsContain`` and ``errorLabelsContain`` fields to - ``result`` - -:2019-08-07: Add Prose Tests section - -:2019-06-07: Mention $merge stage for aggregate alongside $out - -:2019-03-01: Add top-level ``runOn`` field to denote server version and/or - topology requirements requirements for the test file. Removes the - ``minServerVersion`` and ``maxServerVersion`` top-level fields, - which are now expressed within ``runOn`` elements. - - Add test-level ``useMultipleMongoses`` field. diff --git a/src/test/spec/json/retryable-writes/etc/templates/handshakeError.yml.template b/src/test/spec/json/retryable-writes/etc/templates/handshakeError.yml.template index 3974392a6..03b0e19bc 100644 --- a/src/test/spec/json/retryable-writes/etc/templates/handshakeError.yml.template +++ b/src/test/spec/json/retryable-writes/etc/templates/handshakeError.yml.template @@ -2,7 +2,7 @@ description: "retryable writes handshake failures" -schemaVersion: "1.3" +schemaVersion: "1.4" # For `serverless: forbid` runOnRequirements: - minServerVersion: "4.2" @@ -51,6 +51,11 @@ tests: # - Tests whether operation successfully retries the handshake and succeeds. {% for operation in operations %} - description: "{{operation.object}}.{{operation.operation_name}} succeeds after retryable handshake network error" + {%- if (operation.operation_name == 'clientBulkWrite') %} + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + {%- endif %} operations: - name: failPoint object: testRunner @@ -95,6 +100,11 @@ tests: commandName: {{operation.command_name}} - description: "{{operation.object}}.{{operation.operation_name}} succeeds after retryable handshake server error (ShutdownInProgress)" + {%- if (operation.operation_name == 'clientBulkWrite') %} + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + {%- endif %} operations: - name: failPoint object: testRunner diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.json deleted file mode 100644 index 66c3ecb33..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "BulkWrite succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 1, - "insertedIds": { - "1": 3 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.yml deleted file mode 100644 index fb9d7e47e..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite-errorLabels.yml +++ /dev/null @@ -1,77 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "BulkWrite succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "bulkWrite" - arguments: - requests: - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x: 1 } } - options: { ordered: true } - outcome: # Driver retries operation and it succeeds - result: - deletedCount: 1 - insertedCount: 1 - insertedIds: { 1: 3 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: {} - collection: - data: - - { _id: 2, x: 23 } - - { _id: 3, x: 33 } - - - description: "BulkWrite fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "bulkWrite" - arguments: - requests: - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x: 1 } } - options: { ordered: true } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.json deleted file mode 100644 index 1e6cc74c0..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "BulkWrite succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 1, - "insertedIds": { - "1": 3 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 1, - "insertedIds": { - "1": 3 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.yml deleted file mode 100644 index 6ca6540f6..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite-serverErrors.yml +++ /dev/null @@ -1,130 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "BulkWrite succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 1 - insertedIds: { 1: 3 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 2, x: 23 } - - { _id: 3, x: 33 } - - - description: "BulkWrite succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 1 - insertedIds: { 1: 3 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 2, x: 23 } - - { _id: 3, x: 33 } - - - - description: "BulkWrite fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["update"] - closeConnection: true - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite.json b/src/test/spec/json/retryable-writes/legacy/bulkWrite.json deleted file mode 100644 index 72a8d0189..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite.json +++ /dev/null @@ -1,806 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "First command is retried", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 1, - "insertedIds": { - "0": 2 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - } - ] - } - } - }, - { - "description": "All commands are retried", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 7 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 4, - "x": 44 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 5, - "x": 55 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "_id": 3, - "x": 333 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 3, - "insertedIds": { - "0": 2, - "2": 3, - "4": 5 - }, - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 1, - "upsertedIds": { - "3": 4 - } - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 333 - }, - { - "_id": 4, - "x": 45 - }, - { - "_id": 5, - "x": 55 - } - ] - } - } - }, - { - "description": "Both commands are retried after their first statement fails", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "0": 2 - }, - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 23 - } - ] - } - } - }, - { - "description": "Second command is retried after its second statement fails", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "skip": 2 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "0": 2 - }, - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 23 - } - ] - } - } - }, - { - "description": "BulkWrite with unordered execution", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 2, - "insertedIds": { - "0": 2, - "1": 3 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "First insertOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "Second updateOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "skip": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "0": 2 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "Third updateOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "skip": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "1": 2 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "Single-document write following deleteMany is retried", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": 11 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 1, - "insertedIds": { - "1": 2 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "Single-document write following updateMany is retried", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateMany", - "arguments": { - "filter": { - "x": 11 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "1": 2 - }, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/bulkWrite.yml b/src/test/spec/json/retryable-writes/legacy/bulkWrite.yml deleted file mode 100644 index 939dacf77..000000000 --- a/src/test/spec/json/retryable-writes/legacy/bulkWrite.yml +++ /dev/null @@ -1,396 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - -tests: - - - description: "First command is retried" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 1 - insertedIds: { 0: 2 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 2, x: 23 } - - - # Write operations in this ordered batch are intentionally sequenced so - # that each write command consists of a single statement, which will - # fail on the first attempt and succeed on the second, retry attempt. - description: "All commands are retried" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 7 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - - - name: "updateOne" - arguments: - filter: { _id: 4, x: 44 } - update: { $inc: { x : 1 }} - upsert: true - - - name: "insertOne" - arguments: - document: { _id: 5, x: 55 } - - - name: "replaceOne" - arguments: - filter: { _id: 3 } - replacement: { _id: 3, x: 333 } - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 3 - insertedIds: { 0: 2, 2: 3, 4: 5 } - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 1 - upsertedIds: { 3: 4 } - collection: - data: - - { _id: 2, x: 23 } - - { _id: 3, x: 333 } - - { _id: 4, x: 45 } - - { _id: 5, x: 55 } - - - description: "Both commands are retried after their first statement fails" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: { 0: 2 } - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 23 } - - - description: "Second command is retried after its second statement fails" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { skip: 2 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: { 0: 2 } - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 23 } - - - description: "BulkWrite with unordered execution" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - options: { ordered: false } - outcome: - result: - deletedCount: 0 - insertedCount: 2 - insertedIds: { 0: 2, 1: 3 } - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "First insertOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - options: { ordered: true } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 0 - insertedIds: { } - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - - description: "Second updateOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { skip: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - - - name: "deleteOne" - arguments: - filter: { _id: 1 } - options: { ordered: true } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: { 0: 2 } - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - - description: "Third updateOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { skip: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - - - name: "updateOne" - arguments: - filter: { _id: 2 } - update: { $inc: { x : 1 }} - options: { ordered: true } - outcome: - error: true - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: { 1: 2 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - # The onPrimaryTransactionalWrite fail point only triggers for write - # operations that include a transaction ID. Therefore, it will not - # affect the initial deleteMany and will trigger once (and only once) - # for the first insertOne attempt. - description: "Single-document write following deleteMany is retried" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "deleteMany" - arguments: - filter: { x: 11 } - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - options: { ordered: true } - outcome: - result: - deletedCount: 1 - insertedCount: 1 - insertedIds: { 1: 2 } - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 2, x: 22 } - - - # The onPrimaryTransactionalWrite fail point only triggers for write - # operations that include a transaction ID. Therefore, it will not - # affect the initial updateMany and will trigger once (and only once) - # for the first insertOne attempt. - description: "Single-document write following updateMany is retried" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "bulkWrite" - arguments: - requests: - - - name: "updateMany" - arguments: - filter: { x: 11 } - update: { $inc: { x : 1 }} - - - name: "insertOne" - arguments: - document: { _id: 2, x: 22 } - options: { ordered: true } - outcome: - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: { 1: 2 } - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - upsertedIds: { } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/deleteMany.json b/src/test/spec/json/retryable-writes/legacy/deleteMany.json deleted file mode 100644 index faa21c44f..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteMany.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "DeleteMany ignores retryWrites", - "useMultipleMongoses": true, - "operation": { - "name": "deleteMany", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": { - "deletedCount": 2 - }, - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/deleteMany.yml b/src/test/spec/json/retryable-writes/legacy/deleteMany.yml deleted file mode 100644 index 4743953ff..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteMany.yml +++ /dev/null @@ -1,22 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "DeleteMany ignores retryWrites" - useMultipleMongoses: true - operation: - name: "deleteMany" - arguments: - filter: { } - outcome: - result: - deletedCount: 2 - collection: - data: [] diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.json deleted file mode 100644 index c14692fd1..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "DeleteOne succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "DeleteOne fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.yml deleted file mode 100644 index 9ee5c7426..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne-errorLabels.yml +++ /dev/null @@ -1,48 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "DeleteOne succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["delete"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: # Driver retries operation and it succeeds - result: - deletedCount: 1 - collection: - data: - - { _id: 2, x: 22 } - - - description: "DeleteOne fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["delete"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.json deleted file mode 100644 index a1a27838d..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.json +++ /dev/null @@ -1,153 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "DeleteOne succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "DeleteOne succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "DeleteOne fails with RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.yml deleted file mode 100644 index f4c98c919..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne-serverErrors.yml +++ /dev/null @@ -1,73 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "DeleteOne succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["delete"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - result: - deletedCount: 1 - collection: - data: - - { _id: 2, x: 22 } - - - description: "DeleteOne succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["delete"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - result: - deletedCount: 1 - collection: - data: - - { _id: 2, x: 22 } - - - description: "DeleteOne fails with RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["delete"] - closeConnection: true - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne.json b/src/test/spec/json/retryable-writes/legacy/deleteOne.json deleted file mode 100644 index 592937ace..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "DeleteOne is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "DeleteOne is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "DeleteOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/deleteOne.yml b/src/test/spec/json/retryable-writes/legacy/deleteOne.yml deleted file mode 100644 index b15c991cd..000000000 --- a/src/test/spec/json/retryable-writes/legacy/deleteOne.yml +++ /dev/null @@ -1,57 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "DeleteOne is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - result: - deletedCount: 1 - collection: - data: - - { _id: 2, x: 22 } - - - description: "DeleteOne is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - result: - deletedCount: 1 - collection: - data: - - { _id: 2, x: 22 } - - - description: "DeleteOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "deleteOne" - arguments: - filter: { _id: 1 } - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.json deleted file mode 100644 index 60e6e0a7b..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndDelete succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndDelete fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.yml deleted file mode 100644 index 5192c5adf..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-errorLabels.yml +++ /dev/null @@ -1,49 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "FindOneAndDelete succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 } } - sort: { x: 1 } - outcome: # Driver retries operation and it succeeds - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 2, x: 22 } - - - description: "FindOneAndDelete fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 } } - sort: { x: 1 } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.json deleted file mode 100644 index c18b63f45..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.json +++ /dev/null @@ -1,170 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndDelete succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndDelete succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndDelete fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.yml deleted file mode 100644 index 688ee3342..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete-serverErrors.yml +++ /dev/null @@ -1,74 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndDelete succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 }} - sort: { x: 1 } - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 2, x: 22 } - - - description: "FindOneAndDelete succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 }} - sort: { x: 1 } - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 2, x: 22 } - - - description: "FindOneAndDelete fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["findAndModify"] - closeConnection: true - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 } } - sort: { x: 1 } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.json b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.json deleted file mode 100644 index 0cbe18108..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndDelete is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndDelete is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndDelete is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "x": { - "$gte": 11 - } - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.yml deleted file mode 100644 index 1456ad716..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndDelete.yml +++ /dev/null @@ -1,58 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndDelete is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 }} - sort: { x: 1 } - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 2, x: 22 } - - - description: "FindOneAndDelete is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 }} - sort: { x: 1 } - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 2, x: 22 } - - - description: "FindOneAndDelete is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndDelete" - arguments: - filter: { x: { $gte: 11 }} - sort: { x: 1 } - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.json deleted file mode 100644 index afa2f47af..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndReplace succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndReplace fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.yml deleted file mode 100644 index 184366163..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-errorLabels.yml +++ /dev/null @@ -1,52 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "FindOneAndReplace succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: # Driver retries operation and it succeeds - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "FindOneAndReplace fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.json deleted file mode 100644 index 944a3af84..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.json +++ /dev/null @@ -1,178 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndReplace succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndReplace succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndReplace fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.yml deleted file mode 100644 index 6f0f31874..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace-serverErrors.yml +++ /dev/null @@ -1,80 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndReplace succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "FindOneAndReplace succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - - description: "FindOneAndReplace fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["findAndModify"] - closeConnection: true - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.json b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.json deleted file mode 100644 index e1f9ab7f8..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.json +++ /dev/null @@ -1,145 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndReplace is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndReplace is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndReplace is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - }, - "returnDocument": "Before" - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.yml deleted file mode 100644 index 36d81d461..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndReplace.yml +++ /dev/null @@ -1,63 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndReplace is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "FindOneAndReplace is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "FindOneAndReplace is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndReplace" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - returnDocument: "Before" - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.json deleted file mode 100644 index 19b3a9e77..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndUpdate succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.yml deleted file mode 100644 index 03751d568..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-errorLabels.yml +++ /dev/null @@ -1,52 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "FindOneAndUpdate succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: "Before" - outcome: # Driver retries operation and it succeeds - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "FindOneAndUpdate fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: "Before" - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.json deleted file mode 100644 index e83a61061..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndUpdate succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.yml deleted file mode 100644 index c1e0b6a7c..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate-serverErrors.yml +++ /dev/null @@ -1,79 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndUpdate succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "FindOneAndUpdate succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["findAndModify"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "FindOneAndUpdate fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["findAndModify"] - closeConnection: true - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - returnDocument: "Before" - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.json b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.json deleted file mode 100644 index 9ae2d87d8..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "FindOneAndUpdate is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - } - }, - "outcome": { - "result": { - "_id": 1, - "x": 11 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.yml b/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.yml deleted file mode 100644 index 9235526be..000000000 --- a/src/test/spec/json/retryable-writes/legacy/findOneAndUpdate.yml +++ /dev/null @@ -1,62 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "FindOneAndUpdate is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "FindOneAndUpdate is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - returnDocument: "Before" - outcome: - result: { _id: 1, x: 11 } - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "FindOneAndUpdate is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "findOneAndUpdate" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.json deleted file mode 100644 index 65fd377fa..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertMany succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.yml deleted file mode 100644 index 9f5e16362..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany-errorLabels.yml +++ /dev/null @@ -1,54 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - -tests: - - description: "InsertMany succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: # Driver retries operation and it succeeds - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.json deleted file mode 100644 index fe8dbf4a6..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertMany succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.yml deleted file mode 100644 index 0dc7518c6..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany-serverErrors.yml +++ /dev/null @@ -1,84 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - -tests: - - - description: "InsertMany succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["insert"] - closeConnection: true - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany.json b/src/test/spec/json/retryable-writes/legacy/insertMany.json deleted file mode 100644 index 0ad326e2d..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany.json +++ /dev/null @@ -1,163 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertMany succeeds after one network error", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany with unordered execution", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany fails after multiple network errors", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": "alwaysOn", - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertMany.yml b/src/test/spec/json/retryable-writes/legacy/insertMany.yml deleted file mode 100644 index eed450e0a..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertMany.yml +++ /dev/null @@ -1,74 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - -tests: - - - description: "InsertMany succeeds after one network error" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: true } - outcome: - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany with unordered execution" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - options: { ordered: false } - outcome: - result: - insertedIds: { 0: 2, 1: 3 } - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertMany fails after multiple network errors" - failPoint: - # Normally, a mongod will insert the documents as a batch with a - # single commit. If this fails, mongod may try to insert each - # document one at a time depending on the failure. Therefore our - # single insert command may trigger the failpoint twice on each - # driver attempt. This test permanently enables the fail point to - # ensure the retry attempt always fails. - configureFailPoint: onPrimaryTransactionalWrite - mode: "alwaysOn" - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "insertMany" - arguments: - documents: - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - { _id: 4, x: 44 } - options: { ordered: true } - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.json deleted file mode 100644 index d90ac5dfb..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [], - "tests": [ - { - "description": "InsertOne succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "x": 11 - } - } - }, - "outcome": { - "result": { - "insertedId": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "InsertOne fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "x": 11 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.yml deleted file mode 100644 index 87100aa5c..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne-errorLabels.yml +++ /dev/null @@ -1,44 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: [] - -tests: - - description: "InsertOne succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "insertOne" - arguments: - document: { _id: 1, x: 11 } - outcome: # Driver retries operation and it succeeds - result: - insertedId: 1 - collection: - data: - - { _id: 1, x: 11 } - - - description: "InsertOne fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "insertOne" - arguments: - document: { _id: 1, x: 11 } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: [] diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.json deleted file mode 100644 index 5179a6ab7..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.json +++ /dev/null @@ -1,1162 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "InsertOne succeeds after connection failure", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails after connection failure when retryWrites option is false", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 10107, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 13436, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 13435, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11602, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11600, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 7, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 6, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 9001, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 89, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after ExceededTimeLimit", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 262, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails after Interrupted", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601, - "closeConnection": false - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after WriteConcernError InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11600, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after WriteConcernError InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11602, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after WriteConcernError PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 189, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails after multiple retryable writeConcernErrors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails after WriteConcernError Interrupted", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "writeConcernError": { - "code": 11601, - "errmsg": "operation was interrupted" - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails after WriteConcernError WriteConcernFailed", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "writeConcernError": { - "code": 64, - "codeName": "WriteConcernFailed", - "errmsg": "waiting for replication timed out", - "errInfo": { - "wtimeout": true - } - } - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.yml deleted file mode 100644 index bfbd18774..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne-serverErrors.yml +++ /dev/null @@ -1,527 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "InsertOne succeeds after connection failure" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne fails after connection failure when retryWrites option is false" - clientOptions: - retryWrites: false - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - # If retryWrites is false, the driver should not add the - # RetryableWriteError label to the error. - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - - description: "InsertOne succeeds after NotWritablePrimary" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 10107 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after NotPrimaryOrSecondary" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 13436 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after NotPrimaryNoSecondaryOk" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 13435 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after InterruptedDueToReplStateChange" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 11602 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after InterruptedAtShutdown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 11600 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 91 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after HostNotFound" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 7 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after HostUnreachable" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 6 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after SocketException" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 9001 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after NetworkTimeout" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 89 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after ExceededTimeLimit" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 262 - errorLabels: ["RetryableWriteError"] - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne fails after Interrupted" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 11601 - closeConnection: false - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - - description: "InsertOne succeeds after WriteConcernError InterruptedAtShutdown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11600 - errmsg: Replication is being shut down - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after WriteConcernError InterruptedDueToReplStateChange" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11602 - errmsg: Replication is being shut down - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after WriteConcernError PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 189 - errmsg: Replication is being shut down - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne fails after multiple retryable writeConcernErrors" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["insert"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } # The write was still applied. - - - description: "InsertOne fails after WriteConcernError Interrupted" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - writeConcernError: - code: 11601 - errmsg: operation was interrupted - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } # The write was still applied. - - - description: "InsertOne fails after WriteConcernError WriteConcernFailed" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - writeConcernError: - code: 64 - codeName: WriteConcernFailed - errmsg: waiting for replication timed out - errInfo: {wtimeout: True} - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } # The write was still applied. - - - - description: "InsertOne fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["insert"] - closeConnection: true - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne.json b/src/test/spec/json/retryable-writes/legacy/insertOne.json deleted file mode 100644 index 04dee6dd6..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne.json +++ /dev/null @@ -1,139 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "InsertOne is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "result": { - "insertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/insertOne.yml b/src/test/spec/json/retryable-writes/legacy/insertOne.yml deleted file mode 100644 index ebfdf23e6..000000000 --- a/src/test/spec/json/retryable-writes/legacy/insertOne.yml +++ /dev/null @@ -1,61 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "InsertOne is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - result: - insertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 33 } - - - description: "InsertOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "insertOne" - arguments: - document: { _id: 3, x: 33 } - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.json deleted file mode 100644 index 6029b875d..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "ReplaceOne succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "ReplaceOne fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.yml deleted file mode 100644 index 419390929..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne-errorLabels.yml +++ /dev/null @@ -1,53 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "ReplaceOne succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: # Driver retries operation and it succeeds - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "ReplaceOne fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.json deleted file mode 100644 index 6b35722e1..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.json +++ /dev/null @@ -1,177 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "ReplaceOne succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "ReplaceOne succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "ReplaceOne fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.yml deleted file mode 100644 index 3dd79db75..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne-serverErrors.yml +++ /dev/null @@ -1,82 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "ReplaceOne succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "ReplaceOne succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "ReplaceOne fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["update"] - closeConnection: true - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne.json b/src/test/spec/json/retryable-writes/legacy/replaceOne.json deleted file mode 100644 index e5b8cf8ea..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "ReplaceOne is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "ReplaceOne is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "ReplaceOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/replaceOne.yml b/src/test/spec/json/retryable-writes/legacy/replaceOne.yml deleted file mode 100644 index 0000904a4..000000000 --- a/src/test/spec/json/retryable-writes/legacy/replaceOne.yml +++ /dev/null @@ -1,66 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "ReplaceOne is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "ReplaceOne is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 111 } - - { _id: 2, x: 22 } - - - description: "ReplaceOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "replaceOne" - arguments: - filter: { _id: 1 } - replacement: { _id: 1, x: 111 } - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/updateMany.json b/src/test/spec/json/retryable-writes/legacy/updateMany.json deleted file mode 100644 index 46fef73e7..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateMany.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "UpdateMany ignores retryWrites", - "useMultipleMongoses": true, - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 23 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/updateMany.yml b/src/test/spec/json/retryable-writes/legacy/updateMany.yml deleted file mode 100644 index f3ab39faa..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateMany.yml +++ /dev/null @@ -1,27 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "UpdateMany ignores retryWrites" - useMultipleMongoses: true - operation: - name: "updateMany" - arguments: - filter: { } - update: { $inc: { x : 1 }} - outcome: - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 23 } diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.json b/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.json deleted file mode 100644 index 5bd00cde9..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "UpdateOne succeeds with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne fails if server does not return RetryableWriteError", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.yml b/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.yml deleted file mode 100644 index 6bfef3b12..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne-errorLabels.yml +++ /dev/null @@ -1,53 +0,0 @@ -runOn: - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - description: "UpdateOne succeeds with RetryableWriteError from server" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - outcome: # Driver retries operation and it succeeds - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "UpdateOne fails if server does not return RetryableWriteError" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - outcome: - error: true # Driver does not retry operation because there was no RetryableWriteError label on response - result: - errorLabelsOmit: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.json b/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.json deleted file mode 100644 index cf274f57e..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topology": [ - "sharded", - "load-balanced" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "UpdateOne succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne fails with a RetryableWriteError label after two connection failures", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "error": true, - "result": { - "errorLabelsContain": [ - "RetryableWriteError" - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.yml b/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.yml deleted file mode 100644 index fffe851ca..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne-serverErrors.yml +++ /dev/null @@ -1,82 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.7" - topology: ["sharded", "load-balanced"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "UpdateOne succeeds after PrimarySteppedDown" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "UpdateOne succeeds after WriteConcernError ShutdownInProgress" - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["update"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "UpdateOne fails with a RetryableWriteError label after two connection failures" - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["update"] - closeConnection: true - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x: 1 } } - outcome: - error: true - result: - errorLabelsContain: ["RetryableWriteError"] - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne.json b/src/test/spec/json/retryable-writes/legacy/updateOne.json deleted file mode 100644 index 0f806dc3d..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne.json +++ /dev/null @@ -1,288 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "3.6", - "topology": [ - "replicaset" - ] - } - ], - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "tests": [ - { - "description": "UpdateOne is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - }, - { - "description": "UpdateOne with upsert is committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3, - "x": 33 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 34 - } - ] - } - } - }, - { - "description": "UpdateOne with upsert is not committed on first attempt", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 1 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3, - "x": 33 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 3 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 34 - } - ] - } - } - }, - { - "description": "UpdateOne with upsert is never committed", - "failPoint": { - "configureFailPoint": "onPrimaryTransactionalWrite", - "mode": { - "times": 2 - }, - "data": { - "failBeforeCommitExceptionCode": 1 - } - }, - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3, - "x": 33 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "error": true, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/retryable-writes/legacy/updateOne.yml b/src/test/spec/json/retryable-writes/legacy/updateOne.yml deleted file mode 100644 index 56b7d822b..000000000 --- a/src/test/spec/json/retryable-writes/legacy/updateOne.yml +++ /dev/null @@ -1,129 +0,0 @@ -runOn: - - - minServerVersion: "3.6" - topology: ["replicaset"] - -data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - -tests: - - - description: "UpdateOne is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "UpdateOne is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - collection: - data: - - { _id: 1, x: 12 } - - { _id: 2, x: 22 } - - - description: "UpdateOne is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 1 } - update: { $inc: { x : 1 }} - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - - description: "UpdateOne with upsert is committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 3, x: 33 } - update: { $inc: { x : 1 }} - upsert: true - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 34 } - - - description: "UpdateOne with upsert is not committed on first attempt" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 1 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 3, x: 33 } - update: { $inc: { x : 1 }} - upsert: true - outcome: - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 3 - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } - - { _id: 3, x: 34 } - - - description: "UpdateOne with upsert is never committed" - failPoint: - configureFailPoint: onPrimaryTransactionalWrite - mode: { times: 2 } - data: { failBeforeCommitExceptionCode: 1 } - operation: - name: "updateOne" - arguments: - filter: { _id: 3, x: 33 } - update: { $inc: { x : 1 }} - upsert: true - outcome: - error: true - collection: - data: - - { _id: 1, x: 11 } - - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.json b/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.json new file mode 100644 index 000000000..fd25c345a --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.json @@ -0,0 +1,149 @@ +{ + "description": "aggregate with $out/$merge does not set txnNumber", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "mergeCollection", + "databaseName": "retryable-writes-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "aggregate with $out does not set txnNumber", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "outCollection" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "aggregate with $merge does not set txnNumber", + "runOnRequirements": [ + { + "minServerVersion": "4.1.11" + } + ], + "operations": [ + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "mergeCollection" + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "aggregate", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.yml b/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.yml new file mode 100644 index 000000000..5114b3163 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/aggregate-out-merge.yml @@ -0,0 +1,67 @@ +description: "aggregate with $out/$merge does not set txnNumber" + +schemaVersion: "1.4" + +runOnRequirements: + - minServerVersion: "3.6" + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + # The output collection must already exist for $merge on a sharded cluster + - collectionName: &mergeCollection mergeCollection + databaseName: *database0Name + documents: [] + +tests: + - description: "aggregate with $out does not set txnNumber" + runOnRequirements: + - serverless: forbid # $out is not supported on serverless + operations: + - object: *collection0 + name: aggregate + arguments: + pipeline: + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $out: outCollection } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: aggregate + command: + txnNumber: { $$exists: false } + - description: "aggregate with $merge does not set txnNumber" + runOnRequirements: + - minServerVersion: "4.1.11" + operations: + - object: *collection0 + name: aggregate + arguments: + pipeline: + - { $sort: { x: 1 } } + - { $match: { _id: { $gt: 1 } } } + - { $merge: { into: *mergeCollection } } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: aggregate + command: + txnNumber: { $$exists: false } diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.json b/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.json new file mode 100644 index 000000000..13ba9bae7 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.json @@ -0,0 +1,416 @@ +{ + "description": "bulkWrite-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "BulkWrite succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 3 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 3 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "BulkWrite succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 3 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.yml new file mode 100644 index 000000000..9adec6de7 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite-errorLabels.yml @@ -0,0 +1,222 @@ +description: bulkWrite-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'BulkWrite succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteOne: + filter: { _id: 1 } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + # Driver retries operation and it succeeds + expectResult: + deletedCount: 1 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 3 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteOne: + filter: { _id: 1 } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteOne: + filter: { _id: 1 } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 3 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } + - + description: 'BulkWrite succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteOne: + filter: { _id: 1 } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 3 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.json b/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.json index aed210ec2..0a063ab4d 100644 --- a/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.json +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.json @@ -1,12 +1,19 @@ { "description": "retryable-writes bulkWrite serverErrors", - "schemaVersion": "1.0", + "schemaVersion": "1.3", "runOnRequirements": [ { - "minServerVersion": "3.6", + "minServerVersion": "4.0", "topologies": [ "replicaset" ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] } ], "createEntities": [ @@ -55,16 +62,7 @@ "description": "BulkWrite succeeds after retryable writeConcernError in first batch", "runOnRequirements": [ { - "minServerVersion": "4.0", - "topologies": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topologies": [ - "sharded" - ] + "minServerVersion": "4.3.1" } ], "operations": [ @@ -200,6 +198,88 @@ ] } ] + }, + { + "description": "BulkWrite fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] } ] } diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.yml index dc664ab76..a88a20612 100644 --- a/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.yml +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite-serverErrors.yml @@ -1,10 +1,12 @@ description: "retryable-writes bulkWrite serverErrors" -schemaVersion: "1.0" +schemaVersion: "1.3" runOnRequirements: - - minServerVersion: "3.6" + - minServerVersion: "4.0" topologies: [ replicaset ] + - minServerVersion: "4.1.7" + topologies: [ sharded, load-balanced ] createEntities: - client: @@ -30,10 +32,7 @@ initialData: tests: - description: "BulkWrite succeeds after retryable writeConcernError in first batch" runOnRequirements: - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.1.7" - topologies: [ sharded ] + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -94,3 +93,44 @@ tests: documents: - { _id: 1, x: 11 } - { _id: 3, x: 33 } # The write was still applied + - + description: 'BulkWrite fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ update ] + closeConnection: true + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteOne: + filter: { _id: 1 } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite.json b/src/test/spec/json/retryable-writes/unified/bulkWrite.json new file mode 100644 index 000000000..f2bd9e0eb --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite.json @@ -0,0 +1,1083 @@ +{ + "description": "bulkWrite", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "First command is retried", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 23 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "All commands are retried", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 7 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 4, + "x": 44 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + } + }, + { + "insertOne": { + "document": { + "_id": 5, + "x": 55 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 3 + }, + "replacement": { + "_id": 3, + "x": 333 + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 3, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "2": 3, + "4": 5 + } + }, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 1, + "upsertedIds": { + "3": 4 + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 333 + }, + { + "_id": 4, + "x": 45 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ] + }, + { + "description": "Both commands are retried after their first statement fails", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2 + } + }, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 23 + } + ] + } + ] + }, + { + "description": "Second command is retried after its second statement fails", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "skip": 2 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2 + } + }, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 23 + } + ] + } + ] + }, + { + "description": "BulkWrite with unordered execution", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "insertOne": { + "document": { + "_id": 3, + "x": 33 + } + } + } + ], + "ordered": false + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 2, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "First insertOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": { + "$$unsetOrMatches": {} + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "Second updateOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "skip": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 2 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "Third updateOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "skip": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 2 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "Single-document write following deleteMany is retried", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteMany": { + "filter": { + "x": 11 + } + } + }, + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 1, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 2 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "Single-document write following updateMany is retried", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateMany": { + "filter": { + "x": 11 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "insertOne": { + "document": { + "_id": 2, + "x": 22 + } + } + } + ], + "ordered": true + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "1": 2 + } + }, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "collection bulkWrite with updateMany does not set txnNumber", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "updateMany": { + "filter": {}, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "collection bulkWrite with deleteMany does not set txnNumber", + "operations": [ + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "deleteMany": { + "filter": {} + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/bulkWrite.yml b/src/test/spec/json/retryable-writes/unified/bulkWrite.yml new file mode 100644 index 000000000..c92fd4b74 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/bulkWrite.yml @@ -0,0 +1,559 @@ +description: bulkWrite + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'First command is retried' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + deleteOne: + filter: { _id: 1 } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '0': 2 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: delete + command: + txnNumber: { $$exists: true } + - + # Write operations in this ordered batch are intentionally sequenced so that + # each write command consists of a single statement, which will fail on the + # first attempt and succeed on the second, retry attempt. + description: 'All commands are retried' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 7 } + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + insertOne: + document: { _id: 3, x: 33 } + - + updateOne: + filter: { _id: 4, x: 44 } + update: { $inc: { x: 1 } } + upsert: true + - + insertOne: + document: { _id: 5, x: 55 } + - + replaceOne: + filter: { _id: 3 } + replacement: { _id: 3, x: 333 } + - + deleteOne: + filter: { _id: 1 } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 3 + insertedIds: + $$unsetOrMatches: + '0': 2 + '2': 3 + '4': 5 + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 1 + upsertedIds: { '3': 4 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 23 } + - { _id: 3, x: 333 } + - { _id: 4, x: 45 } + - { _id: 5, x: 55 } + - + description: 'Both commands are retried after their first statement fails' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '0': 2 } } + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 23 } + - + description: 'Second command is retried after its second statement fails' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { skip: 2 } + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '0': 2 } } + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 23 } + - + description: 'BulkWrite with unordered execution' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + insertOne: + document: { _id: 3, x: 33 } + ordered: false + expectResult: + deletedCount: 0 + insertedCount: 2 + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - + description: 'First insertOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + deleteOne: + filter: { _id: 1 } + ordered: true + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 0 + insertedIds: + $$unsetOrMatches: { } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'Second updateOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { skip: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + - + deleteOne: + filter: { _id: 1 } + ordered: true + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '0': 2 } } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'Third updateOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { skip: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + updateOne: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - + insertOne: + document: { _id: 2, x: 22 } + - + updateOne: + filter: { _id: 2 } + update: { $inc: { x: 1 } } + ordered: true + expectError: + isError: true + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 2 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + # The onPrimaryTransactionalWrite fail point only triggers for write + # operations that include a transaction ID. Therefore, it will not affect + # the initial deleteMany and will trigger once (and only once) for the first + # insertOne attempt. + description: 'Single-document write following deleteMany is retried' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteMany: + filter: { x: 11 } + - + insertOne: + document: { _id: 2, x: 22 } + ordered: true + expectResult: + deletedCount: 1 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 2 } } + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + # The onPrimaryTransactionalWrite fail point only triggers for write + # operations that include a transaction ID. Therefore, it will not affect + # the initial updateMany and will trigger once (and only once) for the first + # insertOne attempt. + description: 'Single-document write following updateMany is retried' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: bulkWrite + arguments: + requests: + - + updateMany: + filter: { x: 11 } + update: { $inc: { x: 1 } } + - + insertOne: + document: { _id: 2, x: 22 } + ordered: true + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: { $$unsetOrMatches: { '1': 2 } } + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + upsertedIds: { } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - description: "collection bulkWrite with updateMany does not set txnNumber" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - + updateMany: + filter: {} + update: { $set: { x: 1 } } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: false } + - description: "collection bulkWrite with deleteMany does not set txnNumber" + operations: + - object: *collection0 + name: bulkWrite + arguments: + requests: + - + deleteMany: + filter: {} + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: delete + command: + txnNumber: { $$exists: false } \ No newline at end of file diff --git a/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.json b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.json new file mode 100644 index 000000000..d16e0c9c8 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.json @@ -0,0 +1,351 @@ +{ + "description": "client bulkWrite retryable writes with client errors", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "retryable-writes-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with one network error succeeds after retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "client bulkWrite with two network errors fails after retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "verboseResults": true + }, + "expectError": { + "isClientError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.yml b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.yml new file mode 100644 index 000000000..e5214b90f --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-clientErrors.yml @@ -0,0 +1,173 @@ +description: "client bulkWrite retryable writes with client errors" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "retryable-writes-tests.coll0" + +tests: + - description: "client bulkWrite with one network error succeeds after retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + closeConnection: true + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 4 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - description: "client bulkWrite with two network errors fails after retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 2 + data: + failCommands: [ bulkWrite ] + closeConnection: true + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + verboseResults: true + expectError: + isClientError: true + errorLabelsContain: ["RetryableWriteError"] # Error label added by driver. + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + # An implicit session is included with the transaction number: + lsid: { "$$exists": true } + txnNumber: { "$$exists": true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.json b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.json new file mode 100644 index 000000000..a1f7c8152 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.json @@ -0,0 +1,882 @@ +{ + "description": "client bulkWrite retryable writes", + "schemaVersion": "1.21", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "client": { + "id": "clientRetryWritesFalse", + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ], + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "_yamlAnchors": { + "namespace": "retryable-writes-tests.coll0" + }, + "tests": [ + { + "description": "client bulkWrite with no multi: true operations succeeds after retryable top-level error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "replaceOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + } + }, + { + "deleteOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 1, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 222 + }, + { + "_id": 4, + "x": 44 + } + ] + } + ] + }, + { + "description": "client bulkWrite with multi: true operations fails after retryable top-level error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ] + }, + "expectError": { + "errorCode": 189, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": true + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "updateOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "replaceOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 2 + }, + "replacement": { + "x": 222 + } + } + }, + { + "deleteOne": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 2, + "modifiedCount": 2, + "deletedCount": 1, + "insertResults": { + "0": { + "insertedId": 4 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + } + }, + "deleteResults": { + "3": { + "deletedCount": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "_id": 2 + }, + "updateMods": { + "x": 222 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": false + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "lsid": { + "$$exists": true + }, + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with multi: true operations fails after retryable writeConcernError", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "updateMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "deleteMany": { + "namespace": "retryable-writes-tests.coll0", + "filter": { + "_id": 3 + } + } + } + ] + }, + "expectError": { + "writeConcernErrors": [ + { + "code": 91, + "message": "Replication is being shut down" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": true + }, + { + "delete": 0, + "filter": { + "_id": 3 + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "client bulkWrite with retryWrites: false does not retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "clientRetryWritesFalse", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "clientRetryWritesFalse", + "name": "clientBulkWrite", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-tests.coll0", + "document": { + "_id": 4, + "x": 44 + } + } + } + ] + }, + "expectError": { + "errorCode": 189, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "clientRetryWritesFalse", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 4, + "x": 44 + } + } + ], + "nsInfo": [ + { + "ns": "retryable-writes-tests.coll0" + } + ], + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.yml new file mode 100644 index 000000000..951068231 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/client-bulkWrite-serverErrors.yml @@ -0,0 +1,416 @@ +description: "client bulkWrite retryable writes" +schemaVersion: "1.21" +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - client: + id: &clientRetryWritesFalse clientRetryWritesFalse + uriOptions: + retryWrites: false + observeEvents: [ commandStartedEvent ] + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + +_yamlAnchors: + namespace: &namespace "retryable-writes-tests.coll0" + +tests: + - description: "client bulkWrite with no multi: true operations succeeds after retryable top-level error" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - replaceOne: + namespace: *namespace + filter: { _id: 2 } + replacement: { x: 222 } + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 1 + insertResults: + 0: + insertedId: 4 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 222 } + - { _id: 4, x: 44 } + - description: "client bulkWrite with multi: true operations fails after retryable top-level error" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - deleteMany: + namespace: *namespace + filter: { _id: 3 } + expectError: + errorCode: 189 + errorLabelsContain: [ RetryableWriteError ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: true + - delete: 0 + filter: { _id: 3 } + multi: true + nsInfo: + - ns: *namespace + txnNumber: { $$exists: false } + - description: "client bulkWrite with no multi: true operations succeeds after retryable writeConcernError" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorLabels: [ RetryableWriteError ] + writeConcernError: + code: 91 + errmsg: "Replication is being shut down" + - object: *client0 + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - replaceOne: + namespace: *namespace + filter: { _id: 2 } + replacement: { x: 222 } + - deleteOne: + namespace: *namespace + filter: { _id: 3 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 2 + modifiedCount: 2 + deletedCount: 1 + insertResults: + 0: + insertedId: 4 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + deleteResults: + 3: + deletedCount: 1 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: false + - update: 0 + filter: { _id: 2 } + updateMods: { x: 222 } + multi: false + - delete: 0 + filter: { _id: 3 } + multi: false + nsInfo: + - ns: *namespace + lsid: { $$exists: true } + txnNumber: { $$exists: true } + - description: "client bulkWrite with multi: true operations fails after retryable writeConcernError" + operations: + - object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorLabels: [ RetryableWriteError ] + writeConcernError: + code: 91 + errmsg: "Replication is being shut down" + - object: *client0 + name: clientBulkWrite + arguments: + models: + - updateMany: + namespace: *namespace + filter: { _id: 1 } + update: + $inc: { x: 1 } + - deleteMany: + namespace: *namespace + filter: { _id: 3 } + expectError: + writeConcernErrors: + - code: 91 + message: "Replication is being shut down" + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - update: 0 + filter: { _id: 1 } + updateMods: + $inc: { x: 1 } + multi: true + - delete: 0 + filter: { _id: 3 } + multi: true + nsInfo: + - ns: *namespace + txnNumber: { $$exists: false } + - description: "client bulkWrite with retryWrites: false does not retry" + operations: + - object: testRunner + name: failPoint + arguments: + client: *clientRetryWritesFalse + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: [ bulkWrite ] + errorCode: 189 # PrimarySteppedDown + errorLabels: [ RetryableWriteError ] + - object: *clientRetryWritesFalse + name: clientBulkWrite + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 4, x: 44 } + expectError: + errorCode: 189 + errorLabelsContain: [ RetryableWriteError ] + expectEvents: + - client: *clientRetryWritesFalse + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 4, x: 44 } + nsInfo: + - ns: *namespace + txnNumber: { $$exists: false } diff --git a/src/test/spec/json/retryable-writes/unified/deleteMany.json b/src/test/spec/json/retryable-writes/unified/deleteMany.json new file mode 100644 index 000000000..381f37795 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteMany.json @@ -0,0 +1,96 @@ +{ + "description": "deleteMany", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "DeleteMany ignores retryWrites", + "operations": [ + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "filter": {} + }, + "expectResult": { + "deletedCount": 2 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/deleteMany.yml b/src/test/spec/json/retryable-writes/unified/deleteMany.yml new file mode 100644 index 000000000..a05fe8076 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteMany.yml @@ -0,0 +1,57 @@ +description: deleteMany + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'DeleteMany ignores retryWrites' + operations: + - + object: *collection0 + name: deleteMany + arguments: + filter: { } + expectResult: + deletedCount: 2 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: delete + command: + txnNumber: { $$exists: false } diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.json b/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.json new file mode 100644 index 000000000..88920862e --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.json @@ -0,0 +1,266 @@ +{ + "description": "deleteOne-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "DeleteOne succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "DeleteOne fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "DeleteOne succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "DeleteOne succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.yml new file mode 100644 index 000000000..08e700a9b --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne-errorLabels.yml @@ -0,0 +1,157 @@ +description: deleteOne-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'DeleteOne succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + # Driver retries operation and it succeeds + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'DeleteOne fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'DeleteOne succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'DeleteOne succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ delete ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.json b/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.json new file mode 100644 index 000000000..0808b7921 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.json @@ -0,0 +1,114 @@ +{ + "description": "deleteOne-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "DeleteOne fails with RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.yml new file mode 100644 index 000000000..2b63c43e3 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne-serverErrors.yml @@ -0,0 +1,67 @@ +description: deleteOne-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'DeleteOne fails with RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ delete ] + closeConnection: true + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne.json b/src/test/spec/json/retryable-writes/unified/deleteOne.json new file mode 100644 index 000000000..9e37ff8bc --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne.json @@ -0,0 +1,218 @@ +{ + "description": "deleteOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "DeleteOne is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "delete", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "DeleteOne is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "DeleteOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/deleteOne.yml b/src/test/spec/json/retryable-writes/unified/deleteOne.yml new file mode 100644 index 000000000..5a176f829 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/deleteOne.yml @@ -0,0 +1,123 @@ +description: deleteOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'DeleteOne is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: delete + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: delete + command: + txnNumber: { $$exists: true } + - + description: 'DeleteOne is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectResult: + deletedCount: 1 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'DeleteOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: deleteOne + arguments: + filter: { _id: 1 } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.json b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.json new file mode 100644 index 000000000..8639873fc --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.json @@ -0,0 +1,289 @@ +{ + "description": "findOneAndDelete-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndDelete succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.yml new file mode 100644 index 000000000..cd712f2eb --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-errorLabels.yml @@ -0,0 +1,158 @@ +description: findOneAndDelete-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndDelete succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + # Driver retries operation and it succeeds + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'FindOneAndDelete fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndDelete succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'FindOneAndDelete succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.json b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.json new file mode 100644 index 000000000..f6d8e9d69 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.json @@ -0,0 +1,119 @@ +{ + "description": "findOneAndDelete-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndDelete fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.yml new file mode 100644 index 000000000..a9da496c0 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete-serverErrors.yml @@ -0,0 +1,68 @@ +description: findOneAndDelete-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndDelete fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ findAndModify ] + closeConnection: true + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete.json b/src/test/spec/json/retryable-writes/unified/findOneAndDelete.json new file mode 100644 index 000000000..ebfb8ce66 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete.json @@ -0,0 +1,235 @@ +{ + "description": "findOneAndDelete", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndDelete is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndDelete is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndDelete is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "x": { + "$gte": 11 + } + }, + "sort": { + "x": 1 + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndDelete.yml b/src/test/spec/json/retryable-writes/unified/findOneAndDelete.yml new file mode 100644 index 000000000..378873321 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndDelete.yml @@ -0,0 +1,124 @@ +description: findOneAndDelete + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndDelete is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - + description: 'FindOneAndDelete is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2, x: 22 } + - + description: 'FindOneAndDelete is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndDelete + arguments: + filter: { x: { $gte: 11 } } + sort: { x: 1 } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.json b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.json new file mode 100644 index 000000000..78db52e75 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.json @@ -0,0 +1,301 @@ +{ + "description": "findOneAndReplace-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.yml new file mode 100644 index 000000000..254b61980 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-errorLabels.yml @@ -0,0 +1,165 @@ +description: findOneAndReplace-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndReplace succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + # Driver retries operation and it succeeds + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndReplace fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndReplace succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndReplace succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.json b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.json new file mode 100644 index 000000000..1c355c3eb --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.json @@ -0,0 +1,119 @@ +{ + "description": "findOneAndReplace-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.yml new file mode 100644 index 000000000..090356bad --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace-serverErrors.yml @@ -0,0 +1,69 @@ +description: findOneAndReplace-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndReplace fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ findAndModify ] + closeConnection: true + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace.json b/src/test/spec/json/retryable-writes/unified/findOneAndReplace.json new file mode 100644 index 000000000..638d15a41 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace.json @@ -0,0 +1,243 @@ +{ + "description": "findOneAndReplace", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndReplace is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndReplace is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndReplace is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + }, + "returnDocument": "Before" + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndReplace.yml b/src/test/spec/json/retryable-writes/unified/findOneAndReplace.yml new file mode 100644 index 000000000..cc2468264 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndReplace.yml @@ -0,0 +1,129 @@ +description: findOneAndReplace + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndReplace is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - + description: 'FindOneAndReplace is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndReplace is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndReplace + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + returnDocument: Before + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.json b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.json new file mode 100644 index 000000000..38b3f7ba4 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.json @@ -0,0 +1,305 @@ +{ + "description": "findOneAndUpdate-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.yml new file mode 100644 index 000000000..034edbe77 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-errorLabels.yml @@ -0,0 +1,165 @@ +description: findOneAndUpdate-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndUpdate succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + # Driver retries operation and it succeeds + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndUpdate fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndUpdate succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndUpdate succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ findAndModify ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.json b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.json new file mode 100644 index 000000000..150012ac7 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.json @@ -0,0 +1,120 @@ +{ + "description": "findOneAndUpdate-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.yml new file mode 100644 index 000000000..8f9765fc1 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate-serverErrors.yml @@ -0,0 +1,69 @@ +description: findOneAndUpdate-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndUpdate fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ findAndModify ] + closeConnection: true + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.json b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.json new file mode 100644 index 000000000..eefe98ae1 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.json @@ -0,0 +1,245 @@ +{ + "description": "findOneAndUpdate", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "FindOneAndUpdate is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "findAndModify", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 1, + "x": 11 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "FindOneAndUpdate is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.yml b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.yml new file mode 100644 index 000000000..51c77f108 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/findOneAndUpdate.yml @@ -0,0 +1,128 @@ +description: findOneAndUpdate + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'FindOneAndUpdate is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: findAndModify + command: + txnNumber: { $$exists: true } + - + description: 'FindOneAndUpdate is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: { _id: 1, x: 11 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'FindOneAndUpdate is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: findOneAndUpdate + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/handshakeError.json b/src/test/spec/json/retryable-writes/unified/handshakeError.json index df37bd723..93cb2e849 100644 --- a/src/test/spec/json/retryable-writes/unified/handshakeError.json +++ b/src/test/spec/json/retryable-writes/unified/handshakeError.json @@ -1,6 +1,6 @@ { "description": "retryable writes handshake failures", - "schemaVersion": "1.3", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.2", @@ -53,6 +53,224 @@ } ], "tests": [ + { + "description": "client.clientBulkWrite succeeds after retryable handshake network error", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-handshake-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "ping", + "saslContinue" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "retryable-writes-handshake-tests.coll", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + }, + { + "connectionCheckOutStartedEvent": {} + } + ] + }, + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1 + }, + "databaseName": "retryable-writes-handshake-tests" + } + }, + { + "commandFailedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "commandName": "bulkWrite" + } + }, + { + "commandSucceededEvent": { + "commandName": "bulkWrite" + } + } + ] + } + ] + }, { "description": "collection.insertOne succeeds after retryable handshake network error", "operations": [ diff --git a/src/test/spec/json/retryable-writes/unified/handshakeError.yml b/src/test/spec/json/retryable-writes/unified/handshakeError.yml index 9b2774bc7..174346337 100644 --- a/src/test/spec/json/retryable-writes/unified/handshakeError.yml +++ b/src/test/spec/json/retryable-writes/unified/handshakeError.yml @@ -2,7 +2,7 @@ description: "retryable writes handshake failures" -schemaVersion: "1.3" +schemaVersion: "1.4" # For `serverless: forbid` runOnRequirements: - minServerVersion: "4.2" @@ -50,6 +50,98 @@ tests: # - Triggers failpoint (second time). # - Tests whether operation successfully retries the handshake and succeeds. + - description: "client.clientBulkWrite succeeds after retryable handshake network error" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: retryable-writes-handshake-tests.coll + document: { _id: 8, x: 88 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: bulkWrite + - commandSucceededEvent: + commandName: bulkWrite + + - description: "client.clientBulkWrite succeeds after retryable handshake server error (ShutdownInProgress)" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ping, saslContinue] + closeConnection: true + - name: runCommand + object: *database + arguments: { commandName: ping, command: { ping: 1 } } + expectError: { isError: true } + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: retryable-writes-handshake-tests.coll + document: { _id: 8, x: 88 } + expectEvents: + - client: *client + eventType: cmap + events: + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - { connectionCheckOutStartedEvent: {} } + - client: *client + events: + - commandStartedEvent: + command: { ping: 1 } + databaseName: *databaseName + - commandFailedEvent: + commandName: ping + - commandStartedEvent: + commandName: bulkWrite + - commandSucceededEvent: + commandName: bulkWrite + - description: "collection.insertOne succeeds after retryable handshake network error" operations: - name: failPoint diff --git a/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.json b/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.json new file mode 100644 index 000000000..5254ba7cb --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.json @@ -0,0 +1,335 @@ +{ + "description": "insertMany-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "InsertMany succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertMany fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertMany succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertMany succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.yml new file mode 100644 index 000000000..631b3a576 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany-errorLabels.yml @@ -0,0 +1,185 @@ +description: insertMany-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'InsertMany succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + # Driver retries operation and it succeeds + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertMany fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertMany succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertMany succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } diff --git a/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.json b/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.json new file mode 100644 index 000000000..f5f513603 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.json @@ -0,0 +1,114 @@ +{ + "description": "insertMany-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "InsertMany fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.yml new file mode 100644 index 000000000..f229428bd --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany-serverErrors.yml @@ -0,0 +1,68 @@ +description: insertMany-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'InsertMany fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ insert ] + closeConnection: true + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } diff --git a/src/test/spec/json/retryable-writes/unified/insertMany.json b/src/test/spec/json/retryable-writes/unified/insertMany.json new file mode 100644 index 000000000..35a18c46c --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany.json @@ -0,0 +1,290 @@ +{ + "description": "insertMany", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ], + "tests": [ + { + "description": "InsertMany succeeds after one network error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": true + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "InsertMany with unordered execution", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "ordered": false + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "InsertMany fails after multiple network errors", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": "alwaysOn", + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "ordered": true + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/insertMany.yml b/src/test/spec/json/retryable-writes/unified/insertMany.yml new file mode 100644 index 000000000..0ead50da7 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertMany.yml @@ -0,0 +1,158 @@ +description: insertMany + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + +tests: + - + description: 'InsertMany succeeds after one network error' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: true + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - + description: 'InsertMany with unordered execution' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + ordered: false + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - + description: 'InsertMany fails after multiple network errors' + operations: + - + # Normally, a mongod will insert the documents as a batch with a single + # commit. If this fails, mongod may try to insert each document one at a + # time depending on the failure. Therefore our single insert command may + # trigger the failpoint twice on each driver attempt. This test + # permanently enables the fail point to ensure the retry attempt always + # fails. + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: alwaysOn + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + ordered: true + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.json b/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.json new file mode 100644 index 000000000..39f31a8aa --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.json @@ -0,0 +1,1127 @@ +{ + "description": "insertOne-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "InsertOne succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [] + } + ] + }, + { + "description": "InsertOne succeeds after NotWritablePrimary", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 10107, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 13436, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 13435, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11602, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after InterruptedAtShutdown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11600, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after HostNotFound", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 7, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after HostUnreachable", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 6, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after SocketException", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 9001, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after NetworkTimeout", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after ExceededTimeLimit", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 262, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after WriteConcernError InterruptedAtShutdown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11600, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after WriteConcernError InterruptedDueToReplStateChange", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11602, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after WriteConcernError PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 189, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + }, + { + "description": "InsertOne fails after multiple retryable writeConcernErrors", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.yml new file mode 100644 index 000000000..ce5242177 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertOne-errorLabels.yml @@ -0,0 +1,610 @@ +description: insertOne-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'InsertOne succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + # Driver retries operation and it succeeds + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'InsertOne succeeds after NotWritablePrimary' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 10107 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after NotPrimaryOrSecondary' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 13436 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after NotPrimaryNoSecondaryOk' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 13435 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after InterruptedDueToReplStateChange' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 11602 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after InterruptedAtShutdown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 11600 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 189 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 91 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after HostNotFound' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 7 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after HostUnreachable' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 6 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after SocketException' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 9001 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after NetworkTimeout' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 89 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after ExceededTimeLimit' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 262 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after WriteConcernError InterruptedAtShutdown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11600 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after WriteConcernError InterruptedDueToReplStateChange' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11602 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after WriteConcernError PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 189 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - + description: 'InsertOne fails after multiple retryable writeConcernErrors' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ insert ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } # The write was still applied. diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-noWritesPerformedError.yml b/src/test/spec/json/retryable-writes/unified/insertOne-noWritesPerformedError.yml index 3295d153d..6d8e8e7d4 100644 --- a/src/test/spec/json/retryable-writes/unified/insertOne-noWritesPerformedError.yml +++ b/src/test/spec/json/retryable-writes/unified/insertOne-noWritesPerformedError.yml @@ -29,11 +29,9 @@ tests: client: *client0 failPoint: configureFailPoint: failCommand - mode: - times: 2 + mode: { times: 2 } data: - failCommands: - - insert + failCommands: [ insert ] errorCode: 64 errorLabels: - NoWritesPerformed @@ -41,8 +39,7 @@ tests: - name: insertOne object: *collection0 arguments: - document: - x: 1 + document: { x: 1 } expectError: errorCode: 64 errorLabelsContain: diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json index a87f45169..8edafb702 100644 --- a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json +++ b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.json @@ -1,12 +1,19 @@ { "description": "retryable-writes insertOne serverErrors", - "schemaVersion": "1.0", + "schemaVersion": "1.9", "runOnRequirements": [ { - "minServerVersion": "3.6", + "minServerVersion": "4.0", "topologies": [ "replicaset" ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] } ], "createEntities": [ @@ -55,16 +62,7 @@ "description": "InsertOne succeeds after retryable writeConcernError", "runOnRequirements": [ { - "minServerVersion": "4.0", - "topologies": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", - "topologies": [ - "sharded" - ] + "minServerVersion": "4.3.1" } ], "operations": [ @@ -168,6 +166,699 @@ ] } ] + }, + { + "description": "RetryableWriteError label is added based on top-level code in pre-4.4 server response", + "runOnRequirements": [ + { + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 189 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3, + "x": 33 + } + ] + }, + "commandName": "insert", + "databaseName": "retryable-writes-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3, + "x": 33 + } + ] + }, + "commandName": "insert", + "databaseName": "retryable-writes-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "RetryableWriteError label is added based on writeConcernError in pre-4.4 mongod response", + "runOnRequirements": [ + { + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "topologies": [ + "replicaset" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3, + "x": 33 + } + ] + }, + "commandName": "insert", + "databaseName": "retryable-writes-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3, + "x": 33 + } + ] + }, + "commandName": "insert", + "databaseName": "retryable-writes-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "RetryableWriteError label is not added based on writeConcernError in pre-4.4 mongos response", + "runOnRequirements": [ + { + "minServerVersion": "4.2", + "maxServerVersion": "4.2.99", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll", + "documents": [ + { + "_id": 3, + "x": 33 + } + ] + }, + "commandName": "insert", + "databaseName": "retryable-writes-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertOne succeeds after connection failure", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertOne fails after connection failure when retryWrites option is false", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + } + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "coll" + } + } + ] + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "InsertOne fails after Interrupted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601, + "closeConnection": false + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "InsertOne fails after WriteConcernError Interrupted", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 11601, + "errmsg": "operation was interrupted" + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertOne fails after WriteConcernError WriteConcernTimeout", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 64, + "errmsg": "waiting for replication timed out", + "errInfo": { + "wtimeout": true + } + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertOne fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] } ] } diff --git a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml index 689328818..6fd43365d 100644 --- a/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml +++ b/src/test/spec/json/retryable-writes/unified/insertOne-serverErrors.yml @@ -1,10 +1,12 @@ description: "retryable-writes insertOne serverErrors" -schemaVersion: "1.0" +schemaVersion: "1.9" runOnRequirements: - - minServerVersion: "3.6" + - minServerVersion: "4.0" topologies: [ replicaset ] + - minServerVersion: "4.1.7" + topologies: [ sharded, load-balanced ] createEntities: - client: @@ -30,10 +32,7 @@ initialData: tests: - description: "InsertOne succeeds after retryable writeConcernError" runOnRequirements: - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.1.7" - topologies: [ sharded ] + - minServerVersion: "4.3.1" # failCommand errorLabels option operations: - name: failPoint object: testRunner @@ -76,3 +75,331 @@ tests: - { _id: 1, x: 11 } - { _id: 2, x: 22 } - { _id: 3, x: 33 } # The write was still applied + + - description: "RetryableWriteError label is added based on top-level code in pre-4.4 server response" + runOnRequirements: + - minServerVersion: "4.2" + maxServerVersion: "4.2.99" + topologies: [ replicaset, sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + # Trigger the fail point twice to allow asserting the error label in + # the retry attempt's response. + mode: { times: 2 } + data: + failCommands: [ "insert" ] + errorCode: 189 # PrimarySteppedDown + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 33 } + expectError: + errorLabelsContain: [ "RetryableWriteError" ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: &insertCommandStartedEvent + command: + insert: *collectionName + documents: [{ _id: 3, x: 33 }] + commandName: insert + databaseName: *databaseName + - commandStartedEvent: *insertCommandStartedEvent + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + + - description: "RetryableWriteError label is added based on writeConcernError in pre-4.4 mongod response" + runOnRequirements: + - minServerVersion: "4.2" + maxServerVersion: "4.2.99" + topologies: [ replicaset ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + # Trigger the fail point twice to allow asserting the error label in + # the retry attempt's response. + mode: { times: 2 } + data: + failCommands: [ "insert" ] + writeConcernError: + code: 91 # ShutdownInProgress + errmsg: "Replication is being shut down" + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 33 } + expectError: + errorLabelsContain: [ "RetryableWriteError" ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: *insertCommandStartedEvent + - commandStartedEvent: *insertCommandStartedEvent + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + # writeConcernError doesn't prevent the server from applying the write + - { _id: 3, x: 33 } + + - description: "RetryableWriteError label is not added based on writeConcernError in pre-4.4 mongos response" + runOnRequirements: + - minServerVersion: "4.2" + maxServerVersion: "4.2.99" + topologies: [ sharded ] + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + # Trigger the fail point only once since a RetryableWriteError label + # will not be added and the write will not be retried. + mode: { times: 1 } + data: + failCommands: [ "insert" ] + writeConcernError: + code: 91 # ShutdownInProgress + errmsg: "Replication is being shut down" + - name: insertOne + object: *collection0 + arguments: + document: { _id: 3, x: 33 } + expectError: + errorLabelsOmit: [ "RetryableWriteError" ] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: *insertCommandStartedEvent + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + # writeConcernError doesn't prevent the server from applying the write + - { _id: 3, x: 33 } + - + description: 'InsertOne succeeds after connection failure' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + closeConnection: true + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertOne fails after connection failure when retryWrites option is false' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + - database: + id: &database1 database1 + client: *client1 + databaseName: *databaseName + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collectionName + - + name: failPoint + object: testRunner + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + closeConnection: true + - + object: *collection1 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + # If retryWrites is false, the driver should not add the + # RetryableWriteError label to the error. + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'InsertOne fails after Interrupted' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + errorCode: 11601 + closeConnection: false + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'InsertOne fails after WriteConcernError Interrupted' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + writeConcernError: + code: 11601 + errmsg: 'operation was interrupted' + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } # The write was still applied. + - + description: 'InsertOne fails after WriteConcernError WriteConcernTimeout' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ insert ] + writeConcernError: + code: 64 + errmsg: 'waiting for replication timed out' + errInfo: + wtimeout: true + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } # The write was still applied. + - + description: 'InsertOne fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ insert ] + closeConnection: true + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/insertOne.json b/src/test/spec/json/retryable-writes/unified/insertOne.json new file mode 100644 index 000000000..a6afdbf22 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertOne.json @@ -0,0 +1,245 @@ +{ + "description": "insertOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "InsertOne is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "InsertOne is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + ] + }, + { + "description": "InsertOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/insertOne.yml b/src/test/spec/json/retryable-writes/unified/insertOne.yml new file mode 100644 index 000000000..9b4563482 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/insertOne.yml @@ -0,0 +1,127 @@ +description: insertOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'InsertOne is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: true } + - + description: 'InsertOne is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - + description: 'InsertOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3, x: 33 } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.json b/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.json new file mode 100644 index 000000000..22c4561ae --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.json @@ -0,0 +1,300 @@ +{ + "description": "replaceOne-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "ReplaceOne fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "ReplaceOne succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "ReplaceOne succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.yml new file mode 100644 index 000000000..38f271d56 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne-errorLabels.yml @@ -0,0 +1,170 @@ +description: replaceOne-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'ReplaceOne succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + # Driver retries operation and it succeeds + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'ReplaceOne fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'ReplaceOne succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'ReplaceOne succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.json b/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.json new file mode 100644 index 000000000..c957db724 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.json @@ -0,0 +1,118 @@ +{ + "description": "replaceOne-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.yml new file mode 100644 index 000000000..b6f2f65bd --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne-serverErrors.yml @@ -0,0 +1,68 @@ +description: replaceOne-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'ReplaceOne fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ update ] + closeConnection: true + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne.json b/src/test/spec/json/retryable-writes/unified/replaceOne.json new file mode 100644 index 000000000..ee6e37d3b --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne.json @@ -0,0 +1,242 @@ +{ + "description": "replaceOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "ReplaceOne is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "ReplaceOne is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "ReplaceOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/replaceOne.yml b/src/test/spec/json/retryable-writes/unified/replaceOne.yml new file mode 100644 index 000000000..90fc55903 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/replaceOne.yml @@ -0,0 +1,132 @@ +description: replaceOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'ReplaceOne is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: true } + - + description: 'ReplaceOne is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 111 } + - { _id: 2, x: 22 } + - + description: 'ReplaceOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: replaceOne + arguments: + filter: { _id: 1 } + replacement: { _id: 1, x: 111 } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.json b/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.json new file mode 100644 index 000000000..eaa114acf --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.json @@ -0,0 +1,77 @@ +{ + "description": "unacknowledged write does not set txnNumber", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + } + } + } + ], + "tests": [ + { + "description": "unacknowledged write does not set txnNumber", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "x": 11 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.yml b/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.yml new file mode 100644 index 000000000..3a0cce6ae --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/unacknowledged-write-concern.yml @@ -0,0 +1,40 @@ +description: "unacknowledged write does not set txnNumber" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "3.6" + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name retryable-writes-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + collectionOptions: + writeConcern: { w: 0 } + +tests: + - description: "unacknowledged write does not set txnNumber" + operations: + - object: *collection0 + name: insertOne + arguments: + document: { _id: 1, x: 11 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: insert + command: + txnNumber: { $$exists: false } diff --git a/src/test/spec/json/retryable-writes/unified/updateMany.json b/src/test/spec/json/retryable-writes/unified/updateMany.json new file mode 100644 index 000000000..12c5204ee --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateMany.json @@ -0,0 +1,112 @@ +{ + "description": "updateMany", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "UpdateMany ignores retryWrites", + "operations": [ + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 23 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": false + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/updateMany.yml b/src/test/spec/json/retryable-writes/unified/updateMany.yml new file mode 100644 index 000000000..d1febec30 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateMany.yml @@ -0,0 +1,62 @@ +description: updateMany + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'UpdateMany ignores retryWrites' + operations: + - + object: *collection0 + name: updateMany + arguments: + filter: { } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 23 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: false } diff --git a/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.json b/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.json new file mode 100644 index 000000000..e44cef45f --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.json @@ -0,0 +1,304 @@ +{ + "description": "updateOne-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne succeeds with RetryableWriteError from server", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "UpdateOne fails if server does not return RetryableWriteError", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isError": true, + "errorLabelsOmit": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "UpdateOne succeeds after PrimarySteppedDown", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "UpdateOne succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.yml b/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.yml new file mode 100644 index 000000000..f530e8dba --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne-errorLabels.yml @@ -0,0 +1,170 @@ +description: updateOne-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'UpdateOne succeeds with RetryableWriteError from server' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + # Driver retries operation and it succeeds + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'UpdateOne fails if server does not return RetryableWriteError' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + # Driver does not retry operation because there was no RetryableWriteError label on response + expectError: + isError: true + errorLabelsOmit: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'UpdateOne succeeds after PrimarySteppedDown' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorCode: 189 + errorLabels: + - RetryableWriteError + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'UpdateOne succeeds after WriteConcernError ShutdownInProgress' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ update ] + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.json b/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.json new file mode 100644 index 000000000..648834ada --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.json @@ -0,0 +1,119 @@ +{ + "description": "updateOne-serverErrors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne fails with a RetryableWriteError label after two connection failures", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isError": true, + "errorLabelsContain": [ + "RetryableWriteError" + ] + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.yml b/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.yml new file mode 100644 index 000000000..6cd7281db --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne-serverErrors.yml @@ -0,0 +1,71 @@ +# This file was created automatically using mongodb-spec-converter. +# Please review the generated file, then remove this notice. + +description: updateOne-serverErrors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: [ replicaset ] + - + minServerVersion: 4.1.7 + topologies: [ sharded, load-balanced ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'UpdateOne fails with a RetryableWriteError label after two connection failures' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ update ] + closeConnection: true + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + isError: true + errorLabelsContain: + - RetryableWriteError + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/retryable-writes/unified/updateOne.json b/src/test/spec/json/retryable-writes/unified/updateOne.json new file mode 100644 index 000000000..99ffba8e2 --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne.json @@ -0,0 +1,424 @@ +{ + "description": "updateOne", + "schemaVersion": "1.0", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "retryable-writes-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll" + } + } + ], + "initialData": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "tests": [ + { + "description": "UpdateOne is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "update", + "command": { + "txnNumber": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "UpdateOne is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "UpdateOne is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + }, + { + "description": "UpdateOne with upsert is committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3, + "x": 33 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 3 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + }, + { + "description": "UpdateOne with upsert is not committed on first attempt", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 1 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3, + "x": 33 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 3 + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 34 + } + ] + } + ] + }, + { + "description": "UpdateOne with upsert is never committed", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "onPrimaryTransactionalWrite", + "mode": { + "times": 2 + }, + "data": { + "failBeforeCommitExceptionCode": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3, + "x": 33 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectError": { + "isError": true + } + } + ], + "outcome": [ + { + "collectionName": "coll", + "databaseName": "retryable-writes-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/retryable-writes/unified/updateOne.yml b/src/test/spec/json/retryable-writes/unified/updateOne.yml new file mode 100644 index 000000000..5c255b0da --- /dev/null +++ b/src/test/spec/json/retryable-writes/unified/updateOne.yml @@ -0,0 +1,225 @@ +description: updateOne + +schemaVersion: '1.0' + +runOnRequirements: + - + minServerVersion: '3.6' + topologies: [ replicaset ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name retryable-writes-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name coll + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + +tests: + - + description: 'UpdateOne is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + expectEvents: + - client: client0 + events: + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: true } + - commandStartedEvent: + commandName: update + command: + txnNumber: { $$exists: true } + - + description: 'UpdateOne is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 22 } + - + description: 'UpdateOne is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - + description: 'UpdateOne with upsert is committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 3, x: 33 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 34 } + - + description: 'UpdateOne with upsert is not committed on first attempt' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 1 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 3, x: 33 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 3 + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 34 } + - + description: 'UpdateOne with upsert is never committed' + operations: + - + name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: onPrimaryTransactionalWrite + mode: { times: 2 } + data: + failBeforeCommitExceptionCode: 1 + - + object: *collection0 + name: updateOne + arguments: + filter: { _id: 3, x: 33 } + update: { $inc: { x: 1 } } + upsert: true + expectError: + isError: true + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } diff --git a/src/test/spec/json/run-command/README.rst b/src/test/spec/json/run-command/README.rst new file mode 100644 index 000000000..3c07def65 --- /dev/null +++ b/src/test/spec/json/run-command/README.rst @@ -0,0 +1,14 @@ +================= +Run Command Tests +================= + +.. contents:: + +---- + +Introduction +============ + +The YAML and JSON files in the ``unified`` sub-directories are platform-independent tests +that drivers can use to prove their conformance to the RunCommand spec. Tests in the +``unified`` directory are written using the `Unified Test Format <../../unified-test-format/unified-test-format.rst>`_. diff --git a/src/test/spec/json/run-command/unified/runCommand.json b/src/test/spec/json/run-command/unified/runCommand.json new file mode 100644 index 000000000..007e514bd --- /dev/null +++ b/src/test/spec/json/run-command/unified/runCommand.json @@ -0,0 +1,635 @@ +{ + "description": "runCommand", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "collection", + "database": "db", + "collectionName": "collection" + } + }, + { + "database": { + "id": "dbWithRC", + "client": "client", + "databaseName": "dbWithRC", + "databaseOptions": { + "readConcern": { + "level": "local" + } + } + } + }, + { + "database": { + "id": "dbWithWC", + "client": "client", + "databaseName": "dbWithWC", + "databaseOptions": { + "writeConcern": { + "w": 0 + } + } + } + }, + { + "session": { + "id": "session", + "client": "client" + } + }, + { + "client": { + "id": "clientWithStableApi", + "observeEvents": [ + "commandStartedEvent" + ], + "serverApi": { + "version": "1", + "strict": true + } + } + }, + { + "database": { + "id": "dbWithStableApi", + "client": "clientWithStableApi", + "databaseName": "dbWithStableApi" + } + } + ], + "initialData": [ + { + "collectionName": "collection", + "databaseName": "db", + "documents": [] + } + ], + "tests": [ + { + "description": "always attaches $db and implicit lsid to given command and omits default readPreference", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "db", + "lsid": { + "$$exists": true + }, + "$readPreference": { + "$$exists": false + } + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "always gossips the $clusterTime on the sent command", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "ping" + } + }, + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$clusterTime": { + "$$exists": true + } + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "attaches the provided session lsid to given command", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "session": "session" + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "attaches the provided $readPreference to given command", + "runOnRequirements": [ + { + "topologies": [ + "replicaset", + "sharded-replicaset", + "load-balanced", + "sharded" + ] + } + ], + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "readPreference": { + "mode": "nearest" + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$readPreference": { + "mode": "nearest" + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "does not attach $readPreference to given command on standalone", + "runOnRequirements": [ + { + "topologies": [ + "single" + ] + } + ], + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "readPreference": { + "mode": "nearest" + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$readPreference": { + "$$exists": false + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "does not attach primary $readPreference to given command", + "operations": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + }, + "readPreference": { + "mode": "primary" + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$readPreference": { + "$$exists": false + }, + "$db": "db" + }, + "commandName": "ping" + } + } + ] + } + ] + }, + { + "description": "does not inherit readConcern specified at the db level", + "operations": [ + { + "name": "runCommand", + "object": "dbWithRC", + "arguments": { + "commandName": "aggregate", + "command": { + "aggregate": "collection", + "pipeline": [], + "cursor": {} + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "collection", + "readConcern": { + "$$exists": false + }, + "$db": "dbWithRC" + }, + "commandName": "aggregate" + } + } + ] + } + ] + }, + { + "description": "does not inherit writeConcern specified at the db level", + "operations": [ + { + "name": "runCommand", + "object": "dbWithWC", + "arguments": { + "commandName": "insert", + "command": { + "insert": "collection", + "documents": [ + { + "foo": "bar" + } + ], + "ordered": true + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collection", + "writeConcern": { + "$$exists": false + }, + "$db": "dbWithWC" + }, + "commandName": "insert" + } + } + ] + } + ] + }, + { + "description": "does not retry retryable errors on given command", + "runOnRequirements": [ + { + "minServerVersion": "4.2" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ] + }, + { + "description": "attaches transaction fields to given command", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.2", + "topologies": [ + "sharded-replicaset", + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "withTransaction", + "object": "session", + "arguments": { + "callback": [ + { + "name": "runCommand", + "object": "db", + "arguments": { + "session": "session", + "commandName": "insert", + "command": { + "insert": "collection", + "documents": [ + { + "foo": "transaction" + } + ], + "ordered": true + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "collection", + "documents": [ + { + "foo": "transaction" + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "db" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session" + }, + "txnNumber": 1, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "attaches apiVersion fields to given command when stableApi is configured on the client", + "runOnRequirements": [ + { + "minServerVersion": "5.0" + } + ], + "operations": [ + { + "name": "runCommand", + "object": "dbWithStableApi", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + } + ], + "expectEvents": [ + { + "client": "clientWithStableApi", + "events": [ + { + "commandStartedEvent": { + "command": { + "ping": 1, + "$db": "dbWithStableApi", + "apiVersion": "1", + "apiStrict": true, + "apiDeprecationErrors": { + "$$unsetOrMatches": false + } + }, + "commandName": "ping" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/run-command/unified/runCommand.yml b/src/test/spec/json/run-command/unified/runCommand.yml new file mode 100644 index 000000000..eaa12eff2 --- /dev/null +++ b/src/test/spec/json/run-command/unified/runCommand.yml @@ -0,0 +1,319 @@ +description: runCommand + +schemaVersion: "1.3" + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: [commandStartedEvent] + - database: + id: &db db + client: *client + databaseName: *db + - collection: + id: &collection collection + database: *db + collectionName: *collection + - database: + id: &dbWithRC dbWithRC + client: *client + databaseName: *dbWithRC + databaseOptions: + readConcern: { level: 'local' } + - database: + id: &dbWithWC dbWithWC + client: *client + databaseName: *dbWithWC + databaseOptions: + writeConcern: { w: 0 } + - session: + id: &session session + client: *client + # Stable API test + - client: + id: &clientWithStableApi clientWithStableApi + observeEvents: [commandStartedEvent] + serverApi: + version: "1" + strict: true + - database: + id: &dbWithStableApi dbWithStableApi + client: *clientWithStableApi + databaseName: *dbWithStableApi + +initialData: +- collectionName: *collection + databaseName: *db + documents: [] + +tests: + - description: always attaches $db and implicit lsid to given command and omits default readPreference + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $db: *db + lsid: { $$exists: true } + $readPreference: { $$exists: false } + commandName: ping + + - description: always gossips the $clusterTime on the sent command + runOnRequirements: + # Only replicasets and sharded clusters have a $clusterTime + - topologies: [ replicaset, sharded ] + operations: + # We have to run one command to obtain a clusterTime to gossip + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + commandName: ping + # Only check the shape of the second ping which should have the $clusterTime received from the first operation + - commandStartedEvent: + command: + ping: 1 + $clusterTime: { $$exists: true } + commandName: ping + + - description: attaches the provided session lsid to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + session: *session + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + lsid: { $$sessionLsid: *session } + $db: *db + commandName: ping + + - description: attaches the provided $readPreference to given command + runOnRequirements: + # Exclude single topology, which is most likely a standalone server + - topologies: [ replicaset, sharded-replicaset, load-balanced, sharded ] + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: &readPreference { mode: 'nearest' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: *readPreference + $db: *db + commandName: ping + + - description: does not attach $readPreference to given command on standalone + runOnRequirements: + # This test assumes that the single topology contains a standalone server; + # however, it is possible for a single topology to contain a direct + # connection to another server type. + # See: https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#topology-type-single + - topologies: [ single ] + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: { mode: 'nearest' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: { $$exists: false } + $db: *db + commandName: ping + + - description: does not attach primary $readPreference to given command + operations: + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + readPreference: { mode: 'primary' } + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + ping: 1 + $readPreference: { $$exists: false } + $db: *db + commandName: ping + + - description: does not inherit readConcern specified at the db level + operations: + - name: runCommand + object: *dbWithRC + # Test with a command that supports a readConcern option. + # expectResult is intentionally omitted because some drivers + # may automatically convert command responses into cursors. + arguments: + commandName: aggregate + command: { aggregate: *collection, pipeline: [], cursor: {} } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + aggregate: *collection + readConcern: { $$exists: false } + $db: *dbWithRC + commandName: aggregate + + - description: does not inherit writeConcern specified at the db level + operations: + - name: runCommand + object: *dbWithWC + arguments: + commandName: insert + command: + insert: *collection + documents: [ { foo: 'bar' } ] + ordered: true + expectResult: { ok: 1 } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + writeConcern: { $$exists: false } + $db: *dbWithWC + commandName: insert + + - description: does not retry retryable errors on given command + runOnRequirements: + - minServerVersion: "4.2" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ping] + closeConnection: true + - name: runCommand + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectError: + isClientError: true + + - description: attaches transaction fields to given command + runOnRequirements: + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.2" + topologies: [ sharded-replicaset, load-balanced ] + operations: + - name: withTransaction + object: *session + arguments: + callback: + - name: runCommand + object: *db + arguments: + session: *session + commandName: insert + command: + insert: *collection + documents: [ { foo: 'transaction' } ] + ordered: true + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } } + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + insert: *collection + documents: [ { foo: 'transaction' } ] + ordered: true + lsid: { $$sessionLsid: *session } + txnNumber: 1 + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *db + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session } + txnNumber: 1 + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + readConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + + - description: attaches apiVersion fields to given command when stableApi is configured on the client + runOnRequirements: + - minServerVersion: "5.0" + operations: + - name: runCommand + object: *dbWithStableApi + arguments: + commandName: ping + command: + ping: 1 + expectResult: { ok: 1 } + expectEvents: + - client: *clientWithStableApi + events: + - commandStartedEvent: + command: + ping: 1 + $db: *dbWithStableApi + apiVersion: "1" + apiStrict: true + apiDeprecationErrors: { $$unsetOrMatches: false } + commandName: ping diff --git a/src/test/spec/json/run-command/unified/runCursorCommand.json b/src/test/spec/json/run-command/unified/runCursorCommand.json new file mode 100644 index 000000000..4f1ec8a01 --- /dev/null +++ b/src/test/spec/json/run-command/unified/runCursorCommand.json @@ -0,0 +1,877 @@ +{ + "description": "runCursorCommand", + "schemaVersion": "1.9", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent", + "connectionReadyEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ] + } + }, + { + "session": { + "id": "session", + "client": "client" + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "collection", + "database": "db", + "collectionName": "collection" + } + } + ], + "initialData": [ + { + "collectionName": "collection", + "databaseName": "db", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "successfully executes checkMetadataConsistency cursor creating command", + "runOnRequirements": [ + { + "minServerVersion": "7.0", + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "checkMetadataConsistency", + "command": { + "checkMetadataConsistency": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "checkMetadataConsistency": 1, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "checkMetadataConsistency" + } + } + ] + } + ] + }, + { + "description": "errors if the command response is not a cursor", + "operations": [ + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ] + }, + { + "description": "creates an implicit session that is reused across getMores", + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "command": { + "find": "collection", + "batchSize": 2 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + }, + { + "name": "assertSameLsidOnLastTwoCommands", + "object": "testRunner", + "arguments": { + "client": "client" + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "batchSize": 2, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "accepts an explicit session that is reused across getMores", + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "session": "session", + "command": { + "find": "collection", + "batchSize": 2 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + }, + { + "name": "assertSameLsidOnLastTwoCommands", + "object": "testRunner", + "arguments": { + "client": "client" + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "batchSize": 2, + "$db": "db", + "lsid": { + "$$sessionLsid": "session" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$sessionLsid": "session" + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "returns pinned connections to the pool when the cursor is exhausted", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "commandName": "find", + "batchSize": 2, + "session": "session", + "command": { + "find": "collection", + "batchSize": 2 + } + }, + "saveResultAsEntity": "cursor" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client", + "connections": 1 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor", + "expectResult": { + "_id": 1, + "x": 11 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor", + "expectResult": { + "_id": 2, + "x": 22 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor", + "expectResult": { + "_id": 3, + "x": 33 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor", + "expectResult": { + "_id": 4, + "x": 44 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "cursor", + "expectResult": { + "_id": 5, + "x": 55 + } + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client", + "connections": 0 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "batchSize": 2, + "$db": "db", + "lsid": { + "$$sessionLsid": "session" + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$sessionLsid": "session" + } + }, + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$sessionLsid": "session" + } + }, + "commandName": "getMore" + } + } + ] + }, + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionReadyEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + }, + { + "description": "returns pinned connections to the pool when the cursor is closed", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ] + } + ], + "operations": [ + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "commandName": "find", + "command": { + "find": "collection", + "batchSize": 2 + } + }, + "saveResultAsEntity": "cursor" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client", + "connections": 1 + } + }, + { + "name": "close", + "object": "cursor" + }, + { + "name": "assertNumberConnectionsCheckedOut", + "object": "testRunner", + "arguments": { + "client": "client", + "connections": 0 + } + } + ] + }, + { + "description": "supports configuring getMore batchSize", + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "batchSize": 5, + "command": { + "find": "collection", + "batchSize": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "batchSize": 1, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "batchSize": 5, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "supports configuring getMore maxTimeMS", + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "maxTimeMS": 300, + "command": { + "find": "collection", + "maxTimeMS": 200, + "batchSize": 1 + } + }, + "ignoreResultAndError": true + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "ignoreExtraEvents": true, + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "maxTimeMS": 200, + "batchSize": 1, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "maxTimeMS": 300, + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "supports configuring getMore comment", + "runOnRequirements": [ + { + "minServerVersion": "4.4" + } + ], + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "comment": { + "hello": "getMore" + }, + "command": { + "find": "collection", + "batchSize": 1, + "comment": { + "hello": "find" + } + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "batchSize": 1, + "comment": { + "hello": "find" + }, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "comment": { + "hello": "getMore" + }, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "does not close the cursor when receiving an empty batch", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "dropCollection", + "object": "db", + "arguments": { + "collection": "cappedCollection" + } + }, + { + "name": "createCollection", + "object": "db", + "arguments": { + "collection": "cappedCollection", + "capped": true, + "size": 4096, + "max": 3 + }, + "saveResultAsEntity": "cappedCollection" + }, + { + "name": "insertMany", + "object": "cappedCollection", + "arguments": { + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + }, + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "cursorType": "tailable", + "commandName": "find", + "batchSize": 2, + "command": { + "find": "cappedCollection", + "tailable": true + } + }, + "saveResultAsEntity": "cursor" + }, + { + "name": "iterateOnce", + "object": "cursor" + }, + { + "name": "iterateOnce", + "object": "cursor" + }, + { + "name": "iterateOnce", + "object": "cursor" + }, + { + "name": "close", + "object": "cursor" + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "cappedCollection" + }, + "commandName": "drop" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "cappedCollection" + }, + "commandName": "create" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "cappedCollection" + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "cappedCollection", + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "cappedCollection", + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "command": { + "killCursors": "cappedCollection", + "cursors": { + "$$type": "array" + } + }, + "commandName": "killCursors" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/run-command/unified/runCursorCommand.yml b/src/test/spec/json/run-command/unified/runCursorCommand.yml new file mode 100644 index 000000000..1f9bf532c --- /dev/null +++ b/src/test/spec/json/run-command/unified/runCursorCommand.yml @@ -0,0 +1,391 @@ +description: runCursorCommand + +schemaVersion: '1.9' + +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: [commandStartedEvent, connectionReadyEvent, connectionCheckedOutEvent, connectionCheckedInEvent] + - session: + id: &session session + client: *client + - database: + id: &db db + client: *client + databaseName: *db + - collection: + id: &collection collection + database: *db + collectionName: *collection + +initialData: + - collectionName: collection + databaseName: *db + documents: &documents + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } + +tests: + # This is what this API was invented to do. + - description: successfully executes checkMetadataConsistency cursor creating command + runOnRequirements: + - minServerVersion: '7.0' + topologies: [sharded] + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: checkMetadataConsistency + command: { checkMetadataConsistency: 1 } + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + checkMetadataConsistency: 1 + $db: *db + lsid: { $$exists: true } + commandName: checkMetadataConsistency + + - description: errors if the command response is not a cursor + operations: + - name: createCommandCursor + object: *db + arguments: + commandName: ping + command: { ping: 1 } + expectError: + isClientError: true + + + # Driver Sessions + - description: creates an implicit session that is reused across getMores + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + command: { find: *collection, batchSize: 2 } + expectResult: *documents + - name: assertSameLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: *collection + batchSize: 2 + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$exists: true } + commandName: getMore + + - description: accepts an explicit session that is reused across getMores + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + session: *session + command: { find: *collection, batchSize: 2 } + expectResult: *documents + - name: assertSameLsidOnLastTwoCommands + object: testRunner + arguments: + client: *client + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: *collection + batchSize: 2 + $db: *db + lsid: { $$sessionLsid: *session } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$sessionLsid: *session } + commandName: getMore + + # Load Balancers + - description: returns pinned connections to the pool when the cursor is exhausted + runOnRequirements: + - topologies: [ load-balanced ] + operations: + - name: createCommandCursor + object: *db + arguments: + commandName: find + batchSize: 2 + session: *session + command: { find: *collection, batchSize: 2 } + saveResultAsEntity: &cursor cursor + - name: assertNumberConnectionsCheckedOut + object: testRunner + arguments: + client: *client + connections: 1 + - name: iterateUntilDocumentOrError + object: *cursor + expectResult: { _id: 1, x: 11 } + - name: iterateUntilDocumentOrError + object: *cursor + expectResult: { _id: 2, x: 22 } + - name: iterateUntilDocumentOrError + object: *cursor + expectResult: { _id: 3, x: 33 } + - name: iterateUntilDocumentOrError + object: *cursor + expectResult: { _id: 4, x: 44 } + - name: iterateUntilDocumentOrError + object: *cursor + expectResult: { _id: 5, x: 55 } + - name: assertNumberConnectionsCheckedOut + object: testRunner + arguments: + client: *client + connections: 0 + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: *collection + batchSize: 2 + $db: *db + lsid: { $$sessionLsid: *session } + commandName: find # 2 documents + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$sessionLsid: *session } + commandName: getMore # 2 documents + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$sessionLsid: *session } + commandName: getMore # 1 document + # Total documents: 5 + - client: *client + eventType: cmap + events: + - connectionReadyEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + + - description: returns pinned connections to the pool when the cursor is closed + runOnRequirements: + - topologies: [ load-balanced ] + operations: + - name: createCommandCursor + object: *db + arguments: + commandName: find + command: { find: *collection, batchSize: 2 } + saveResultAsEntity: *cursor + - name: assertNumberConnectionsCheckedOut + object: testRunner + arguments: + client: *client + connections: 1 + - name: close + object: *cursor + - name: assertNumberConnectionsCheckedOut + object: testRunner + arguments: + client: *client + connections: 0 + + # Iterating the Cursor / Executing GetMores + - description: supports configuring getMore batchSize + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + batchSize: 5 + command: { find: *collection, batchSize: 1 } + expectResult: *documents + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: *collection + batchSize: 1 + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + batchSize: 5 + $db: *db + lsid: { $$exists: true } + commandName: getMore + + - description: supports configuring getMore maxTimeMS + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + maxTimeMS: 300 + command: { find: *collection, maxTimeMS: 200, batchSize: 1 } + ignoreResultAndError: true + expectEvents: + - client: *client + eventType: command + # The getMore should receive an error here because we do not have the right kind of cursor + # So drivers should run a killCursors, but neither the error nor the killCursors command is relevant to this test + ignoreExtraEvents: true + events: + - commandStartedEvent: + command: + find: *collection + maxTimeMS: 200 + batchSize: 1 + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + maxTimeMS: 300 + lsid: { $$exists: true } + commandName: getMore + + - description: supports configuring getMore comment + runOnRequirements: + - minServerVersion: '4.4' + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + comment: { hello: 'getMore' } + command: { find: *collection, batchSize: 1, comment: { hello: 'find' } } + expectResult: *documents + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + find: *collection + batchSize: 1 + comment: { hello: 'find' } + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + comment: { hello: 'getMore' } + $db: *db + lsid: { $$exists: true } + commandName: getMore + + # Tailable cursor + - description: does not close the cursor when receiving an empty batch + runOnRequirements: + - serverless: forbid + operations: + - name: dropCollection + object: *db + arguments: + collection: &cappedCollection cappedCollection + - name: createCollection + object: *db + arguments: + collection: *cappedCollection + capped: true + size: 4096 + max: 3 + saveResultAsEntity: *cappedCollection + - name: insertMany + object: *cappedCollection + arguments: + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - name: createCommandCursor + object: *db + arguments: + cursorType: tailable + commandName: find + batchSize: 2 + command: { find: *cappedCollection, tailable: true } + saveResultAsEntity: &cursor cursor + - name: iterateOnce + object: *cursor + - name: iterateOnce + object: *cursor + - name: iterateOnce + object: *cursor + - name: close + object: *cursor + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + command: + drop: *cappedCollection + commandName: drop + - commandStartedEvent: + command: + create: *cappedCollection + commandName: create + - commandStartedEvent: + command: + insert: *cappedCollection + commandName: insert + - commandStartedEvent: + command: + find: *cappedCollection + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *cappedCollection + $db: *db + lsid: { $$exists: true } + commandName: getMore + - commandStartedEvent: + command: + killCursors: *cappedCollection + cursors: { $$type: array } + commandName: killCursors diff --git a/src/test/spec/json/server-discovery-and-monitoring/README.md b/src/test/spec/json/server-discovery-and-monitoring/README.md new file mode 100644 index 000000000..e747fee53 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/README.md @@ -0,0 +1,239 @@ +# Server Discovery And Monitoring Tests + +______________________________________________________________________ + +The YAML and JSON files in this directory tree are platform-independent tests that drivers can use to prove their +conformance to the Server Discovery And Monitoring Spec. + +Additional prose tests, that cannot be represented as spec tests, are described and MUST be implemented. + +## Version + +Files in the "specifications" repository have no version scheme. They are not tied to a MongoDB server version. + +## Format + +Each YAML file has the following keys: + +- description: A textual description of the test. +- uri: A connection string. +- phases: An array of "phase" objects. A phase of the test optionally sends inputs to the client, then tests the + client's resulting TopologyDescription. + +Each phase object has the following keys: + +- description: (optional) A textual description of this phase. +- responses: (optional) An array of "response" objects. If not provided, the test runner should construct the client and + perform assertions specified in the outcome object without processing any responses. +- applicationErrors: (optional) An array of "applicationError" objects. +- outcome: An "outcome" object representing the TopologyDescription. + +A response is a pair of values: + +- The source, for example "a:27017". This is the address the client sent the "hello" or legacy hello command to. +- A hello or legacy hello response, for example `{ok: 1, helloOk: true, isWritablePrimary: true}`. If the response + includes an electionId it is shown in extended JSON like `{"$oid": "000000000000000000000002"}`. The empty response + `{}` indicates a network error when attempting to call "hello" or legacy hello. + +An "applicationError" object has the following keys: + +- address: The source address, for example "a:27017". +- generation: (optional) The error's generation number, for example `1`. When absent this value defaults to the pool's + current generation number. +- maxWireVersion: The `maxWireVersion` of the connection the error occurs on, for example `9`. Added to support testing + the behavior of "not writable primary" errors on \<4.2 and >=4.2 servers. +- when: A string describing when this mock error should occur. Supported values are: + - "beforeHandshakeCompletes": Simulate this mock error as if it occurred during a new connection's handshake for an + application operation. + - "afterHandshakeCompletes": Simulate this mock error as if it occurred on an established connection for an + application operation (i.e. after the connection pool check out succeeds). +- type: The type of error to mock. Supported values are: + - "command": A command error. Always accompanied with a "response". + - "network": A non-timeout network error. + - "timeout": A network timeout error. +- response: (optional) A command error response, for example `{ok: 0, errmsg: "not primary"}`. Present if and only if + `type` is "command". Note the server only returns "not primary" if the "hello" command has been run on this + connection. Otherwise the legacy error message is returned. + +In non-monitoring tests, an "outcome" represents the correct TopologyDescription that results from processing the +responses in the phases so far. It has the following keys: + +- topologyType: A string like "ReplicaSetNoPrimary". +- setName: A string with the expected replica set name, or null. +- servers: An object whose keys are addresses like "a:27017", and whose values are "server" objects. +- logicalSessionTimeoutMinutes: null or an integer. +- maxSetVersion: absent or an integer. +- maxElectionId: absent or a BSON ObjectId. +- compatible: absent or a bool. + +A "server" object represents a correct ServerDescription within the client's current TopologyDescription. It has the +following keys: + +- type: A ServerType name, like "RSSecondary". See [ServerType](../server-discovery-and-monitoring.md#servertype) for + details pertaining to async and multi-threaded drivers. +- error: An optional string that must be a substring of the message on the `ServerDescription.error` object +- setName: A string with the expected replica set name, or null. +- setVersion: absent or an integer. +- electionId: absent, null, or an ObjectId. +- logicalSessionTimeoutMinutes: absent, null, or an integer. +- minWireVersion: absent or an integer. +- maxWireVersion: absent or an integer. +- topologyVersion: absent, null, or a topologyVersion document. +- pool: (optional) A "pool" object. + +A "pool" object represents a correct connection pool for a given server. It has the following keys: + +- generation: This server's expected pool generation, like `0`. + +In monitoring tests, an "outcome" contains a list of SDAM events that should have been published by the client as a +result of processing hello or legacy hello responses in the current phase. Any SDAM events published by the client +during its construction (that is, prior to processing any of the responses) should be combined with the events published +during processing of hello or legacy hello responses of the first phase of the test. A test MAY explicitly verify events +published during client construction by providing an empty responses array for the first phase. + +## Use as unittests + +### Mocking + +Drivers should be able to test their server discovery and monitoring logic without any network I/O, by parsing hello (or +legacy hello) and application error from the test file and passing them into the driver code. Parts of the client and +monitoring code may need to be mocked or subclassed to achieve this. +[A reference implementation for PyMongo 3.10.1 is available here](https://github.com/mongodb/mongo-python-driver/blob/3.10.1/test/test_discovery_and_monitoring.py). + +### Initialization + +For each file, create a fresh client object initialized with the file's "uri". + +All files in the "single" directory include a connection string with one host and no "replicaSet" option. Set the +client's initial TopologyType to Single, however that is achieved using the client's API. (The spec says "The user MUST +be able to set the initial TopologyType to Single" without specifying how.) + +All files in the "sharded" directory include a connection string with multiple hosts and no "replicaSet" option. Set the +client's initial TopologyType to Unknown or Sharded, depending on the client's API. + +All files in the "rs" directory include a connection string with a "replicaSet" option. Set the client's initial +TopologyType to ReplicaSetNoPrimary. (For most clients, parsing a connection string with a "replicaSet" option +automatically sets the TopologyType to ReplicaSetNoPrimary.) Some of the files in "rs" are post-fixed with "pre-6.0". +These files test the `updateRSFromPrimary` behavior prior to maxWireVersion 17, there should be no special handling +required for these tests. + +Set up a listener to collect SDAM events published by the client, including events published during client construction. + +### Test Phases + +For each phase in the file: + +1. Parse the "responses" array. Pass in the responses in order to the driver code. If a response is the empty object + `{}`, simulate a network error. +2. Parse the "applicationErrors" array. For each element, simulate the given error as if it occurred while running an + application operation. Note that it is sufficient to construct a mock error and call the procedure which updates + the topology, e.g. `topology.handleApplicationError(address, generation, maxWireVersion, error)`. + +For non-monitoring tests, once all responses are processed, assert that the phase's "outcome" object is equivalent to +the driver's current TopologyDescription. + +For monitoring tests, once all responses are processed, assert that the events collected so far by the SDAM event +listener are equivalent to the events specified in the phase. + +Some fields such as "logicalSessionTimeoutMinutes", "compatible", and "topologyVersion" were added later and haven't +been added to all test files. If these fields are present, test that they are equivalent to the fields of the driver's +current TopologyDescription or ServerDescription. + +For monitoring tests, clear the list of events collected so far. + +Continue until all phases have been executed. + +## Integration Tests + +Integration tests are provided in the "unified" directory and are written in the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +## Prose Tests + +The following prose tests cannot be represented as spec tests and MUST be implemented. + +### Streaming protocol Tests + +Drivers that implement the streaming protocol (multi-threaded or asynchronous drivers) must implement the following +tests. Each test should be run against a standalone, replica set, and sharded cluster unless otherwise noted. + +Some of these cases should already be tested with the old protocol; in that case just verify the test cases succeed with +the new protocol. + +1. Configure the client with heartbeatFrequencyMS set to 500, overriding the default of 10000. Assert the client + processes hello and legacy hello replies more frequently (approximately every 500ms). + +### RTT Tests + +Run the following test(s) on MongoDB 4.4+. + +1. Test that RTT is continuously updated. + 1. Create a client with `heartbeatFrequencyMS=500`, `appName=streamingRttTest`, and subscribe to server events. + + 2. Run a find command to wait for the server to be discovered. + + 3. Sleep for 2 seconds. This must be long enough for multiple heartbeats to succeed. + + 4. Assert that each `ServerDescriptionChangedEvent` includes a non-zero RTT. + + 5. Configure the following failpoint to block hello or legacy hello commands for 250ms which should add extra latency + to each RTT check: + + ```javascript + db.adminCommand({ + configureFailPoint: "failCommand", + mode: {times: 1000}, + data: { + failCommands: ["hello"], // or the legacy hello command + blockConnection: true, + blockTimeMS: 500, + appName: "streamingRttTest", + }, + }); + ``` + + 6. Wait for the server's RTT to exceed 250ms. Eventually the average RTT should also exceed 500ms but we use 250ms to + speed up the test. Note that the + [Server Description Equality](../server-discovery-and-monitoring.md#server-description-equality) rule means that + ServerDescriptionChangedEvents will not be published. This test may need to use a driver specific helper to + obtain the latest RTT instead. If the RTT does not exceed 250ms after 10 seconds, consider the test failed. + + 7. Disable the failpoint: + + ```javascript + db.adminCommand({ + configureFailPoint: "failCommand", + mode: "off", + }); + ``` + +### Heartbeat Tests + +1. Test that `ServerHeartbeatStartedEvent` is emitted before the monitoring socket was created + 1. Create a mock TCP server (example shown below) that pushes a `client connected` event to a shared array when a + client connects and a `client hello received` event when the server receives the client hello and then closes + the connection: + + ```javascript + let events = []; + server = createServer(clientSocket => { + events.push('client connected'); + + clientSocket.on('data', () => { + events.push('client hello received'); + clientSocket.destroy(); + }); + }); + server.listen(9999); + ``` + + 2. Create a client with `serverSelectionTimeoutMS: 500` and listen to `ServerHeartbeatStartedEvent` and + `ServerHeartbeatFailedEvent`, pushing the event name to the same shared array as the mock TCP server + + 3. Attempt to connect client to previously created TCP server, catching the error when the client fails to connect + + 4. Assert that the first four elements in the array are: : + + ```javascript + ['serverHeartbeatStartedEvent', 'client connected', 'client hello received', 'serverHeartbeatFailedEvent'] + ``` diff --git a/src/test/spec/json/server-discovery-and-monitoring/README.rst b/src/test/spec/json/server-discovery-and-monitoring/README.rst deleted file mode 100644 index 7dcaaa8fa..000000000 --- a/src/test/spec/json/server-discovery-and-monitoring/README.rst +++ /dev/null @@ -1,452 +0,0 @@ -===================================== -Server Discovery And Monitoring Tests -===================================== - -.. contents:: - ----- - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the -Server Discovery And Monitoring Spec. - -Additional prose tests, that cannot be represented as spec tests, are -described and MUST be implemented. - -Version -------- - -Files in the "specifications" repository have no version scheme. They are not -tied to a MongoDB server version. - -Format ------- - -Each YAML file has the following keys: - -- description: A textual description of the test. -- uri: A connection string. -- phases: An array of "phase" objects. - A phase of the test optionally sends inputs to the client, - then tests the client's resulting TopologyDescription. - -Each phase object has the following keys: - -- description: (optional) A textual description of this phase. -- responses: (optional) An array of "response" objects. If not provided, - the test runner should construct the client and perform assertions specified - in the outcome object without processing any responses. -- applicationErrors: (optional) An array of "applicationError" objects. -- outcome: An "outcome" object representing the TopologyDescription. - -A response is a pair of values: - -- The source, for example "a:27017". - This is the address the client sent the "hello" or legacy hello command to. -- A hello or legacy hello response, for example ``{ok: 1, helloOk: true, isWritablePrimary: true}``. - If the response includes an electionId it is shown in extended JSON like - ``{"$oid": "000000000000000000000002"}``. - The empty response `{}` indicates a network error - when attempting to call "hello" or legacy hello. - -An "applicationError" object has the following keys: - -- address: The source address, for example "a:27017". -- generation: (optional) The error's generation number, for example ``1``. - When absent this value defaults to the pool's current generation number. -- maxWireVersion: The ``maxWireVersion`` of the connection the error occurs - on, for example ``9``. Added to support testing the behavior of "not writable primary" - errors on <4.2 and >=4.2 servers. -- when: A string describing when this mock error should occur. Supported - values are: - - - "beforeHandshakeCompletes": Simulate this mock error as if it occurred - during a new connection's handshake for an application operation. - - "afterHandshakeCompletes": Simulate this mock error as if it occurred - on an established connection for an application operation (i.e. after - the connection pool check out succeeds). - -- type: The type of error to mock. Supported values are: - - - "command": A command error. Always accompanied with a "response". - - "network": A non-timeout network error. - - "timeout": A network timeout error. - -- response: (optional) A command error response, for example - ``{ok: 0, errmsg: "not primary"}``. Present if and only if ``type`` is - "command". Note the server only returns "not primary" if the "hello" command - has been run on this connection. Otherwise the legacy error message is returned. - -In non-monitoring tests, an "outcome" represents the correct -TopologyDescription that results from processing the responses in the phases -so far. It has the following keys: - -- topologyType: A string like "ReplicaSetNoPrimary". -- setName: A string with the expected replica set name, or null. -- servers: An object whose keys are addresses like "a:27017", and whose values - are "server" objects. -- logicalSessionTimeoutMinutes: null or an integer. -- maxSetVersion: absent or an integer. -- maxElectionId: absent or a BSON ObjectId. -- compatible: absent or a bool. - -A "server" object represents a correct ServerDescription within the client's -current TopologyDescription. It has the following keys: - -- type: A ServerType name, like "RSSecondary". See `ServerType <../server-discovery-and-monitoring.rst#servertype>`_ for details pertaining to async and multi-threaded drivers. -- setName: A string with the expected replica set name, or null. -- setVersion: absent or an integer. -- electionId: absent, null, or an ObjectId. -- logicalSessionTimeoutMinutes: absent, null, or an integer. -- minWireVersion: absent or an integer. -- maxWireVersion: absent or an integer. -- topologyVersion: absent, null, or a topologyVersion document. -- pool: (optional) A "pool" object. - -A "pool" object represents a correct connection pool for a given server. -It has the following keys: - -- generation: This server's expected pool generation, like ``0``. - -In monitoring tests, an "outcome" contains a list of SDAM events that should -have been published by the client as a result of processing hello or legacy hello -responses in the current phase. Any SDAM events published by the client during its -construction (that is, prior to processing any of the responses) should be -combined with the events published during processing of hello or legacy hello -responses of the first phase of the test. A test MAY explicitly verify events -published during client construction by providing an empty responses array for the -first phase. - - -Use as unittests ----------------- - -Mocking -~~~~~~~ - -Drivers should be able to test their server discovery and monitoring logic without -any network I/O, by parsing hello (or legacy hello) and application error from the -test file and passing them into the driver code. Parts of the client and -monitoring code may need to be mocked or subclassed to achieve this. -`A reference implementation for PyMongo 3.10.1 is available here -`_. - -Initialization -~~~~~~~~~~~~~~ - -For each file, create a fresh client object initialized with the file's "uri". - -All files in the "single" directory include a connection string with one host -and no "replicaSet" option. -Set the client's initial TopologyType to Single, however that is achieved using the client's API. -(The spec says "The user MUST be able to set the initial TopologyType to Single" -without specifying how.) - -All files in the "sharded" directory include a connection string with multiple hosts -and no "replicaSet" option. -Set the client's initial TopologyType to Unknown or Sharded, depending on the client's API. - -All files in the "rs" directory include a connection string with a "replicaSet" option. -Set the client's initial TopologyType to ReplicaSetNoPrimary. -(For most clients, parsing a connection string with a "replicaSet" option -automatically sets the TopologyType to ReplicaSetNoPrimary.) - -Set up a listener to collect SDAM events published by the client, including -events published during client construction. - -Test Phases -~~~~~~~~~~~ - -For each phase in the file: - -#. Parse the "responses" array. Pass in the responses in order to the driver - code. If a response is the empty object ``{}``, simulate a network error. - -#. Parse the "applicationErrors" array. For each element, simulate the given - error as if it occurred while running an application operation. Note that - it is sufficient to construct a mock error and call the procedure which - updates the topology, e.g. - ``topology.handleApplicationError(address, generation, maxWireVersion, error)``. - -For non-monitoring tests, -once all responses are processed, assert that the phase's "outcome" object -is equivalent to the driver's current TopologyDescription. - -For monitoring tests, once all responses are processed, assert that the -events collected so far by the SDAM event listener are equivalent to the -events specified in the phase. - -Some fields such as "logicalSessionTimeoutMinutes", "compatible", and -"topologyVersion" were added later and haven't been added to all test files. -If these fields are present, test that they are equivalent to the fields of -the driver's current TopologyDescription or ServerDescription. - -For monitoring tests, clear the list of events collected so far. - -Continue until all phases have been executed. - -Integration Tests ------------------ - -Integration tests are provided in the "integration" directory. - -Test Format -~~~~~~~~~~~ - -The same as the `Transactions Spec Test format -`_ with the following -additions: - -- The ``runOn`` requirement gains a new field: - - - ``authEnabled`` (optional): If True, skip this test if auth is not enabled. - If False, skip this test if auth is enabled. If this field is omitted, - this test can be run on clusters with or without auth. - -Special Test Operations -~~~~~~~~~~~~~~~~~~~~~~~ - -Certain operations that appear in the "operations" array do not correspond to -API methods but instead represent special test operations. Such operations are -defined on the "testRunner" object and are documented in the -`Transactions Spec Test -`_. - -Additional, SDAM test specific operations are documented here: - -configureFailPoint -'''''''''''''''''' - -The "configureFailPoint" operation instructs the test runner to configure -the given server failpoint on the "admin" database. The runner MUST disable -this failpoint at the end of the test. For example:: - - - name: configureFailPoint - object: testRunner - arguments: - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - -Tests that use the "configureFailPoint" operation do not include -``configureFailPoint`` commands in their command expectations. Drivers MUST -ensure that ``configureFailPoint`` commands do not appear in the list of logged -commands, either by manually filtering it from the list of observed commands or -by using a different MongoClient to execute ``configureFailPoint``. - -Note, similar to the ``tests.failPoint`` field described in the `Transactions -Spec Test format `_ tests -with ``useMultipleMongoses: true`` will not contain a ``configureFailPoint`` -operation. - -wait -'''' - -The "wait" operation instructs the test runner to sleep for "ms" -milliseconds. For example:: - - - name: wait - object: testRunner - arguments: - ms: 1000 - -waitForEvent -'''''''''''' - -The "waitForEvent" operation instructs the test runner to wait until the test's -MongoClient has published a specific event a given number of times. For -example, the following instructs the test runner to wait for at least one -PoolClearedEvent to be published:: - - - name: waitForEvent - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - -Note that "count" includes events that were published while running previous -operations. - -If the "waitForEvent" operation is not satisfied after 10 seconds, the -operation is considered an error. - -ServerMarkedUnknownEvent -```````````````````````` - -The ServerMarkedUnknownEvent may appear as an event in `waitForEvent`_ and -`assertEventCount`_. This event is defined as ServerDescriptionChangedEvent -where newDescription.type is ``Unknown``. - -assertEventCount -'''''''''''''''' - -The "assertEventCount" operation instructs the test runner to assert the test's -MongoClient has published a specific event a given number of times. For -example, the following instructs the test runner to assert that a single -PoolClearedEvent was published:: - - - name: assertEventCount - object: testRunner - arguments: - event: PoolClearedEvent - count: 1 - -recordPrimary -''''''''''''' - -The "recordPrimary" operation instructs the test runner to record the current -primary of the test's MongoClient. For example:: - - - name: recordPrimary - object: testRunner - -runAdminCommand -''''''''''''''' - -The "runAdminCommand" operation instructs the test runner to run the given -command on the admin database. Drivers MUST run this command on a different -MongoClient from the one used for test operations. For example:: - - - name: runAdminCommand - object: testRunner - command_name: replSetFreeze - arguments: - command: - replSetFreeze: 0 - readPreference: - mode: Secondary - -waitForPrimaryChange -'''''''''''''''''''' - -The "waitForPrimaryChange" operation instructs the test runner to wait up to -"timeoutMS" milliseconds for the MongoClient to discover a new primary server. -The new primary should be different from the one recorded by "recordPrimary". -For example:: - - - name: waitForPrimaryChange - object: testRunner - arguments: - timeoutMS: 15000 - -To implement, Drivers can subscribe to ServerDescriptionChangedEvents and wait -for an event where newDescription.type is ``RSPrimary`` and the address is -different from the one previously recorded by "recordPrimary". - -startThread -''''''''''' - -The "startThread" operation instructs the test runner to start a new thread -with the provided "name". The `runOnThread`_ and `waitForThread`_ operations -reference a thread by its "name". For example:: - - - name: startThread - object: testRunner - arguments: - name: thread1 - -runOnThread -''''''''''' - -The "runOnThread" operation instructs the test runner to schedule an operation -to be run on the given thread. runOnThread MUST NOT wait for the scheduled -operation to complete. For example:: - - - name: runOnThread - object: testRunner - arguments: - name: thread1 - operation: - name: insertOne - object: collection - arguments: - document: - _id: 2 - error: true - -waitForThread -''''''''''''' - -The "waitForThread" operation instructs the test runner to stop the given -thread, wait for it to complete, and assert that the thread exited without -any errors. For example:: - - - name: waitForThread - object: testRunner - arguments: - name: thread1 - -Prose Tests ------------ - -The following prose tests cannot be represented as spec tests and MUST be -implemented. - -Streaming protocol Tests -~~~~~~~~~~~~~~~~~~~~~~~~ - -Drivers that implement the streaming protocol (multi-threaded or -asynchronous drivers) must implement the following tests. Each test should be -run against a standalone, replica set, and sharded cluster unless otherwise -noted. - -Some of these cases should already be tested with the old protocol; in -that case just verify the test cases succeed with the new protocol. - -1. Configure the client with heartbeatFrequencyMS set to 500, - overriding the default of 10000. Assert the client processes - hello and legacy hello replies more frequently (approximately every 500ms). - -RTT Tests -~~~~~~~~~ - -Run the following test(s) on MongoDB 4.4+. - -1. Test that RTT is continuously updated. - - #. Create a client with ``heartbeatFrequencyMS=500``, - ``appName=streamingRttTest``, and subscribe to server events. - - #. Run a find command to wait for the server to be discovered. - - #. Sleep for 2 seconds. This must be long enough for multiple heartbeats - to succeed. - - #. Assert that each ``ServerDescriptionChangedEvent`` includes a non-zero - RTT. - - #. Configure the following failpoint to block hello or legacy hello commands - for 250ms which should add extra latency to each RTT check:: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: {times: 1000}, - data: { - failCommands: ["hello"], // or the legacy hello command - blockConnection: true, - blockTimeMS: 500, - appName: "streamingRttTest", - }, - }); - - #. Wait for the server's RTT to exceed 250ms. Eventually the average RTT - should also exceed 500ms but we use 250ms to speed up the test. Note - that the `Server Description Equality`_ rule means that - ServerDescriptionChangedEvents will not be published. This test may - need to use a driver specific helper to obtain the latest RTT instead. - If the RTT does not exceed 250ms after 10 seconds, consider the test - failed. - - #. Disable the failpoint:: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: "off", - }); - -.. Section for links. - -.. _Server Description Equality: /source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#server-description-equality diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.md b/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.md new file mode 100644 index 000000000..fd463c48d --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.md @@ -0,0 +1,9 @@ +# SDAM Monitoring Tests + +The YAML and JSON files in this directory tree are platform-independent tests that drivers can use to prove their +conformance to the SDAM Monitoring spec. + +## Format + +The format of the tests follows the standard SDAM test and should be able to leverage the existing test runner in each +language for the SDAM tests. diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.rst b/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.rst deleted file mode 100644 index 7c741544e..000000000 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/README.rst +++ /dev/null @@ -1,12 +0,0 @@ -===================== -SDAM Monitoring Tests -===================== - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the SDAM Monitoring spec. - -Format ------- - -The format of the tests follows the standard SDAM test and should be able to leverage -the existing test runner in each language for the SDAM tests. diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.json index dd8f7fc51..097203694 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.yml index 5d808f260..1de96154c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/discovered_standalone.yml @@ -5,7 +5,7 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 21 } outcome: events: diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.json index 950e32efe..41d048729 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.json @@ -19,7 +19,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.yml index ce9cdf46e..e5ca9cd90 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_no_primary.yml @@ -17,7 +17,7 @@ phases: - "a:27017" - "b:27017" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: events: - diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.json index 2ad94d6e6..3ccc127d1 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.json @@ -18,7 +18,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.yml index 2c7891906..256895bb2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_primary.yml @@ -16,7 +16,7 @@ phases: - "a:27017" - "b:27017" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: events: - diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.json index ae28faa30..dc6fbe7e7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.json @@ -69,7 +69,7 @@ "a:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.yml index 2471f4768..25c006861 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/replica_set_with_removal.yml @@ -50,7 +50,7 @@ phases: primary: "a:27017", hosts: [ "a:27017" ], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 } - - "b:27017" diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.json index 401c5d99c..1f4e5c1d7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.json @@ -18,7 +18,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.yml index 7a060128f..69d0500de 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/required_replica_set.yml @@ -14,7 +14,7 @@ phases: primary: "a:27017", hosts: [ "a:27017", "b:27017" ], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 } outcome: events: diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.json index 821a1525d..f375a383c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.yml index d9f6bcfaf..0c3ed6460 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone.yml @@ -5,7 +5,7 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 21 } outcome: events: diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.json b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.json index 5958e2d26..4d046ff8e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.json +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -21,7 +21,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.yml b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.yml index f2f83ffba..255ec2dd4 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/monitoring/standalone_suppress_equal_description_changes.yml @@ -5,10 +5,10 @@ phases: responses: - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 21 } - - "a:27017" - - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 6 } + - { ok: 1, helloOk: true, isWritablePrimary: true, minWireVersion: 0, maxWireVersion: 21 } outcome: events: diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.json b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.json index 444b13e9d..dfd5d57df 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.yml index 8b5d9f456..ce17d7e30 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible.yml @@ -12,7 +12,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.json b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.json index cf92dd1ed..95e03ea95 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.yml index 643e84981..ed97ab31a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/compatible_unknown.yml @@ -12,7 +12,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.json index 53709b0ce..803462b15 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.yml index 67d29eadb..5334bc824 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters.yml @@ -16,7 +16,7 @@ phases: [ arbiters: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.json index 64fb49f4f..e58d7c7fb 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.yml index c7edb52b0..882dbb0f8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_arbiters_replicaset.yml @@ -16,7 +16,7 @@ phases: [ arbiters: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.json index 2e24c83e0..3b7fc836e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.json @@ -12,7 +12,7 @@ "isWritablePrimary": false, "isreplicaset": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.yml index e613f684b..7a9cbd555 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: false, isreplicaset: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.json index cf5fe83a5..1a8457983 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.json @@ -12,7 +12,7 @@ "isWritablePrimary": false, "isreplicaset": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.yml index 75ec3e64a..61ba1eab5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_ghost_replicaset.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: false, isreplicaset: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.json index e4a90f1f9..10bd51ede 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.yml index 64ed1d82b..f48325580 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden.yml @@ -17,7 +17,7 @@ phases: [ hosts: ["c:27017", "d:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.json index 04420596f..63cf55867 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.yml index 614a39d48..8d496544d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_hidden_replicaset.yml @@ -17,7 +17,7 @@ phases: [ hosts: ["c:27017", "d:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.json index 30258409f..0a292c675 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -56,7 +56,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.yml index 637d86d7f..25419733c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives.yml @@ -16,7 +16,7 @@ phases: [ passives: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -55,7 +55,7 @@ phases: [ passives: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.json index 266eaa523..c48fd4762 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -56,7 +56,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.yml index a5ced995d..d981280a2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_passives_replicaset.yml @@ -16,7 +16,7 @@ phases: [ passives: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -55,7 +55,7 @@ phases: [ passives: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.json index 2d1292bbd..04e7a4984 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.yml index eaedf130b..3c11e3e4f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.json index 54dfefba5..3cdcfdcee 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.yml index 7879701bb..47831fa60 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_primary_replicaset.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.json index 4ab25667f..9c3b8d8b7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.json @@ -17,7 +17,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.yml index d78e70c5d..11c65863f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["c:27017", "d:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.json index e3958d70a..3da9efb06 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -34,7 +34,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.yml index 19159d1ef..c25d33ab2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_rsother_replicaset.yml @@ -17,7 +17,7 @@ phases: [ hosts: ["c:27017", "d:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -28,7 +28,7 @@ phases: [ hosts: ["c:27017", "d:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.json index 22325d4e0..64a1ce31e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.json @@ -17,7 +17,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.yml index 184849d3f..83af822ed 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary.yml @@ -16,7 +16,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.json index d903b6444..d230f976a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.json @@ -17,7 +17,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.yml index c73a535f6..71229387c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discover_secondary_replicaset.yml @@ -16,7 +16,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.json b/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.json index 50e126922..e9deaa758 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.json @@ -18,7 +18,7 @@ "c:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -59,7 +59,7 @@ "d:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -103,7 +103,7 @@ "e:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -147,7 +147,7 @@ "c:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.yml index 30c82e0ce..19be04897 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/discovery.yml @@ -17,7 +17,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017", "c:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -63,7 +63,7 @@ phases: [ primary: "d:27017", hosts: ["b:27017", "c:27017", "d:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -113,7 +113,7 @@ phases: [ setName: "rs", hosts: ["b:27017", "c:27017", "d:27017", "e:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -165,7 +165,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017", "c:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json b/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json index a7b49e2b9..2fcea2bf6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ], [ @@ -39,7 +39,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ], [ @@ -58,7 +58,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml index e552e984c..4b9563fcf 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/electionId_precedence_setVersion.yml @@ -14,7 +14,7 @@ phases: electionId: $oid: "000000000000000000000001" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 - - "b:27017" - ok: 1 helloOk: true @@ -27,7 +27,7 @@ phases: electionId: $oid: "000000000000000000000001" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 - - "a:27017" - ok: 1 helloOk: true @@ -40,7 +40,7 @@ phases: electionId: $oid: "000000000000000000000002" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 outcome: servers: "a:27017": diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.json b/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.json index 17df3207f..f1deedf9f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -39,7 +39,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.yml index 48bb683d6..17e8aa2c1 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/equal_electionids.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, @@ -27,7 +27,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.json b/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.json index 4e02304c6..085e81e26 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.json @@ -15,7 +15,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.yml index e97fbca05..2ecd27ec7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/hosts_differ_from_seeds.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.json b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.json index f0539cb33..bda18d9f6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.yml index e4928f191..2e46ff295 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_arbiter.yml @@ -12,7 +12,7 @@ phases: setName: "rs" hosts: ["a:27017", "b:27017"] minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 - - "b:27017" - ok: 1 diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.json b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.json index 824e953f9..9d82e3168 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.yml index da1db790f..c99badf4a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_ghost.yml @@ -12,7 +12,7 @@ phases: setName: "rs" hosts: ["a:27017", "b:27017"] minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 - - "b:27017" - ok: 1 diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.json b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.json index 6f301ef5d..149ba0114 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.yml index 98061c0a1..16452e7b0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/incompatible_other.yml @@ -12,7 +12,7 @@ phases: setName: "rs" hosts: ["a:27017", "b:27017"] minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 - - "b:27017" - ok: 1 diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.json b/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.json index 96389d3b7..c68790ddf 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.json @@ -20,7 +20,7 @@ "setName": "rs", "logicalSessionTimeoutMinutes": 3, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -58,7 +58,7 @@ "isWritablePrimary": false, "isreplicaset": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -104,7 +104,7 @@ "setName": "rs", "arbiterOnly": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -152,7 +152,7 @@ "setName": "rs", "logicalSessionTimeoutMinutes": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -194,7 +194,7 @@ "hidden": true, "logicalSessionTimeoutMinutes": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -244,7 +244,7 @@ "setName": "rs", "logicalSessionTimeoutMinutes": null, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.yml index 26679f339..2cea00e99 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/ls_timeout.yml @@ -14,7 +14,7 @@ phases: [ setName: "rs", logicalSessionTimeoutMinutes: 3, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], outcome: { @@ -51,7 +51,7 @@ phases: [ isWritablePrimary: false, isreplicaset: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], outcome: { @@ -90,7 +90,7 @@ phases: [ setName: "rs", arbiterOnly: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -131,7 +131,7 @@ phases: [ setName: "rs", logicalSessionTimeoutMinutes: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], outcome: { @@ -172,7 +172,7 @@ phases: [ hidden: true, logicalSessionTimeoutMinutes: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], outcome: { @@ -214,7 +214,7 @@ phases: [ setName: "rs", logicalSessionTimeoutMinutes: null, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.json b/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.json index 0e2c2c462..a05fed0ef 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -49,7 +49,7 @@ "a:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.yml index 37e63bc05..a43a88d53 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/member_reconfig.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -52,7 +52,7 @@ phases: [ setName: "rs", hosts: ["a:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.json index 0756003a8..db100db9f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -40,7 +40,7 @@ "a:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.yml index 50c005665..1be000ea4 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/member_standalone.yml @@ -13,7 +13,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -44,7 +44,7 @@ phases: [ setName: "rs", hosts: ["a:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.json b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.json index ed1a6245f..69b07516b 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -50,7 +50,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -58,7 +58,8 @@ "servers": { "a:27017": { "type": "Unknown", - "setName": null + "setName": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.yml index 736dd06c5..50c996f52 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -52,7 +52,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -63,7 +63,8 @@ phases: [ "a:27017": { type: "Unknown", - setName: + setName:, + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.json index ccb3a41f7..90ef0ce8d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -67,7 +67,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -76,7 +76,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", @@ -114,7 +115,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -123,7 +124,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to electionId/setVersion mismatch" }, "b:27017": { "type": "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.yml index dfebbd856..6418301c0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_electionid.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -54,7 +54,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -63,7 +63,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId: , + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { type: "RSPrimary", @@ -92,7 +93,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -100,7 +101,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to electionId/setVersion mismatch" }, "b:27017": { type: "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.json b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.json index 415a0f66a..9c1e2d4bd 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -67,7 +67,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -76,7 +76,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", @@ -114,7 +115,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -123,7 +124,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to electionId/setVersion mismatch" }, "b:27017": { "type": "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.yml index 3ebc798b6..7abf69a8c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_new_setversion.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -54,7 +54,7 @@ phases: [ setVersion: 2, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -63,7 +63,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { type: "RSPrimary", @@ -92,7 +93,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -100,7 +101,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to electionId/setVersion mismatch" }, "b:27017": { type: "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.json index d7b19cfe8..774b3a573 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -49,7 +49,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.yml index ca6303cda..dc6df9cd8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/new_primary_wrong_set_name.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -55,7 +55,7 @@ phases: [ hosts: ["a:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.json b/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.json index 538077ef0..6bf10bd62 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.json @@ -10,7 +10,7 @@ "ok": 1, "helloOk": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.yml index c18b27ee3..4e2f0b1be 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/non_rs_member.yml @@ -11,7 +11,7 @@ phases: [ ok: 1, helloOk: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.json b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.json index 96a944f0c..62915495e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.json @@ -21,7 +21,7 @@ "C:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.yml index d8003ee37..a543ab72a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case.yml @@ -17,7 +17,7 @@ phases: [ passives: ["B:27017"], arbiters: ["C:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.json b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.json index ab1720cef..0d9ba6213 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.json @@ -22,7 +22,7 @@ "C:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -67,7 +67,7 @@ "C:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.yml index a0df3351a..03ac43f31 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/normalize_case_me.yml @@ -18,7 +18,7 @@ phases: [ passives: ["B:27017"], arbiters: ["C:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -66,7 +66,7 @@ phases: [ passives: ["B:27017"], arbiters: ["C:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json new file mode 100644 index 000000000..8a77f31c5 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.json @@ -0,0 +1,203 @@ +{ + "description": "Pre 6.0 Primaries with and without electionIds", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setVersion": 1, + "setName": "rs", + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1 + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + }, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + } + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setVersion": 1, + "setName": "rs", + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + }, + { + "responses": [ + [ + "c:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017", + "c:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + }, + "c:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000002" + } + } + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml new file mode 100644 index 000000000..5313a8adf --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id-pre-6.0.yml @@ -0,0 +1,175 @@ +description: "Pre 6.0 Primaries with and without electionIds" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has no electionId. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + } + }, + + # B is elected, it has an electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"}, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId: + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"} + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # A still claims to be primary, no electionId, we have to trust it. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setVersion: 1, + setName: "rs", + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + }, + + # But we remember B's electionId, so when we finally hear from C + # claiming it is primary, we ignore it due to its outdated electionId + { + responses: [ + ["c:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017", "c:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"}, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + outcome: { + servers: { + # Still primary. + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + }, + "c:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000002"}, + } + } +] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.json b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.json index 8eb519595..8a99a7847 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.json @@ -18,7 +18,7 @@ "setVersion": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -66,7 +66,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -116,7 +116,7 @@ "setVersion": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -170,7 +170,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.yml index 9377285e4..54c1af272 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/null_election_id.yml @@ -15,7 +15,7 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -57,7 +57,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -99,7 +99,7 @@ phases: [ setVersion: 1, setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { @@ -144,7 +144,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.json index 9c54b3985..e34280e88 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -41,7 +41,7 @@ "isWritablePrimary": false, "isreplicaset": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.yml index dbf5b9289..ee0158db0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_ghost.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -43,7 +43,7 @@ phases: [ isWritablePrimary: false, isreplicaset: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.json index ac416e57d..79510d939 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -41,7 +41,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.yml index 6cdb07710..251f15527 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_mongos.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -43,7 +43,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.json index a64524d0c..abcc1e2d0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -38,7 +38,7 @@ { "ok": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.yml index abcc7fcfe..9f6a0817b 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_becomes_standalone.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -40,7 +40,7 @@ phases: [ ["a:27017", { ok: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.json index bf70ca301..3b564d2c9 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -44,7 +44,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.yml index 00ed1c0a1..e49aa2491 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_changes_set_name.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -48,7 +48,7 @@ phases: [ hosts: ["a:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.json index 3db854f08..73a01a82a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.yml index 9a4965654..84c88daa6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.json index 3a80b150f..b030bd2c5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -39,7 +39,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -48,7 +48,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", @@ -115,7 +116,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -124,6 +125,7 @@ "a:27017": { "type": "Unknown", "setName": null, + "error": "primary marked stale due to electionId/setVersion mismatch", "electionId": null }, "b:27017": { @@ -159,7 +161,7 @@ "$oid": "000000000000000000000003" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -203,7 +205,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.yml index 0d7d294f9..4ee861201 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_electionid.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, @@ -27,7 +27,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -36,7 +36,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { type: "RSPrimary", @@ -91,7 +92,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -99,6 +100,7 @@ phases: [ "a:27017": { type: "Unknown", setName: , + error: "primary marked stale due to electionId/setVersion mismatch", electionId: }, "b:27017": { @@ -127,7 +129,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000003"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -163,7 +165,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.json index 32e03fb7d..653a5f29e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -39,7 +39,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -48,7 +48,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", @@ -115,7 +116,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -124,6 +125,7 @@ "a:27017": { "type": "Unknown", "setName": null, + "error": "primary marked stale due to electionId/setVersion mismatch", "electionId": null }, "b:27017": { @@ -159,7 +161,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -203,7 +205,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.yml index 41f2f8d7b..bc6c538e9 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_disconnect_setversion.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, @@ -27,7 +27,7 @@ phases: [ setVersion: 2, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -36,7 +36,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { type: "RSPrimary", @@ -91,7 +92,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -99,6 +100,7 @@ phases: [ "a:27017": { type: "Unknown", setName: , + error: "primary marked stale due to electionId/setVersion mismatch", electionId: }, "b:27017": { @@ -127,7 +129,7 @@ phases: [ setVersion: 2, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { @@ -163,7 +165,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.json index bc02cc957..1ca72225a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.json @@ -18,7 +18,7 @@ "setName": "rs", "primary": "b:27017", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -48,7 +48,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.yml index f5b536c01..cebbe7d08 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_hint_from_secondary_with_mismatched_me.yml @@ -17,7 +17,7 @@ phases: [ setName: "rs", primary: "b:27017", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -45,7 +45,7 @@ phases: [ hosts: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.json index 2d2c0f40d..6bb6226f8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.json @@ -31,7 +31,7 @@ "ok": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.yml index c24fd1a99..25ca1722e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me.yml @@ -22,6 +22,6 @@ phases: ok: 1 setName: rs minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 uri: 'mongodb://localhost:27017/?replicaSet=rs' diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.json index 4c4009365..a55dcfc6d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.json @@ -18,7 +18,7 @@ "primary": "localhost:27017", "me": "a:27017", "minWireVersion": 0, - "maxWireVersion": 7 + "maxWireVersion": 25 } ] ], @@ -55,7 +55,7 @@ "primary": "localhost:27017", "me": "localhost:27018", "minWireVersion": 0, - "maxWireVersion": 7 + "maxWireVersion": 25 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.yml index 5b7e92e60..ae0c3bb50 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_mismatched_me_not_removed.yml @@ -19,7 +19,7 @@ phases: [ # servers from a primary isWritablePrimary are added to the working server set me: "a:27017", minWireVersion: 0, - maxWireVersion: 7 + maxWireVersion: 25 }] ], outcome: { @@ -53,7 +53,7 @@ phases: [ primary: "localhost:27017", me: "localhost:27018", minWireVersion: 0, - maxWireVersion: 7 + maxWireVersion: 25 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.json index ac0d9374f..ed28c48c8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.json @@ -17,7 +17,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -51,7 +51,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -86,7 +86,7 @@ "c:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -127,7 +127,7 @@ "c:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.yml index de3f528fe..aa2878d63 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_reports_new_member.yml @@ -17,7 +17,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -55,7 +55,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -93,7 +93,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017", "c:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -140,7 +140,7 @@ phases: [ primary: "b:27017", hosts: ["a:27017", "b:27017", "c:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.json index 6dbd73dad..798a648d1 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.json @@ -17,7 +17,7 @@ "me": "a:27017", "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -52,7 +52,7 @@ "me": "c:27017", "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.yml index a78982c28..e8bdb00f0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_to_no_primary_mismatched_me.yml @@ -16,7 +16,7 @@ phases: [ me: "a:27017", setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -53,7 +53,7 @@ phases: [ me : "c:27017", setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.json index cc0691fb8..1366e3899 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.json @@ -15,7 +15,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.yml index d4434522a..3e463333e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/primary_wrong_set_name.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.json b/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.json index 610aeae0a..3ce0948ab 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.json @@ -18,7 +18,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -49,7 +49,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -84,7 +84,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -120,7 +120,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.yml index f651005ba..b9e14ed98 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/repeated.yml @@ -15,7 +15,7 @@ phases: hosts: ["a:27017", "c:27017"] setName: "rs" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: "a:27017": @@ -39,7 +39,7 @@ phases: helloOk: true isWritablePrimary: true minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: "a:27017": @@ -64,7 +64,7 @@ phases: hosts: ["a:27017", "c:27017"] setName: "rs" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: "a:27017": @@ -90,7 +90,7 @@ phases: hosts: ["a:27017", "c:27017"] setName: "rs" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: "a:27017": diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.json b/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.json index 3148e1c14..1cd732b82 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.yml index 87e80bdb3..6bdf8cbe2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/replicaset_rsnp.yml @@ -11,7 +11,7 @@ phases: helloOk: true isWritablePrimary: true minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: # Server is removed because it's a standalone and the driver # started in RSNP topology diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.json b/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.json index 87a66d9e7..fa46a14ce 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.json @@ -15,7 +15,7 @@ "a:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -46,7 +46,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.yml index 6ec66c875..fc9961ce3 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/response_from_removed.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -47,7 +47,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.json b/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.json index a39855e65..ccbe7a08a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -32,7 +32,7 @@ "c:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.yml index 09c75f9c7..507eb9d29 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/sec_not_auth.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -27,7 +27,7 @@ phases: [ setName: "rs", hosts: ["b:27017", "c:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json new file mode 100644 index 000000000..f27060533 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.json @@ -0,0 +1,83 @@ +{ + "description": "Pre 6.0 New primary", + "uri": "mongodb://a,b/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "setName": "rs", + "hosts": [ + "a:27017", + "b:27017" + ], + "minWireVersion": 0, + "maxWireVersion": 21 + } + ], + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "setName": "rs", + "hosts": [ + "a:27017", + "b:27017" + ], + "minWireVersion": 0, + "maxWireVersion": 21 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs" + }, + "b:27017": { + "type": "RSSecondary", + "setName": "rs" + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs" + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 0, + "minWireVersion": 0, + "maxWireVersion": 21 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs" + }, + "b:27017": { + "type": "Unknown", + "setName": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs" + } + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml new file mode 100644 index 000000000..b63c43f22 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0-pre-6.0.yml @@ -0,0 +1,87 @@ +description: "Pre 6.0 New primary" + +uri: "mongodb://a,b/?replicaSet=rs" + +phases: [ + + { + responses: [ + + ["a:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 21 + }], + ["b:27017", { + + ok: 1, + helloOk: true, + isWritablePrimary: false, + secondary: true, + setName: "rs", + hosts: ["a:27017", "b:27017"], + minWireVersion: 0, + maxWireVersion: 21 + }] + ], + + outcome: { + + servers: { + + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "RSSecondary", + setName: "rs" + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + }, + + { + responses: [ + + ["b:27017", { + + ok: 0, + minWireVersion: 0, + maxWireVersion: 21 + }] + ], + + outcome: { + + servers: { + "a:27017": { + + type: "RSPrimary", + setName: "rs" + }, + + "b:27017": { + + type: "Unknown", + setName: + } + + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs" + } + } +] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.json index ee9519930..9ffff58ef 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -32,7 +32,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -59,7 +59,7 @@ { "ok": 0, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.yml index d94fafaf9..796e7f668 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ignore_ok_0.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -26,7 +26,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -59,7 +59,7 @@ phases: [ ok: 0, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.json new file mode 100644 index 000000000..c23d8dc4c --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.json @@ -0,0 +1,38 @@ +{ + "description": "Secondary with IPv6 literal", + "uri": "mongodb://[::1]/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "[::1]:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": false, + "secondary": true, + "setName": "rs", + "me": "[::1]:27017", + "hosts": [ + "[::1]:27017" + ], + "minWireVersion": 0, + "maxWireVersion": 26 + } + ] + ], + "outcome": { + "servers": { + "[::1]:27017": { + "type": "RSSecondary", + "setName": "rs" + } + }, + "topologyType": "ReplicaSetNoPrimary", + "setName": "rs", + "logicalSessionTimeoutMinutes": null, + "compatible": true + } + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.yml new file mode 100644 index 000000000..bac349696 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_ipv6_literal.yml @@ -0,0 +1,25 @@ +# Regression test for bug discovered in HELP-68823. +description: Secondary with IPv6 literal +uri: mongodb://[::1]/?replicaSet=rs +phases: +- responses: + - - "[::1]:27017" + - ok: 1 + helloOk: true + isWritablePrimary: false + secondary: true + setName: rs + me: "[::1]:27017" + hosts: + - "[::1]:27017" + minWireVersion: 0 + maxWireVersion: 26 + outcome: + servers: + "[::1]:27017": + type: RSSecondary + setName: rs + topologyType: ReplicaSetNoPrimary + setName: rs + logicalSessionTimeoutMinutes: null + compatible: true diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.json index 6f1b9b598..790e4bfca 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.json @@ -32,7 +32,7 @@ "ok": 1, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.yml index e5f0f9ace..d359609d8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_mismatched_me.yml @@ -25,4 +25,4 @@ phases: ok: 1 setName: rs minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.json index 8d2f152f5..1f86b5054 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.json @@ -16,7 +16,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.yml index 0121d631c..60823815f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.json b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.json index b7ef2d6d6..6b8991415 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -51,7 +51,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.yml index acd471e78..8d1990363 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/secondary_wrong_set_name_with_primary.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -53,7 +53,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.json b/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.json index 6a7ca023a..1cc608a34 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -67,7 +67,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -114,7 +114,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.yml index 3b6475d27..c30a8660e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/set_version_can_rollback.yml @@ -14,7 +14,7 @@ phases: electionId: $oid: '000000000000000000000001' minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 outcome: servers: a:27017: @@ -47,7 +47,7 @@ phases: electionId: $oid: '000000000000000000000002' minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 outcome: servers: a:27017: @@ -70,7 +70,7 @@ phases: responses: - - a:27017 - ok: 1 - hello: true + helloOk: true isWritablePrimary: true hosts: - a:27017 @@ -80,7 +80,7 @@ phases: electionId: $oid: '000000000000000000000001' minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 17 outcome: servers: a:27017: diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json index 91e84d4fa..3669511c5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.json @@ -17,7 +17,7 @@ "setName": "rs", "setVersion": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -56,7 +56,7 @@ "setName": "rs", "setVersion": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml index cdce70f03..3733a12e5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_equal_max_without_electionid.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -51,7 +51,7 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json index b15fd5c1a..06c89609f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.json @@ -17,7 +17,7 @@ "setName": "rs", "setVersion": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -56,7 +56,7 @@ "setName": "rs", "setVersion": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -65,7 +65,8 @@ "a:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" }, "b:27017": { "type": "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml index 34150c442..622597809 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_greaterthan_max_without_electionid.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -52,7 +52,7 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -61,7 +61,8 @@ phases: [ "a:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to discovery of newer primary" }, "b:27017": { type: "RSPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json new file mode 100644 index 000000000..9a1ee6139 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.json @@ -0,0 +1,85 @@ +{ + "description": "Pre 6.0 setVersion is ignored if there is no electionId", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 2, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 2, + "electionId": null + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2 + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2 + } + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml new file mode 100644 index 000000000..41a89e0fb --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,80 @@ +description: "Pre 6.0 setVersion is ignored if there is no electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A is discovered and tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 , + electionId: + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + }, + + # B is elected, its setVersion is older but we believe it anyway, because + # setVersion is only used in conjunction with electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId:, + error: "primary marked stale due to discovery of newer primary" + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + } + } +] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.json index f59c162ae..256fafe10 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.json @@ -17,7 +17,7 @@ "setName": "rs", "setVersion": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -56,7 +56,7 @@ "setName": "rs", "setVersion": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.yml index ea8f28c23..04992929a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/setversion_without_electionid.yml @@ -15,7 +15,7 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -51,7 +51,7 @@ phases: [ setName: "rs", setVersion: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.json index e9075f97f..6de995518 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -45,7 +45,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.yml index 9c4140925..2e1c1a551 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/stepdown_change_set_name.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -50,7 +50,7 @@ phases: [ hosts: ["a:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.json b/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.json index 0433d27a3..696246f8e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.yml index 52912826b..121286554 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/too_new.yml @@ -12,7 +12,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.json b/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.json index 461d00acc..dc8a5b2b9 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.json @@ -16,7 +16,7 @@ "b:27017" ], "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -30,7 +30,9 @@ "hosts": [ "a:27017", "b:27017" - ] + ], + "minWireVersion": 999, + "maxWireVersion": 1000 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.yml index ab238dbab..f4d4bd8b5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/too_old.yml @@ -10,7 +10,7 @@ phases: [ setName: "rs", hosts: ["a:27017", "b:27017"], minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, @@ -18,7 +18,9 @@ phases: [ isWritablePrimary: false, secondary: true, setName: "rs", - hosts: ["a:27017", "b:27017"] + hosts: ["a:27017", "b:27017"], + minWireVersion: 999, + maxWireVersion: 1000 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.json b/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.json index cc19a961f..c6ffb321c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.yml index d0d8547f9..a9c5a24ee 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/unexpected_mongos.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json new file mode 100644 index 000000000..03195aacd --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.json @@ -0,0 +1,140 @@ +{ + "description": "Pre 6.0 Record max setVersion, even from primary without electionId", + "uri": "mongodb://a/?replicaSet=rs", + "phases": [ + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + }, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000001" + } + }, + "b:27017": { + "type": "Unknown", + "setName": null, + "electionId": null + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 1, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "b:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 2, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "electionId": null, + "error": "primary marked stale due to discovery of newer primary" + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 2 + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + }, + { + "responses": [ + [ + "a:27017", + { + "ok": 1, + "helloOk": true, + "isWritablePrimary": true, + "hosts": [ + "a:27017", + "b:27017" + ], + "setName": "rs", + "setVersion": 1, + "electionId": { + "$oid": "000000000000000000000002" + }, + "minWireVersion": 0, + "maxWireVersion": 16 + } + ] + ], + "outcome": { + "servers": { + "a:27017": { + "type": "Unknown", + "setName": null, + "electionId": null, + "error": "primary marked stale due to electionId/setVersion mismatch" + }, + "b:27017": { + "type": "RSPrimary", + "setName": "rs", + "setVersion": 2 + } + }, + "topologyType": "ReplicaSetWithPrimary", + "logicalSessionTimeoutMinutes": null, + "setName": "rs", + "maxSetVersion": 2, + "maxElectionId": { + "$oid": "000000000000000000000001" + } + } + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml new file mode 100644 index 000000000..4849b01fc --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid-pre-6.0.yml @@ -0,0 +1,119 @@ +description: "Pre 6.0 Record max setVersion, even from primary without electionId" + +uri: "mongodb://a/?replicaSet=rs" + +phases: [ + + # Primary A has setVersion and electionId, tells us about B. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"}, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000001"} + }, + "b:27017": { + type: "Unknown", + setName: , + electionId: + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 1, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Reconfig the set and elect B, it has a new setVersion but no electionId. + { + responses: [ + ["b:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 2, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId:, + error: "primary marked stale due to discovery of newer primary" + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + }, + + # Delayed response from A, reporting its reelection. Its setVersion shows + # the election preceded B's so we ignore it. + { + responses: [ + ["a:27017", { + ok: 1, + helloOk: true, + isWritablePrimary: true, + hosts: ["a:27017", "b:27017"], + setName: "rs", + setVersion: 1, + electionId: {"$oid": "000000000000000000000002"}, + minWireVersion: 0, + maxWireVersion: 16 + }] + ], + outcome: { + servers: { + "a:27017": { + type: "Unknown", + setName: , + electionId:, + error: "primary marked stale due to electionId/setVersion mismatch" + }, + "b:27017": { + type: "RSPrimary", + setName: "rs", + setVersion: 2 + } + }, + topologyType: "ReplicaSetWithPrimary", + logicalSessionTimeoutMinutes: null, + setName: "rs", + maxSetVersion: 2, + maxElectionId: {"$oid": "000000000000000000000001"}, + } + } +] diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json index 6dd753d5d..eaf586d72 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.json @@ -20,7 +20,7 @@ "$oid": "000000000000000000000001" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -64,7 +64,7 @@ "setName": "rs", "setVersion": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -81,7 +81,8 @@ "b:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to electionId/setVersion mismatch" } }, "topologyType": "ReplicaSetWithPrimary", @@ -111,7 +112,7 @@ "$oid": "000000000000000000000002" }, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 17 } ] ], @@ -128,7 +129,8 @@ "b:27017": { "type": "Unknown", "setName": null, - "electionId": null + "electionId": null, + "error": "primary marked stale due to electionId/setVersion mismatch" } }, "topologyType": "ReplicaSetWithPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml index 669ec20d4..5359a1f67 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/use_setversion_without_electionid.yml @@ -16,7 +16,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000001"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -53,7 +53,7 @@ phases: [ setName: "rs", setVersion: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], @@ -68,7 +68,8 @@ phases: [ "b:27017": { type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to electionId/setVersion mismatch" } }, topologyType: "ReplicaSetWithPrimary", @@ -92,7 +93,7 @@ phases: [ setVersion: 1, electionId: {"$oid": "000000000000000000000002"}, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 17 }] ], outcome: { @@ -106,7 +107,8 @@ phases: [ "b:27017":{ type: "Unknown", setName: , - electionId: + electionId:, + error: "primary marked stale due to electionId/setVersion mismatch" } }, topologyType: "ReplicaSetWithPrimary", diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.json index 9654ff7b7..d0764d24d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.json @@ -17,7 +17,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.yml index ae75d6f7e..e7d3ace92 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/rs/wrong_set_name.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["b:27017", "c:27017"], setName: "wrong", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.json index e531db97f..ceb0ec24c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.json @@ -23,7 +23,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.yml index 06d5182a5..20519089f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/compatible.yml @@ -17,7 +17,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.json index 9e877a084..bf7e57521 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.yml index f44a2970d..1e8adc6c5 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/discover_single_mongos.yml @@ -13,7 +13,7 @@ phases: isWritablePrimary: true msg: "isdbgrid" minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.json index 93fa398d5..3da0f84ca 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.json @@ -13,7 +13,7 @@ "msg": "isdbgrid", "logicalSessionTimeoutMinutes": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -25,7 +25,7 @@ "msg": "isdbgrid", "logicalSessionTimeoutMinutes": 2, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -56,7 +56,7 @@ "msg": "isdbgrid", "logicalSessionTimeoutMinutes": 1, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -67,7 +67,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.yml index 7f78d0d6c..62ae97f05 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/ls_timeout_mongos.yml @@ -15,7 +15,7 @@ phases: [ msg: "isdbgrid", logicalSessionTimeoutMinutes: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -26,7 +26,7 @@ phases: [ msg: "isdbgrid", logicalSessionTimeoutMinutes: 2, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -63,7 +63,7 @@ phases: [ msg: "isdbgrid", logicalSessionTimeoutMinutes: 1, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -73,7 +73,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.json index 50a93eda5..29b335186 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -23,7 +23,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -76,7 +76,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.yml index c4393d85b..c384b3be1 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/mongos_disconnect.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -24,7 +24,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], @@ -85,7 +85,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.json index 311592d71..ae0c2d9cd 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -23,7 +23,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.yml index 0a49d6424..6311605a8 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/multiple_mongoses.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -24,7 +24,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.json index d74375ebb..4698f576d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -26,7 +26,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.yml index ab25349bd..aa604f110 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/non_mongos_removed.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { @@ -25,7 +25,7 @@ phases: [ hosts: ["b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.json index 4b997d216..c4e984dde 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.json @@ -21,7 +21,9 @@ "ok": 1, "helloOk": true, "isWritablePrimary": true, - "msg": "isdbgrid" + "msg": "isdbgrid", + "minWireVersion": 7, + "maxWireVersion": 900 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.yml index 7d59cb430..03032a494 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_new.yml @@ -15,7 +15,9 @@ phases: [ ok: 1, helloOk: true, isWritablePrimary: true, - msg: "isdbgrid" + msg: "isdbgrid", + minWireVersion: 7, + maxWireVersion: 900 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.json b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.json index 688e1db0f..b918715ad 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.json +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 2, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ diff --git a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.yml b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.yml index 925a8f55b..a709934c2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/sharded/too_old.yml @@ -9,7 +9,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 2, - maxWireVersion: 6 + maxWireVersion: 21 }], ["b:27017", { ok: 1, diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/compatible.json b/src/test/spec/json/server-discovery-and-monitoring/single/compatible.json index 302927598..493d9b748 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/compatible.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/compatible.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/compatible.yml b/src/test/spec/json/server-discovery-and-monitoring/single/compatible.yml index b84c1388c..e183c7127 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/compatible.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/compatible.yml @@ -8,7 +8,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.json index 90676a8f9..1461b4c46 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.json @@ -15,7 +15,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.yml index 18c01226a..0cb73c50e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_external_ip.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["b:27017"], # Internal IP. setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.json index 25fe96518..72be02086 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "msg": "isdbgrid", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.yml index 853ce57c1..e81c9aef9 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_mongos.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, msg: "isdbgrid", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.json index cd8660888..82a51d390 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.yml index 21e565fd9..9a0f3996c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_replicaset.yml @@ -12,7 +12,7 @@ phases: isWritablePrimary: true setName: rs minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: "a:27017": diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.json index e20495605..e06d28436 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.json @@ -17,7 +17,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.yml index 7e262c9db..d9fa87665 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsarbiter.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.json index 409e8502b..45eb1602f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.json @@ -16,7 +16,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.yml index 4ea0b1551..85dcb30a7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rsprimary.yml @@ -15,7 +15,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.json index 305f283b5..b1bef8a49 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.json @@ -17,7 +17,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.yml index b0c4d1f21..2d7da65e4 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_rssecondary.yml @@ -16,7 +16,7 @@ phases: [ hosts: ["a:27017", "b:27017"], setName: "rs", minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.json index b47278482..e71ba07e7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.yml index cd71087e4..ab4cb02ad 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_standalone.yml @@ -13,7 +13,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.json b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.json index 71080e681..8014a0a53 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.json @@ -16,7 +16,7 @@ ], "setName": "wrong", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], @@ -45,7 +45,7 @@ ], "setName": "rs", "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.yml b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.yml index f1e48dc41..46b476d04 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/direct_connection_wrong_set_name.yml @@ -11,7 +11,7 @@ phases: - b:27017 setName: wrong minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: a:27017: @@ -29,7 +29,7 @@ phases: - b:27017 setName: rs minWireVersion: 0 - maxWireVersion: 6 + maxWireVersion: 21 outcome: servers: a:27017: diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.json index 858cbdaf6..d78c81654 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.yml index 49ebb1420..bc112d4ea 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/discover_standalone.yml @@ -13,7 +13,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.json b/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.json index 87b3e4e8a..236eabe00 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.json @@ -12,7 +12,7 @@ "isWritablePrimary": true, "logicalSessionTimeoutMinutes": 7, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.yml index 2926d95a9..4747af677 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/ls_timeout_standalone.yml @@ -14,7 +14,7 @@ phases: [ isWritablePrimary: true, logicalSessionTimeoutMinutes: 7, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.json b/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.json index 8e7c2a10e..cfaac3564 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ], [ @@ -21,7 +21,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.yml b/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.yml index 64103a590..c1ae7d987 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/not_ok_response.yml @@ -13,7 +13,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }], ["a:27017", { @@ -22,7 +22,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.json b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.json index 57f8f861b..675cdbb00 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.json @@ -11,7 +11,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.yml b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.yml index 59b44b162..c8404463f 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_removed.yml @@ -13,7 +13,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.json b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.json index 46660fa8d..488cac491 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.json @@ -10,7 +10,7 @@ "ok": 1, "ismaster": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.yml b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.yml index a18ecb70f..82c6ccfa7 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/standalone_using_legacy_hello.yml @@ -12,7 +12,7 @@ phases: [ ok: 1, ismaster: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.json b/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.json index 58ae7d9de..c3dd98cf6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.json +++ b/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.json @@ -1,5 +1,5 @@ { - "description": "Standalone with default maxWireVersion of 0 is upgraded to one with maxWireVersion 6", + "description": "Standalone with default maxWireVersion of 0 is upgraded to one with maxWireVersion 21", "uri": "mongodb://a", "phases": [ { @@ -35,7 +35,7 @@ "helloOk": true, "isWritablePrimary": true, "minWireVersion": 0, - "maxWireVersion": 6 + "maxWireVersion": 21 } ] ], diff --git a/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.yml b/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.yml index aed6bae6e..87b72b900 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/single/too_old_then_upgraded.yml @@ -1,4 +1,4 @@ -description: "Standalone with default maxWireVersion of 0 is upgraded to one with maxWireVersion 6" +description: "Standalone with default maxWireVersion of 0 is upgraded to one with maxWireVersion 21" uri: "mongodb://a" phases: [ { @@ -29,7 +29,7 @@ phases: [ helloOk: true, isWritablePrimary: true, minWireVersion: 0, - maxWireVersion: 6 + maxWireVersion: 21 }] ], outcome: { diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.json index 5c78ecfe5..62d26494c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.json @@ -1,6 +1,6 @@ { "description": "auth-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.yml index b2dfc4ecc..febf4a46d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-error.yml @@ -1,6 +1,6 @@ description: auth-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.json index 6e1b64546..fd62fe604 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.json @@ -1,6 +1,6 @@ { "description": "auth-misc-command-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.yml index 94d41d661..9969ca92d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-misc-command-error.yml @@ -1,7 +1,7 @@ --- description: auth-misc-command-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.json index 7606d2db7..84763af32 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.json @@ -1,6 +1,6 @@ { "description": "auth-network-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.yml index 9073a927c..cdf77c56d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-error.yml @@ -1,7 +1,7 @@ --- description: auth-network-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.json index 22066e8ba..3cf9576eb 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.json @@ -1,6 +1,6 @@ { "description": "auth-network-timeout-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.yml index 8b29a1e67..49b91d837 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-network-timeout-error.yml @@ -1,7 +1,7 @@ --- description: auth-network-timeout-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.json index 5dd7b5bb6..b9e503af6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.json @@ -1,6 +1,6 @@ { "description": "auth-shutdown-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.yml index 87a937d38..f0bb4d17d 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/auth-shutdown-error.yml @@ -1,7 +1,7 @@ --- description: auth-shutdown-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.json b/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.json index 896cc8d08..a60ccfcb4 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.json @@ -1,6 +1,6 @@ { "description": "cancel-server-check", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.0", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.yml index 67d96706e..af4679254 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/cancel-server-check.yml @@ -1,7 +1,7 @@ --- description: cancel-server-check -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # General failCommand requirements (this file does not use appName diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.json b/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.json index 67a4d9da1..d3e860a9c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.json @@ -1,6 +1,6 @@ { "description": "connectTimeoutMS", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.yml index ef6d1150a..7c610623e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/connectTimeoutMS.yml @@ -1,7 +1,7 @@ --- description: connectTimeoutMS -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.json index 651466bfa..c1b6db40c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.json @@ -1,6 +1,6 @@ { "description": "find-network-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.yml index deae09a19..f97d799a2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-error.yml @@ -1,7 +1,7 @@ --- description: find-network-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.json index 2bde6daa5..e5ac9f21a 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.json @@ -1,6 +1,6 @@ { "description": "find-network-timeout-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.yml index 30c4809cc..e00b7a2be 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-network-timeout-error.yml @@ -1,7 +1,7 @@ --- description: find-network-timeout-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.json index 624ad352f..6e5a2cac0 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.json @@ -1,6 +1,6 @@ { "description": "find-shutdown-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.yml index f2da705d9..395215244 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/find-shutdown-error.yml @@ -1,7 +1,7 @@ --- description: find-shutdown-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.json index 7d6046b76..87958cb2c 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.json @@ -1,9 +1,9 @@ { "description": "hello-command-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { - "minServerVersion": "4.9", + "minServerVersion": "4.4.7", "serverless": "forbid", "topologies": [ "single", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.yml index 2dc8be2f3..1c9c07922 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-command-error.yml @@ -1,11 +1,11 @@ --- description: hello-command-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: - # failCommand appName requirements - - minServerVersion: "4.9" + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: "4.4.7" serverless: forbid topologies: [ single, replicaset, sharded ] diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.json index f44b26a9f..15ed2b605 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.json @@ -1,9 +1,9 @@ { "description": "hello-network-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { - "minServerVersion": "4.9", + "minServerVersion": "4.4.7", "serverless": "forbid", "topologies": [ "single", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.yml index 20932b39c..5f29194fc 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-network-error.yml @@ -1,11 +1,11 @@ --- description: hello-network-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: - # failCommand appName requirements - - minServerVersion: "4.9" + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: "4.4.7" serverless: forbid topologies: [ single, replicaset, sharded ] diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.json b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.json index dfa6b48d6..fe7cf4e78 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.json @@ -1,6 +1,6 @@ { "description": "hello-timeout", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.yml index efab836e6..2a3374d1e 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/hello-timeout.yml @@ -1,7 +1,7 @@ --- description: hello-timeout -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.json index e4ba6684a..bfe41a4cb 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.json @@ -1,6 +1,6 @@ { "description": "insert-network-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.yml index fc9c2f492..fcedf5435 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-network-error.yml @@ -1,7 +1,7 @@ --- description: insert-network-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.json index 3c724fa5e..af7c6c987 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.json @@ -1,6 +1,6 @@ { "description": "insert-shutdown-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.yml index 1ec920a6b..ae32229ff 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/insert-shutdown-error.yml @@ -1,7 +1,7 @@ --- description: insert-shutdown-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # failCommand appName requirements diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.json b/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.json new file mode 100644 index 000000000..d9329646d --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.json @@ -0,0 +1,591 @@ +{ + "description": "interruptInUse", + "schemaVersion": "1.11", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "interruptInUse", + "databaseName": "sdam-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "Connection pool clear uses interruptInUseConnections=true after monitor timeout", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "poolClearedEvent", + "connectionClosedEvent", + "commandStartedEvent", + "commandSucceededEvent", + "commandFailedEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ], + "uriOptions": { + "connectTimeoutMS": 500, + "heartbeatFrequencyMS": 500, + "appname": "interruptInUse", + "retryReads": false, + "minPoolSize": 0 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "interruptInUse" + } + }, + { + "thread": { + "id": "thread1" + } + } + ] + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + } + } + }, + { + "name": "runOnThread", + "object": "testRunner", + "arguments": { + "thread": "thread1", + "operation": { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$where": "sleep(2000) || true" + } + }, + "expectError": { + "isError": true + } + } + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 4 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "blockConnection": true, + "blockTimeMS": 1500, + "appName": "interruptInUse" + } + } + } + }, + { + "name": "waitForThread", + "object": "testRunner", + "arguments": { + "thread": "thread1" + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + } + ] + }, + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "poolClearedEvent": { + "interruptInUseConnections": true + } + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ], + "outcome": [ + { + "collectionName": "interruptInUse", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "Error returned from connection pool clear with interruptInUseConnections=true is retryable", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "poolClearedEvent", + "connectionClosedEvent", + "commandStartedEvent", + "commandFailedEvent", + "commandSucceededEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ], + "uriOptions": { + "connectTimeoutMS": 500, + "heartbeatFrequencyMS": 500, + "appname": "interruptInUseRetryable", + "retryReads": true, + "minPoolSize": 0 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "interruptInUse" + } + }, + { + "thread": { + "id": "thread1" + } + } + ] + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + } + } + }, + { + "name": "runOnThread", + "object": "testRunner", + "arguments": { + "thread": "thread1", + "operation": { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "$where": "sleep(2000) || true" + } + } + } + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 4 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "blockConnection": true, + "blockTimeMS": 1500, + "appName": "interruptInUseRetryable" + } + } + } + }, + { + "name": "waitForThread", + "object": "testRunner", + "arguments": { + "thread": "thread1" + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandFailedEvent": { + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "commandName": "find" + } + }, + { + "commandSucceededEvent": { + "commandName": "find" + } + } + ] + }, + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "poolClearedEvent": { + "interruptInUseConnections": true + } + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ], + "outcome": [ + { + "collectionName": "interruptInUse", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "Error returned from connection pool clear with interruptInUseConnections=true is retryable for write", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "poolClearedEvent", + "connectionClosedEvent", + "commandStartedEvent", + "commandFailedEvent", + "commandSucceededEvent", + "connectionCheckedOutEvent", + "connectionCheckedInEvent" + ], + "uriOptions": { + "connectTimeoutMS": 500, + "heartbeatFrequencyMS": 500, + "appname": "interruptInUseRetryableWrite", + "retryWrites": true, + "minPoolSize": 0 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "interruptInUse" + } + }, + { + "thread": { + "id": "thread1" + } + } + ] + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": 1 + } + } + }, + { + "name": "runOnThread", + "object": "testRunner", + "arguments": { + "thread": "thread1", + "operation": { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "$where": "sleep(2000) || true" + }, + "update": { + "$set": { + "a": "bar" + } + } + } + } + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 4 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "blockConnection": true, + "blockTimeMS": 1500, + "appName": "interruptInUseRetryableWrite" + } + } + } + }, + { + "name": "waitForThread", + "object": "testRunner", + "arguments": { + "thread": "thread1" + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "command", + "events": [ + { + "commandStartedEvent": { + "commandName": "insert" + } + }, + { + "commandSucceededEvent": { + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandFailedEvent": { + "commandName": "update" + } + }, + { + "commandStartedEvent": { + "commandName": "update" + } + }, + { + "commandSucceededEvent": { + "commandName": "update" + } + } + ] + }, + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "poolClearedEvent": { + "interruptInUseConnections": true + } + }, + { + "connectionCheckedInEvent": {} + }, + { + "connectionClosedEvent": {} + }, + { + "connectionCheckedOutEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ], + "outcome": [ + { + "collectionName": "interruptInUse", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1, + "a": "bar" + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.yml new file mode 100644 index 000000000..67cd7d3ae --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/interruptInUse-pool-clear.yml @@ -0,0 +1,341 @@ +--- +description: interruptInUse + +schemaVersion: "1.11" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName interruptInUse + databaseName: &databaseName sdam-tests + documents: [] + +tests: + - description: Connection pool clear uses interruptInUseConnections=true after monitor timeout + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandSucceededEvent + - commandFailedEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUse + retryReads: false + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: find + object: *collection + arguments: + filter: + $where : sleep(2000) || true + expectError: + isError: true + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 4 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUse + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: find + - commandFailedEvent: + commandName: find + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + + - description: Error returned from connection pool clear with interruptInUseConnections=true is retryable + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandFailedEvent + - commandSucceededEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUseRetryable + retryReads: true + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: find + object: *collection + arguments: + filter: + $where : sleep(2000) || true + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 4 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUseRetryable + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: find + - commandFailedEvent: + commandName: find + - commandStartedEvent: + commandName: find + - commandSucceededEvent: + commandName: find + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - _id: 1 + - description: Error returned from connection pool clear with interruptInUseConnections=true is retryable for write + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionClosedEvent + - commandStartedEvent + - commandFailedEvent + - commandSucceededEvent + - connectionCheckedOutEvent + - connectionCheckedInEvent + uriOptions: + connectTimeoutMS: 500 + heartbeatFrequencyMS: 500 + appname: interruptInUseRetryableWrite + retryWrites: true + minPoolSize: 0 + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - thread: + id: &thread1 thread1 + # ensure the primary is discovered + - name: insertOne + object: *collection + arguments: + document: { _id: 1 } + # simulate a long-running query + - name: runOnThread + object: testRunner + arguments: + thread: *thread1 + operation: + name: updateOne + object: *collection + arguments: + filter: + $where: sleep(2000) || true + update: + "$set": { "a": "bar" } + # Configure the monitor check to fail with a timeout. + # Use "times: 4" to increase the probability that the Monitor check triggers + # the failpoint, since the RTT hello may trigger this failpoint one or many + # times as well. + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 4 + data: + failCommands: + - hello + - isMaster + blockConnection: true + blockTimeMS: 1500 + appName: interruptInUseRetryableWrite + - name: waitForThread + object: testRunner + arguments: + thread: *thread1 + + expectEvents: + - client: *client + eventType: command + events: + - commandStartedEvent: + commandName: insert + - commandSucceededEvent: + commandName: insert + - commandStartedEvent: + commandName: update + - commandFailedEvent: + commandName: update + - commandStartedEvent: + commandName: update + - commandSucceededEvent: + commandName: update + - client: *client + eventType: cmap + events: + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + - connectionCheckedOutEvent: {} + - poolClearedEvent: + interruptInUseConnections: true + - connectionCheckedInEvent: {} + - connectionClosedEvent: {} + - connectionCheckedOutEvent: {} + - connectionCheckedInEvent: {} + + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1, a : bar } + diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.json b/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.json new file mode 100644 index 000000000..30c065763 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.json @@ -0,0 +1,88 @@ +{ + "description": "loadbalanced-emit-topology-description-changed-before-close", + "schemaVersion": "1.20", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "topologyDescriptionChangedEvent", + "topologyOpeningEvent", + "topologyClosedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "events": [ + { + "topologyOpeningEvent": {} + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": {} + } + }, + { + "topologyDescriptionChangedEvent": { + "newDescription": { + "type": "LoadBalanced" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.yml new file mode 100644 index 000000000..c919a8cf8 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/loadbalanced-emit-topology-changed-before-close.yml @@ -0,0 +1,49 @@ +description: "loadbalanced-emit-topology-description-changed-before-close" + +schemaVersion: "1.20" + +runOnRequirements: + - topologies: + - load-balanced + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - topologyDescriptionChangedEvent + - topologyOpeningEvent + - topologyClosedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: close + object: *client + expectEvents: + - client: *client + eventType: sdam + events: + - topologyOpeningEvent: {} + - topologyDescriptionChangedEvent: # unknown -> unknown w disconnected server + previousDescription: + type: "Unknown" + newDescription: {} + - topologyDescriptionChangedEvent: # unknown w disconnected server -> loadBalanced + newDescription: + type: "LoadBalanced" + - topologyDescriptionChangedEvent: # loadbalanced -> unknown + newDescription: + type: "Unknown" + - topologyClosedEvent: {} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.json b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.json new file mode 100644 index 000000000..0ad3b0cea --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.json @@ -0,0 +1,166 @@ +{ + "description": "loadbalanced-logging", + "schemaVersion": "1.16", + "runOnRequirements": [ + { + "topologies": [ + "load-balanced" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "topologyDescriptionChangedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring", + "topologyId": { + "$$exists": true + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.yml new file mode 100644 index 000000000..432ec354e --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-loadbalanced.yml @@ -0,0 +1,81 @@ +description: "loadbalanced-logging" + +schemaVersion: "1.16" + +runOnRequirements: + - topologies: + - load-balanced + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - topologyDescriptionChangedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: close + object: *client + expectLogMessages: + - client: *client + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # unknown topology + newDescription: { $$exists: true } # unknown topology, disconnected server + - level: debug + component: topology + data: + message: "Starting server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # loadBalanced topology + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # loadBalanced topology + newDescription: { $$exists: true } # unknown topology + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + topologyId: { $$exists: true } diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.json b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.json new file mode 100644 index 000000000..fe6ac60b6 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.json @@ -0,0 +1,610 @@ +{ + "description": "replicaset-logging", + "schemaVersion": "1.16", + "runOnRequirements": [ + { + "topologies": [ + "replicaset" + ], + "minServerVersion": "4.4" + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient" + } + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "topologyDescriptionChangedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 4 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring", + "topologyId": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "Successful heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatSucceededEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatSucceededEvent": {} + }, + "count": 3 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "serverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1 + } + } + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "serverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1 + } + } + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "serverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1 + } + } + } + } + } + ] + } + ] + }, + { + "description": "Failing heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatFailedEvent" + ], + "uriOptions": { + "appname": "failingHeartbeatLoggingTest" + } + } + } + ] + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "failingHeartbeatLoggingTest", + "closeConnection": true + } + }, + "client": "setupClient" + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatFailedEvent": {} + }, + "count": 1 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "failure": { + "$$exists": true + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.yml new file mode 100644 index 000000000..0e7cc7706 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-replicaset.yml @@ -0,0 +1,289 @@ +description: "replicaset-logging" + +schemaVersion: "1.16" + +runOnRequirements: + - topologies: + - replicaset + minServerVersion: "4.4" # awaitable hello + +createEntities: + - client: + id: &setupClient setupClient + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - topologyDescriptionChangedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial cluster type change from unknown to ReplicaSet, and connect events for each of 3 servers. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 4 + - name: close + object: *client + expectLogMessages: + - client: *client + ignoreMessages: + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + - level: debug + component: topology + data: + message: "Server heartbeat failed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # unknown topology + newDescription: { $$exists: true } # ReplicaSet topology + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # server connected + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # server connected + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # server connected + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # ReplicaSet topology + newDescription: { $$exists: true } # unknown topology + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + topologyId: { $$exists: true } + - description: Successful heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: *client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatSucceededEvent + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatSucceededEvent: {} + count: 3 + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + serverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + reply: + $$matchAsDocument: + "$$matchAsRoot": + ok: 1 + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + serverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + reply: + $$matchAsDocument: + "$$matchAsRoot": + ok: 1 + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + serverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + reply: + $$matchAsDocument: + "$$matchAsRoot": + ok: 1 + - description: Failing heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: *client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatFailedEvent + uriOptions: + appname: failingHeartbeatLoggingTest + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: + - hello + - isMaster + appName: failingHeartbeatLoggingTest + closeConnection: true + client: *setupClient + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatFailedEvent: {} + count: 1 + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: + "$$exists": true + - level: debug + component: topology + data: + message: "Server heartbeat failed" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + failure: { $$exists: true } \ No newline at end of file diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.json b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.json new file mode 100644 index 000000000..3788708ab --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.json @@ -0,0 +1,494 @@ +{ + "description": "sharded-logging", + "schemaVersion": "1.16", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ], + "minServerVersion": "4.4" + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "topologyDescriptionChangedEvent" + ], + "useMultipleMongoses": true + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 3 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring", + "topologyId": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "Successful heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatSucceededEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatSucceededEvent": {} + }, + "count": 1 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "serverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1 + } + } + } + } + } + ] + } + ] + }, + { + "description": "Failing heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatFailedEvent" + ], + "uriOptions": { + "appname": "failingHeartbeatLoggingTest" + } + } + } + ] + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "failingHeartbeatLoggingTest", + "closeConnection": true + } + }, + "client": "setupClient" + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatFailedEvent": {} + }, + "count": 1 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "failure": { + "$$exists": true + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.yml new file mode 100644 index 000000000..65309b6bb --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-sharded.yml @@ -0,0 +1,248 @@ +description: "sharded-logging" + +schemaVersion: "1.16" + +runOnRequirements: + - topologies: + - sharded + minServerVersion: "4.4" # awaitable hello + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - topologyDescriptionChangedEvent + useMultipleMongoses: true + # ensure the topology has been fully discovered before closing the client. + # expected events are initial cluster type change from unknown to sharded, and connect events for each of 2 servers. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 3 + - name: close + object: *client + expectLogMessages: + - client: *client + ignoreMessages: + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + - level: debug + component: topology + data: + message: "Server heartbeat failed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # unknown topology + newDescription: { $$exists: true } # Sharded topology + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # shard router connected + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # shard router connected + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # Sharded topology + newDescription: { $$exists: true } # unknown topology + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + topologyId: { $$exists: true } + - description: Successful heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: *client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatSucceededEvent + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatSucceededEvent: {} + count: 1 + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + serverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + reply: + $$matchAsDocument: + "$$matchAsRoot": + ok: 1 + - description: Failing heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: *client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatFailedEvent + uriOptions: + appname: failingHeartbeatLoggingTest + - name: failPoint + object: testRunner + arguments: + failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: + - hello + - isMaster + appName: failingHeartbeatLoggingTest + closeConnection: true + client: *setupClient + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatFailedEvent: {} + count: 1 + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: + "$$exists": true + - level: debug + component: topology + data: + message: "Server heartbeat failed" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + failure: { $$exists: true } \ No newline at end of file diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.json b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.json new file mode 100644 index 000000000..0682a1a4f --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.json @@ -0,0 +1,519 @@ +{ + "description": "standalone-logging", + "schemaVersion": "1.16", + "runOnRequirements": [ + { + "topologies": [ + "single" + ], + "minServerVersion": "4.4" + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient" + } + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "topologyDescriptionChangedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring", + "topologyId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring", + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed", + "topologyId": { + "$$exists": true + }, + "previousDescription": { + "$$exists": true + }, + "newDescription": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring", + "topologyId": { + "$$exists": true + } + } + } + ] + } + ] + }, + { + "description": "Successful heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatSucceededEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatSucceededEvent": {} + }, + "count": 1 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + } + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "serverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "reply": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "ok": 1 + } + } + } + } + } + ] + } + ] + }, + { + "description": "Failing heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeLogMessages": { + "topology": "debug" + }, + "observeEvents": [ + "serverHeartbeatFailedEvent" + ], + "uriOptions": { + "appname": "failingHeartbeatLoggingTest" + } + } + } + ] + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "failingHeartbeatLoggingTest", + "closeConnection": true + } + } + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatFailedEvent": {} + }, + "count": 1 + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "ignoreExtraMessages": true, + "ignoreMessages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped topology monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Stopped server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Topology description changed" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting server monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Starting topology monitoring" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat started" + } + }, + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat succeeded" + } + } + ], + "messages": [ + { + "level": "debug", + "component": "topology", + "data": { + "message": "Server heartbeat failed", + "awaited": { + "$$exists": true + }, + "topologyId": { + "$$exists": true + }, + "serverHost": { + "$$type": "string" + }, + "serverPort": { + "$$type": [ + "int", + "long" + ] + }, + "driverConnectionId": { + "$$exists": true + }, + "durationMS": { + "$$type": [ + "double", + "int", + "long" + ] + }, + "failure": { + "$$exists": true + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.yml new file mode 100644 index 000000000..b243abf6f --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/logging-standalone.yml @@ -0,0 +1,258 @@ +description: "standalone-logging" + +schemaVersion: "1.16" + +runOnRequirements: + - topologies: + - single + minServerVersion: "4.4" # awaitable hello + +createEntities: + - client: + id: &setupClient setupClient + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - topologyDescriptionChangedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: close + object: *client + expectLogMessages: + - client: *client + ignoreMessages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + - level: debug + component: topology + data: + message: "Server heartbeat failed" + messages: + - level: debug + component: topology + data: + message: "Starting topology monitoring" + topologyId: { $$exists: true } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # unknown topology + newDescription: { $$exists: true } # unknown topology, disconnected server + - level: debug + component: topology + data: + message: "Starting server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } + newDescription: { $$exists: true } # standalone topology + - level: debug + component: topology + data: + message: "Stopped server monitoring" + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + - level: debug + component: topology + data: + message: "Topology description changed" + topologyId: { $$exists: true } + previousDescription: { $$exists: true } # standalone topology + newDescription: { $$exists: true } # unknown topology + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + topologyId: { $$exists: true } + - description: Successful heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatSucceededEvent + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatSucceededEvent: {} + count: 1 + - name: close + object: *client + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Topology description changed" + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Starting topology monitoring" + - level: debug + component: topology + data: + message: "Server heartbeat failed" + messages: + - level: debug + component: topology + data: + message: "Server heartbeat started" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + serverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + reply: + $$matchAsDocument: + "$$matchAsRoot": + ok: 1 + - description: Failing heartbeat + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeLogMessages: + topology: debug + observeEvents: + - serverHeartbeatFailedEvent + uriOptions: + appname: failingHeartbeatLoggingTest + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: "alwaysOn" + data: + failCommands: + - hello + - isMaster + appName: failingHeartbeatLoggingTest + closeConnection: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverHeartbeatFailedEvent: {} + count: 1 + expectLogMessages: + - client: *client + ignoreExtraMessages: true + ignoreMessages: + - level: debug + component: topology + data: + message: "Topology description changed" + - level: debug + component: topology + data: + message: "Stopped topology monitoring" + - level: debug + component: topology + data: + message: "Stopped server monitoring" + - level: debug + component: topology + data: + message: "Topology description changed" + - level: debug + component: topology + data: + message: "Starting server monitoring" + - level: debug + component: topology + data: + message: "Starting topology monitoring" + - level: debug + component: topology + data: + message: "Server heartbeat started" + - level: debug + component: topology + data: + message: "Server heartbeat succeeded" + messages: + - level: debug + component: topology + data: + message: "Server heartbeat failed" + awaited: { $$exists: true } + topologyId: { $$exists: true } + serverHost: { $$type: string } + serverPort: { $$type: [int, long] } + driverConnectionId: { $$exists: true } + durationMS: { $$type: [double, int, long] } + failure: { $$exists: true } \ No newline at end of file diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.json index 0234ac992..bd9e9fcde 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.json @@ -1,9 +1,9 @@ { "description": "minPoolSize-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { - "minServerVersion": "4.9", + "minServerVersion": "4.4.7", "serverless": "forbid", "topologies": [ "single" diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.yml index cc72af578..110e647c6 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/minPoolSize-error.yml @@ -1,11 +1,11 @@ --- description: minPoolSize-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: - # failCommand appName requirements - - minServerVersion: "4.9" + # Require SERVER-49336 for failCommand + appName on the initial handshake. + - minServerVersion: "4.4.7" serverless: forbid topologies: - single diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.json new file mode 100644 index 000000000..b8fd95fee --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.json @@ -0,0 +1,149 @@ +{ + "description": "pool-clear-application-error", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "initialData": [ + { + "collectionName": "find-network-error", + "databaseName": "sdam-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "Pool is cleared before application connection is checked into the pool", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true, + "appName": "findNetworkErrorTest" + } + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "poolClearedEvent", + "connectionCheckedInEvent" + ], + "uriOptions": { + "retryWrites": false, + "retryReads": false, + "appname": "findNetworkErrorTest" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "sdam-tests" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "find-network-error" + } + } + ] + } + }, + { + "name": "find", + "object": "collection", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "poolClearedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionCheckedInEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "poolClearedEvent": {} + }, + { + "connectionCheckedInEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.yml new file mode 100644 index 000000000..43e425711 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-application-error.yml @@ -0,0 +1,88 @@ +--- +description: pool-clear-application-error + +schemaVersion: "1.4" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +initialData: &initialData + - collectionName: &collectionName find-network-error + databaseName: &databaseName sdam-tests + documents: + - _id: 1 + - _id: 2 + +tests: + - description: Pool is cleared before application connection is checked into the pool + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - find + closeConnection: true + appName: findNetworkErrorTest + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - poolClearedEvent + - connectionCheckedInEvent + uriOptions: + retryWrites: false + retryReads: false + appname: findNetworkErrorTest + - database: + id: &database database + client: *client + databaseName: *databaseName + - collection: + id: &collection collection + database: *database + collectionName: *collectionName + - name: find + object: *collection + arguments: + filter: + _id: 1 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + connectionCheckedInEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: cmap + events: + - poolClearedEvent: {} + - connectionCheckedInEvent: {} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json new file mode 100644 index 000000000..126ee5453 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.json @@ -0,0 +1,296 @@ +{ + "description": "pool-clear-on-error-checkout", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single", + "replicaset", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "Pool is cleared before connection is closed (authentication error)", + "runOnRequirements": [ + { + "auth": true + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslContinue" + ], + "appName": "authErrorTest", + "errorCode": 18 + } + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "connectionCheckOutStartedEvent", + "poolClearedEvent", + "connectionClosedEvent" + ], + "uriOptions": { + "retryWrites": false, + "appname": "authErrorTest" + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "foo" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "bar" + } + } + ] + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "poolClearedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ] + }, + { + "description": "Pool is cleared before connection is closed (handshake error)", + "runOnRequirements": [ + { + "topologies": [ + "single" + ] + } + ], + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "connectionCheckOutStartedEvent", + "poolClearedEvent", + "connectionClosedEvent", + "topologyDescriptionChangedEvent" + ], + "uriOptions": { + "retryWrites": false, + "appname": "authErrorTest", + "minPoolSize": 0, + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 1000000 + } + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "foo" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "bar" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Single" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "authErrorTest", + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + "expectError": { + "isError": true + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "poolClearedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCheckOutStartedEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml new file mode 100644 index 000000000..8df74b6a6 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-checkout-error.yml @@ -0,0 +1,176 @@ +--- +description: pool-clear-on-error-checkout + +schemaVersion: "1.4" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single, replicaset, sharded ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +tests: + - description: Pool is cleared before connection is closed (authentication error) + runOnRequirements: + - auth: true + + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + appName: authErrorTest + errorCode: 18 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - connectionCheckOutStartedEvent + - poolClearedEvent + - connectionClosedEvent + uriOptions: + retryWrites: false + appname: authErrorTest + - database: + id: &database database + client: *client + databaseName: foo + - collection: + id: &collection collection + database: *database + collectionName: bar + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + connectionClosedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: cmap + events: + - connectionCheckOutStartedEvent: {} + - poolClearedEvent: {} + - connectionClosedEvent: {} + + - description: Pool is cleared before connection is closed (handshake error) + runOnRequirements: + - topologies: [ single ] + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: + - connectionCheckOutStartedEvent + - poolClearedEvent + - connectionClosedEvent + - topologyDescriptionChangedEvent + uriOptions: + retryWrites: false + appname: authErrorTest + minPoolSize: 0 + # ensure that once we've connected to the server, the failCommand won't + # be triggered by monitors and will only be triggered by handshakes + serverMonitoringMode: poll + heartbeatFrequencyMS: 1000000 + - database: + id: &database database + client: *client + databaseName: foo + - collection: + id: &collection collection + database: *database + collectionName: bar + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: + previousDescription: + type: "Unknown" + newDescription: + type: "Single" + count: 1 + + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - hello + - isMaster + appName: authErrorTest + closeConnection: true + + - name: insertMany + object: *collection + arguments: + documents: + - _id: 3 + - _id: 4 + expectError: + isError: true + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + connectionClosedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: cmap + events: + - connectionCheckOutStartedEvent: {} + - poolClearedEvent: {} + - connectionClosedEvent: {} + diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json new file mode 100644 index 000000000..11c6be5bc --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.json @@ -0,0 +1,230 @@ +{ + "description": "pool-cleared-on-min-pool-size-population-error", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4", + "serverless": "forbid", + "topologies": [ + "single" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "setupClient", + "useMultipleMongoses": false + } + } + ], + "tests": [ + { + "description": "Pool is cleared on authentication error during minPoolSize population", + "runOnRequirements": [ + { + "auth": true + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "saslContinue" + ], + "appName": "authErrorTest", + "errorCode": 18 + } + } + } + }, + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "connectionCreatedEvent", + "poolClearedEvent", + "connectionClosedEvent" + ], + "uriOptions": { + "appname": "authErrorTest", + "minPoolSize": 1 + } + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "poolClearedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ] + }, + { + "description": "Pool is cleared on handshake error during minPoolSize population", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "topologyDescriptionChangedEvent", + "connectionCreatedEvent", + "poolClearedEvent", + "connectionClosedEvent", + "connectionReadyEvent" + ], + "uriOptions": { + "appname": "authErrorTest", + "minPoolSize": 5, + "maxConnecting": 1, + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 1000000 + } + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Single" + } + } + }, + "count": 1 + } + }, + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "setupClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "hello", + "isMaster" + ], + "appName": "authErrorTest", + "closeConnection": true + } + } + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "poolClearedEvent": {} + }, + "count": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "connectionClosedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "cmap", + "events": [ + { + "connectionCreatedEvent": {} + }, + { + "connectionReadyEvent": {} + }, + { + "connectionCreatedEvent": {} + }, + { + "poolClearedEvent": {} + }, + { + "connectionClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml new file mode 100644 index 000000000..7e7ef0c59 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-clear-min-pool-size-error.yml @@ -0,0 +1,144 @@ +--- +description: pool-cleared-on-min-pool-size-population-error + +schemaVersion: "1.4" + +runOnRequirements: + # failCommand appName requirements + - minServerVersion: "4.4" + serverless: forbid + topologies: [ single ] + +createEntities: + - client: + id: &setupClient setupClient + useMultipleMongoses: false + +tests: + - description: Pool is cleared on authentication error during minPoolSize population + runOnRequirements: + # failCommand appName requirements + - auth: true + operations: + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - saslContinue + appName: authErrorTest + errorCode: 18 + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - connectionCreatedEvent + - poolClearedEvent + - connectionClosedEvent + uriOptions: + appname: authErrorTest + minPoolSize: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + connectionClosedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: cmap + events: + - connectionCreatedEvent: {} + - poolClearedEvent: {} + - connectionClosedEvent: {} + + - description: Pool is cleared on handshake error during minPoolSize population + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - topologyDescriptionChangedEvent + - connectionCreatedEvent + - poolClearedEvent + - connectionClosedEvent + - connectionReadyEvent + uriOptions: + appname: authErrorTest + minPoolSize: 5 + maxConnecting: 1 + # ensure that once we've connected to the server, the failCommand won't + # be triggered by monitors and will only be triggered by handshakes + serverMonitoringMode: poll + heartbeatFrequencyMS: 1000000 + + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: + previousDescription: + type: "Unknown" + newDescription: + type: "Single" + count: 1 + + - name: failPoint + object: testRunner + arguments: + client: *setupClient + failPoint: + configureFailPoint: failCommand + mode: + times: 1 + data: + failCommands: + - hello + - isMaster + appName: authErrorTest + closeConnection: true + + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + poolClearedEvent: {} + count: 1 + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + connectionClosedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: cmap + events: + - connectionCreatedEvent: {} + - connectionReadyEvent: {} + - connectionCreatedEvent: {} + - poolClearedEvent: {} + - connectionClosedEvent: {} + diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.json b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.json index 9a7dfd901..b7f6924f2 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.json @@ -1,6 +1,6 @@ { "description": "pool-cleared-error", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.9", diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.yml index 07bfc0c0d..f3bad7959 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/pool-cleared-error.yml @@ -1,7 +1,7 @@ --- description: pool-cleared-error -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # This test requires retryable writes, failCommand appName, and @@ -200,7 +200,7 @@ tests: event: poolClearedEvent: {} count: 1 - # Perform an operation to ensure the node still useable. + # Perform an operation to ensure the node still usable. - name: insertOne object: *collection arguments: diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.json b/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.json index 0ad575cc9..3147a07a1 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.json +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.json @@ -1,6 +1,6 @@ { "description": "rediscover-quickly-after-step-down", - "schemaVersion": "1.10", + "schemaVersion": "1.4", "runOnRequirements": [ { "minServerVersion": "4.4", @@ -117,7 +117,7 @@ "replSetFreeze": 0 }, "readPreference": { - "mode": "Secondary" + "mode": "secondary" }, "commandName": "replSetFreeze" } diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.yml index d0ed6e41d..f3e750916 100644 --- a/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.yml +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/rediscover-quickly-after-step-down.yml @@ -1,7 +1,7 @@ --- description: rediscover-quickly-after-step-down -schemaVersion: "1.10" +schemaVersion: "1.4" runOnRequirements: # 4.4 is required for streaming. @@ -78,7 +78,7 @@ tests: command: replSetFreeze: 0 readPreference: - mode: Secondary + mode: secondary commandName: replSetFreeze # Run replSetStepDown on the meta client. - name: runCommand diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.json b/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.json new file mode 100644 index 000000000..066a4ffee --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.json @@ -0,0 +1,89 @@ +{ + "description": "replicaset-emit-topology-description-changed-before-close", + "schemaVersion": "1.20", + "runOnRequirements": [ + { + "topologies": [ + "replicaset" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "topologyDescriptionChangedEvent", + "topologyOpeningEvent", + "topologyClosedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 4 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": false, + "events": [ + { + "topologyOpeningEvent": {} + }, + { + "topologyDescriptionChangedEvent": {} + }, + { + "topologyDescriptionChangedEvent": {} + }, + { + "topologyDescriptionChangedEvent": {} + }, + { + "topologyDescriptionChangedEvent": {} + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "ReplicaSetWithPrimary" + }, + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.yml new file mode 100644 index 000000000..d0a1158ea --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/replicaset-emit-topology-changed-before-close.yml @@ -0,0 +1,49 @@ +description: "replicaset-emit-topology-description-changed-before-close" + +schemaVersion: "1.20" + +runOnRequirements: + - topologies: + - replicaset + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - topologyDescriptionChangedEvent + - topologyOpeningEvent + - topologyClosedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial server discovery and 3 server connect events. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 4 + - name: close + object: *client + expectEvents: + - client: *client + eventType: sdam + ignoreExtraEvents: false + events: + - topologyOpeningEvent: {} + - topologyDescriptionChangedEvent: {} # unknown -> replset no primary + - topologyDescriptionChangedEvent: {} # server connected + - topologyDescriptionChangedEvent: {} # server connected + - topologyDescriptionChangedEvent: {} # server connected + - topologyDescriptionChangedEvent: # replicaset -> unknown + previousDescription: + type: "ReplicaSetWithPrimary" + newDescription: + type: "Unknown" + - topologyClosedEvent: {} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.json b/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.json new file mode 100644 index 000000000..4b492f7d8 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.json @@ -0,0 +1,512 @@ +{ + "description": "serverMonitoringMode", + "schemaVersion": "1.17", + "runOnRequirements": [ + { + "topologies": [ + "single", + "sharded", + "sharded-replicaset" + ], + "serverless": "forbid" + } + ], + "tests": [ + { + "description": "connect with serverMonitoringMode=auto >=4.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4.0" + } + ], + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "auto" + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent", + "serverHeartbeatFailedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 2 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + }, + { + "serverHeartbeatSucceededEvent": { + "awaited": false + } + }, + { + "serverHeartbeatStartedEvent": { + "awaited": true + } + } + ] + } + ] + }, + { + "description": "connect with serverMonitoringMode=auto <4.4", + "runOnRequirements": [ + { + "maxServerVersion": "4.2.99" + } + ], + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "auto", + "heartbeatFrequencyMS": 500 + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent", + "serverHeartbeatFailedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 2 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + }, + { + "serverHeartbeatSucceededEvent": { + "awaited": false + } + }, + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + } + ] + } + ] + }, + { + "description": "connect with serverMonitoringMode=stream >=4.4", + "runOnRequirements": [ + { + "minServerVersion": "4.4.0" + } + ], + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "stream" + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent", + "serverHeartbeatFailedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 2 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + }, + { + "serverHeartbeatSucceededEvent": { + "awaited": false + } + }, + { + "serverHeartbeatStartedEvent": { + "awaited": true + } + } + ] + } + ] + }, + { + "description": "connect with serverMonitoringMode=stream <4.4", + "runOnRequirements": [ + { + "maxServerVersion": "4.2.99" + } + ], + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "stream", + "heartbeatFrequencyMS": 500 + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent", + "serverHeartbeatFailedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 2 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + }, + { + "serverHeartbeatSucceededEvent": { + "awaited": false + } + }, + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + } + ] + } + ] + }, + { + "description": "connect with serverMonitoringMode=poll", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 500 + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent", + "serverHeartbeatFailedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "runCommand", + "object": "db", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectResult": { + "ok": 1 + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 2 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + }, + { + "serverHeartbeatSucceededEvent": { + "awaited": false + } + }, + { + "serverHeartbeatStartedEvent": { + "awaited": false + } + } + ] + } + ] + }, + { + "description": "poll waits after successful heartbeat", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "serverMonitoringMode": "poll", + "heartbeatFrequencyMS": 1000000 + }, + "useMultipleMongoses": false, + "observeEvents": [ + "serverHeartbeatStartedEvent", + "serverHeartbeatSucceededEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "sdam-tests" + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatSucceededEvent": {} + }, + "count": 1 + } + }, + { + "name": "wait", + "object": "testRunner", + "arguments": { + "ms": 500 + } + }, + { + "name": "assertEventCount", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverHeartbeatStartedEvent": {} + }, + "count": 1 + } + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.yml new file mode 100644 index 000000000..5a81f0e1a --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/serverMonitoringMode.yml @@ -0,0 +1,213 @@ +description: serverMonitoringMode + +schemaVersion: "1.17" +# These tests cannot run on replica sets because the order of the expected +# SDAM events are non-deterministic when monitoring multiple servers. +# They also cannot run on Serverless or load balanced clusters where SDAM is disabled. +runOnRequirements: + - topologies: [single, sharded, sharded-replicaset] + serverless: forbid +tests: + - description: "connect with serverMonitoringMode=auto >=4.4" + runOnRequirements: + - minServerVersion: "4.4.0" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "auto" + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - serverHeartbeatFailedEvent + - database: + id: db + client: client + databaseName: sdam-tests + - &ping + name: runCommand + object: db + arguments: + commandName: ping + command: { ping: 1 } + expectResult: { ok: 1 } + # Wait for the second serverHeartbeatStartedEvent to ensure we start streaming. + - &waitForSecondHeartbeatStarted + name: waitForEvent + object: testRunner + arguments: + client: client + event: + serverHeartbeatStartedEvent: {} + count: 2 + expectEvents: &streamingStartedEvents + - client: client + eventType: sdam + ignoreExtraEvents: true + events: + - serverHeartbeatStartedEvent: + awaited: False + - serverHeartbeatSucceededEvent: + awaited: False + - serverHeartbeatStartedEvent: + awaited: True + + - description: "connect with serverMonitoringMode=auto <4.4" + runOnRequirements: + - maxServerVersion: "4.2.99" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "auto" + heartbeatFrequencyMS: 500 + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - serverHeartbeatFailedEvent + - database: + id: db + client: client + databaseName: sdam-tests + - *ping + # Wait for the second serverHeartbeatStartedEvent to ensure we do not stream. + - *waitForSecondHeartbeatStarted + expectEvents: &pollingStartedEvents + - client: client + eventType: sdam + ignoreExtraEvents: true + events: + - serverHeartbeatStartedEvent: + awaited: False + - serverHeartbeatSucceededEvent: + awaited: False + - serverHeartbeatStartedEvent: + awaited: False + + - description: "connect with serverMonitoringMode=stream >=4.4" + runOnRequirements: + - minServerVersion: "4.4.0" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "stream" + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - serverHeartbeatFailedEvent + - database: + id: db + client: client + databaseName: sdam-tests + - *ping + # Wait for the second serverHeartbeatStartedEvent to ensure we start streaming. + - *waitForSecondHeartbeatStarted + expectEvents: *streamingStartedEvents + + - description: "connect with serverMonitoringMode=stream <4.4" + runOnRequirements: + - maxServerVersion: "4.2.99" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "stream" + heartbeatFrequencyMS: 500 + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - serverHeartbeatFailedEvent + - database: + id: db + client: client + databaseName: sdam-tests + - *ping + # Wait for the second serverHeartbeatStartedEvent to ensure we do not stream. + - *waitForSecondHeartbeatStarted + expectEvents: *pollingStartedEvents + + - description: "connect with serverMonitoringMode=poll" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "poll" + heartbeatFrequencyMS: 500 + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - serverHeartbeatFailedEvent + - database: + id: db + client: client + databaseName: sdam-tests + - *ping + # Wait for the second serverHeartbeatStartedEvent to ensure we do not stream. + - *waitForSecondHeartbeatStarted + expectEvents: *pollingStartedEvents + + - description: "poll waits after successful heartbeat" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: client + uriOptions: + serverMonitoringMode: "poll" + heartbeatFrequencyMS: 1000000 + useMultipleMongoses: false + observeEvents: + - serverHeartbeatStartedEvent + - serverHeartbeatSucceededEvent + - database: + id: db + client: client + databaseName: sdam-tests + # Wait for the first serverHeartbeatSucceededEvent to ensure we start polling. + - name: waitForEvent + object: testRunner + arguments: + client: client + event: + serverHeartbeatSucceededEvent: {} + count: 1 + # Wait for a bit longer to ensure we wait heartbeatFrequencyMS before starting the next check. + - name: wait + object: testRunner + arguments: + ms: 500 + - name: assertEventCount + object: testRunner + arguments: + client: client + event: + serverHeartbeatStartedEvent: {} + count: 1 diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.json b/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.json new file mode 100644 index 000000000..98fb58553 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.json @@ -0,0 +1,108 @@ +{ + "description": "sharded-emit-topology-description-changed-before-close", + "schemaVersion": "1.20", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "topologyDescriptionChangedEvent", + "topologyOpeningEvent", + "topologyClosedEvent" + ], + "useMultipleMongoses": true + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 3 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": false, + "events": [ + { + "topologyOpeningEvent": {} + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Sharded" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Sharded" + }, + "newDescription": { + "type": "Sharded" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Sharded" + }, + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.yml new file mode 100644 index 000000000..cb6cc5ad4 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/sharded-emit-topology-changed-before-close.yml @@ -0,0 +1,62 @@ +description: "sharded-emit-topology-description-changed-before-close" + +schemaVersion: "1.20" + +runOnRequirements: + - topologies: + - sharded + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - topologyDescriptionChangedEvent + - topologyOpeningEvent + - topologyClosedEvent + useMultipleMongoses: true + # ensure the topology has been fully discovered before closing the client. + # expected events are initial cluster type change from unknown to sharded and connect events + # for each of 2 servers + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 3 + - name: close + object: *client + expectEvents: + - client: *client + eventType: sdam + ignoreExtraEvents: false + events: + - topologyOpeningEvent: {} + - topologyDescriptionChangedEvent: # unknown -> unknown w disconnected server + previousDescription: + type: "Unknown" + newDescription: + type: "Unknown" + - topologyDescriptionChangedEvent: # server connected + previousDescription: + type: "Unknown" + newDescription: + type: "Sharded" + - topologyDescriptionChangedEvent: # server connected + previousDescription: + type: "Sharded" + newDescription: + type: "Sharded" + - topologyDescriptionChangedEvent: # sharded -> unknown + previousDescription: + type: "Sharded" + newDescription: + type: "Unknown" + - topologyClosedEvent: {} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.json b/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.json new file mode 100644 index 000000000..27b5444d5 --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.json @@ -0,0 +1,97 @@ +{ + "description": "standalone-emit-topology-description-changed-before-close", + "schemaVersion": "1.20", + "runOnRequirements": [ + { + "topologies": [ + "single" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "Topology lifecycle", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "topologyDescriptionChangedEvent", + "topologyOpeningEvent", + "topologyClosedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "close", + "object": "client" + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": false, + "events": [ + { + "topologyOpeningEvent": {} + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Single" + } + } + }, + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Single" + }, + "newDescription": { + "type": "Unknown" + } + } + }, + { + "topologyClosedEvent": {} + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.yml b/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.yml new file mode 100644 index 000000000..20fa380bd --- /dev/null +++ b/src/test/spec/json/server-discovery-and-monitoring/unified/standalone-emit-topology-changed-before-close.yml @@ -0,0 +1,55 @@ +description: "standalone-emit-topology-description-changed-before-close" + +schemaVersion: "1.20" + +runOnRequirements: + - topologies: + - single + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "Topology lifecycle" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + observeEvents: + - topologyDescriptionChangedEvent + - topologyOpeningEvent + - topologyClosedEvent + # ensure the topology has been fully discovered before closing the client. + # expected events are initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: close + object: *client + expectEvents: + - client: *client + eventType: sdam + ignoreExtraEvents: false + events: + - topologyOpeningEvent: {} + - topologyDescriptionChangedEvent: # unknown -> unknown w disconnected server + previousDescription: + type: "Unknown" + newDescription: + type: "Unknown" + - topologyDescriptionChangedEvent: # unknown w disconnected server -> standalone + previousDescription: + type: "Unknown" + newDescription: + type: "Single" + - topologyDescriptionChangedEvent: # standalone -> unknown + previousDescription: + type: "Single" + newDescription: + type: "Unknown" + - topologyClosedEvent: {} diff --git a/src/test/spec/json/server-selection/README.md b/src/test/spec/json/server-selection/README.md new file mode 100644 index 000000000..8699168cb --- /dev/null +++ b/src/test/spec/json/server-selection/README.md @@ -0,0 +1,85 @@ +# Server Selection Tests + +This directory contains platform-independent tests that drivers can use to prove their conformance to the Server +Selection spec. The tests are provided in both YAML and JSON formats, and drivers may test against whichever format is +more convenient for them. + +## Version + +Files in the "specifications" repository have no version scheme. They are not tied to a MongoDB server version. + +## Test Format and Use + +There are two types of tests for the server selection spec, tests for round trip time (RTT) calculation, and tests for +server selection logic. + +Drivers should be able to test their server selection logic without any network I/O, by parsing topology descriptions +and read preference documents from the test files and passing them into driver code. Parts of the server selection code +may need to be mocked or subclassed to achieve this. + +### RTT Calculation Tests + +These YAML files contain the following keys: + +- `avg_rtt_ms`: a server's previous average RTT, in milliseconds +- `new_rtt_ms`: a new RTT value for this server, in milliseconds +- `new_avg_rtt`: this server's newly-calculated average RTT, in milliseconds + +For each file, create a server description object initialized with `avg_rtt_ms`. Parse `new_rtt_ms`, and ensure that the +new RTT value for the mocked server description is equal to `new_avg_rtt`. + +If driver architecture doesn't easily allow construction of server description objects in isolation, unit testing the +EWMA algorithm using these inputs and expected outputs is acceptable. + +### Server Selection Logic Tests + +These YAML files contain the following setup for each test: + +- `topology_description`: the state of a mocked cluster +- `operation`: the kind of operation to perform, either read or write +- `read_preference`: a read preference document + +For each file, create a new TopologyDescription object initialized with the values from `topology_description`. Create a +ReadPreference object initialized with the values from `read_preference`. + +Together with "operation", pass the newly-created TopologyDescription and ReadPreference to server selection, and ensure +that it selects the correct subset of servers from the TopologyDescription. Each YAML file contains a key for these +stages of server selection: + +- `suitable_servers`: the set of servers in topology_description that are suitable, as per the Server Selection spec, + given operation and read_preference +- `in_latency_window`: the set of suitable_servers that fall within the latency window + +Drivers implementing server selection MUST test that their implementation correctly returns the set of servers in +`in_latency_window`. Drivers SHOULD also test against `suitable_servers` if possible. + +### Selection Within Latency Window YAML Tests + +These tests verify that servers select servers from within the latency window correctly. These tests MUST only be +implemented by multi-threaded or async drivers. + +Each YAML file for these tests has the following format: + +- `topology_description`: the state of a mocked cluster + +- `mocked_topology_state`: array of objects containing the operationCounts for each server included in + `topology_description`. Each element will have all of the following fields: + + - `address`: a unique address identifying this server + - `operation_count`: the `operationCount` for this server + +- `iterations`: the number of selections that should be run as part of this\ + test + +- `outcome`: an object describing the expected outcome of the selections + + - `tolerance`: the maximum difference between an observed frequency and an expected one + - `expected_frequencies`: a document whose keys are the server addresses from the `in_window` array and values are + numbers in `[0, 1]` indicating the frequency at which the server should have been selected. + +For each file, create a new TopologyDescription object initialized with the values from `topology_description` and +populate the operationCounts based on the information provided in `mocked_topology_state`. Then, repeatedly select a +server for a read operation using the mocked TopologyDescription and a "nearest" ReadPreference `iterations` times. Once +`iterations` selections have been made, verify that each server was selected at a frequency within `outcome.tolerance` +of the expected frequency contained in `outcome.expected_frequencies` for that server. If the expected frequency for a +given server is 1 or 0, then the observed frequency MUST be exactly equal to the expected one. diff --git a/src/test/spec/json/server-selection/README.rst b/src/test/spec/json/server-selection/README.rst deleted file mode 100644 index afd87ec68..000000000 --- a/src/test/spec/json/server-selection/README.rst +++ /dev/null @@ -1,109 +0,0 @@ -====================== -Server Selection Tests -====================== - -This directory contains platform-independent tests that drivers can use -to prove their conformance to the Server Selection spec. The tests -are provided in both YAML and JSON formats, and drivers may test against -whichever format is more convenient for them. - -Version -------- - -Files in the "specifications" repository have no version scheme. They are not -tied to a MongoDB server version. - -Test Format and Use -------------------- - -There are two types of tests for the server selection spec, tests for -round trip time (RTT) calculation, and tests for server selection logic. - -Drivers should be able to test their server selection logic -without any network I/O, by parsing topology descriptions and read preference -documents from the test files and passing them into driver code. Parts of the -server selection code may need to be mocked or subclassed to achieve this. - -RTT Calculation Tests ->>>>>>>>>>>>>>>>>>>>> - -These YAML files contain the following keys: - -- ``avg_rtt_ms``: a server's previous average RTT, in milliseconds -- ``new_rtt_ms``: a new RTT value for this server, in milliseconds -- ``new_avg_rtt``: this server's newly-calculated average RTT, in milliseconds - -For each file, create a server description object initialized with ``avg_rtt_ms``. -Parse ``new_rtt_ms``, and ensure that the new RTT value for the mocked server -description is equal to ``new_avg_rtt``. - -If driver architecture doesn't easily allow construction of server description -objects in isolation, unit testing the EWMA algorithm using these inputs -and expected outputs is acceptable. - -Server Selection Logic Tests ->>>>>>>>>>>>>>>>>>>>>>>>>>>> - -These YAML files contain the following setup for each test: - -- ``topology_description``: the state of a mocked cluster -- ``operation``: the kind of operation to perform, either read or write -- ``read_preference``: a read preference document - -For each file, create a new TopologyDescription object initialized with the values -from ``topology_description``. Create a ReadPreference object initialized with the -values from ``read_preference``. - -Together with "operation", pass the newly-created TopologyDescription and ReadPreference -to server selection, and ensure that it selects the correct subset of servers from -the TopologyDescription. Each YAML file contains a key for these stages of server selection: - -- ``suitable_servers``: the set of servers in topology_description that are suitable, as - per the Server Selection spec, given operation and read_preference -- ``in_latency_window``: the set of suitable_servers that fall within the latency window - -Drivers implementing server selection MUST test that their implementation -correctly returns the set of servers in ``in_latency_window``. Drivers SHOULD also test -against ``suitable_servers`` if possible. - -Selection Within Latency Window YAML Tests ->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - -These tests verify that servers select servers from within the latency -window correctly. These tests MUST only be implemented by -multi-threaded or async drivers. - -Each YAML file for these tests has the following format: - -- ``topology_description``: the state of a mocked cluster - -- ``mocked_topology_state``: array of objects containing the operationCounts for - each server included in ``topology_description``. Each element will have all - of the following fields: - - - ``address``: a unique address identifying this server - - - ``operation_count``: the ``operationCount`` for this server - -- ``iterations``: the number of selections that should be run as part of this - test - -- ``outcome``: an object describing the expected outcome of the selections - - - ``tolerance``: the maximum difference between an observed frequency and an - expected one - - - ``expected_frequencies``: a document whose keys are the server addresses - from the ``in_window`` array and values are numbers in [0, 1] indicating the - frequency at which the server should have been selected. - -For each file, create a new TopologyDescription object initialized with the -values from ``topology_description`` and populate the operationCounts based on -the information provided in ``mocked_topology_state``. Then, repeatedly select a -server for a read operation using the mocked TopologyDescription and a "nearest" -ReadPreference ``iterations`` times. Once ``iterations`` selections have been -made, verify that each server was selected at a frequency within -``outcome.tolerance`` of the expected frequency contained in -``outcome.expected_frequencies`` for that server. If the expected frequency for -a given server is 1 or 0, then the observed frequency MUST be exactly equal to -the expected one. diff --git a/src/test/spec/json/server-selection/logging/load-balanced.json b/src/test/spec/json/server-selection/logging/load-balanced.json index 5fa156140..5855c4e99 100644 --- a/src/test/spec/json/server-selection/logging/load-balanced.json +++ b/src/test/spec/json/server-selection/logging/load-balanced.json @@ -1,6 +1,6 @@ { "description": "server-selection-logging", - "schemaVersion": "1.14", + "schemaVersion": "1.13", "runOnRequirements": [ { "topologies": [ diff --git a/src/test/spec/json/server-selection/logging/load-balanced.yml b/src/test/spec/json/server-selection/logging/load-balanced.yml index e32dcb559..ec3bc6102 100644 --- a/src/test/spec/json/server-selection/logging/load-balanced.yml +++ b/src/test/spec/json/server-selection/logging/load-balanced.yml @@ -1,6 +1,6 @@ description: "server-selection-logging" -schemaVersion: "1.14" +schemaVersion: "1.13" runOnRequirements: - topologies: diff --git a/src/test/spec/json/server-selection/logging/operation-id.json b/src/test/spec/json/server-selection/logging/operation-id.json index 276e4b8d6..6cdbcb3f5 100644 --- a/src/test/spec/json/server-selection/logging/operation-id.json +++ b/src/test/spec/json/server-selection/logging/operation-id.json @@ -47,6 +47,9 @@ } } ], + "_yamlAnchors": { + "namespace": "logging-tests.server-selection" + }, "tests": [ { "description": "Successful bulkWrite operation: log messages have operationIds", @@ -224,6 +227,190 @@ ] } ] + }, + { + "description": "Successful client bulkWrite operation: log messages have operationIds", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 2 + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "logging-tests.server-selection", + "document": { + "x": 1 + } + } + } + ] + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection started", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection succeeded", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + } + ] + } + ] + }, + { + "description": "Failed client bulkWrite operation: log messages have operationIds", + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "failPointClient", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": "alwaysOn", + "data": { + "failCommands": [ + "hello", + "ismaster" + ], + "appName": "loggingClient", + "closeConnection": true + } + } + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "serverDescriptionChangedEvent": { + "newDescription": { + "type": "Unknown" + } + } + }, + "count": 1 + } + }, + { + "name": "bulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "logging-tests.server-selection", + "document": { + "x": 1 + } + } + } + ] + }, + "expectError": { + "isClientError": true + } + } + ], + "expectLogMessages": [ + { + "client": "client", + "messages": [ + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection started", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "info", + "component": "serverSelection", + "data": { + "message": "Waiting for suitable server to become available", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + }, + { + "level": "debug", + "component": "serverSelection", + "data": { + "message": "Server selection failed", + "operationId": { + "$$type": [ + "int", + "long" + ] + }, + "operation": "bulkWrite" + } + } + ] + } + ] } ] } diff --git a/src/test/spec/json/server-selection/logging/operation-id.yml b/src/test/spec/json/server-selection/logging/operation-id.yml index 430e81a58..24e48f941 100644 --- a/src/test/spec/json/server-selection/logging/operation-id.yml +++ b/src/test/spec/json/server-selection/logging/operation-id.yml @@ -30,6 +30,9 @@ createEntities: - client: id: &failPointClient failPointClient +_yamlAnchors: + namespace: &namespace "logging-tests.server-selection" + tests: - description: "Successful bulkWrite operation: log messages have operationIds" operations: @@ -122,3 +125,97 @@ tests: operationId: { $$type: [int, long] } operation: insert + - description: "Successful client bulkWrite operation: log messages have operationIds" + runOnRequirements: + - minServerVersion: "8.0" # required for bulkWrite command + operations: + # ensure we've discovered the server so it is immediately available + # and no extra "waiting for suitable server" messages are emitted. + # expected topology events reflect initial server discovery and server connect event. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 2 + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + expectLogMessages: + - client: *client + messages: + - level: debug + component: serverSelection + data: + message: "Server selection started" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: debug + component: serverSelection + data: + message: "Server selection succeeded" + operationId: { $$type: [int, long] } + operation: bulkWrite + + - description: "Failed client bulkWrite operation: log messages have operationIds" + runOnRequirements: + - minServerVersion: "8.0" # required for bulkWrite command + operations: + # fail all hello/legacy hello commands for the main client. + - name: failPoint + object: testRunner + arguments: + client: *failPointClient + failPoint: + configureFailPoint: failCommand + mode: alwaysOn + data: + failCommands: ["hello", "ismaster"] + appName: *appName + closeConnection: true + # wait until we've marked the server unknown due + # to a failed heartbeat. + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + serverDescriptionChangedEvent: + newDescription: + type: Unknown + count: 1 + - name: bulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { x: 1 } + expectError: + isClientError: true # server selection timeout + expectLogMessages: + - client: *client + messages: + - level: debug + component: serverSelection + data: + message: "Server selection started" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: info + component: serverSelection + data: + message: "Waiting for suitable server to become available" + operationId: { $$type: [int, long] } + operation: bulkWrite + - level: debug + component: serverSelection + data: + message: "Server selection failed" + operationId: { $$type: [int, long] } + operation: bulkWrite diff --git a/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.json b/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.json new file mode 100644 index 000000000..76d3d774e --- /dev/null +++ b/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.json @@ -0,0 +1,18 @@ +{ + "topology_description": { + "type": "Unknown", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSGhost" + } + ] + }, + "operation": "read", + "read_preference": { + "mode": "Nearest" + }, + "suitable_servers": [], + "in_latency_window": [] +} diff --git a/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.yml b/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.yml new file mode 100644 index 000000000..2929a7bc0 --- /dev/null +++ b/src/test/spec/json/server-selection/server_selection/Unknown/read/ghost.yml @@ -0,0 +1,11 @@ +topology_description: + type: Unknown + servers: + - address: a:27017 + avg_rtt_ms: 5 + type: RSGhost +operation: read +read_preference: + mode: Nearest +suitable_servers: [] +in_latency_window: [] diff --git a/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.json b/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.json new file mode 100644 index 000000000..65caa4cd0 --- /dev/null +++ b/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.json @@ -0,0 +1,18 @@ +{ + "topology_description": { + "type": "Unknown", + "servers": [ + { + "address": "a:27017", + "avg_rtt_ms": 5, + "type": "RSGhost" + } + ] + }, + "operation": "write", + "read_preference": { + "mode": "Nearest" + }, + "suitable_servers": [], + "in_latency_window": [] +} diff --git a/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.yml b/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.yml new file mode 100644 index 000000000..ac794aa53 --- /dev/null +++ b/src/test/spec/json/server-selection/server_selection/Unknown/write/ghost.yml @@ -0,0 +1,11 @@ +topology_description: + type: Unknown + servers: + - address: a:27017 + avg_rtt_ms: 5 + type: RSGhost +operation: write +read_preference: + mode: Nearest +suitable_servers: [] +in_latency_window: [] diff --git a/src/test/spec/json/sessions/README.md b/src/test/spec/json/sessions/README.md new file mode 100644 index 000000000..8d817a59f --- /dev/null +++ b/src/test/spec/json/sessions/README.md @@ -0,0 +1,262 @@ +# Driver Session Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +sessions. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +### Snapshot session tests + +The default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow +configuration may lead to `SnapshotTooOld` errors. Drivers can work around this issue by increasing the server's +`minSnapshotHistoryWindowInSeconds` parameter, for example: + +```python +client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=600) +``` + +### Testing against servers that do not support sessions + +Since all regular 3.6+ servers support sessions, the prose tests which test for session non-support SHOULD use a +mongocryptd server as the test server (available with server versions 4.2+); however, if future versions of mongocryptd +support sessions or if mongocryptd is not a viable option for the driver implementing these tests, another server MAY be +substituted as long as it does not return a non-null value for `logicalSessionTimeoutMinutes`; in the event that no such +server is readily available, a mock server may be used as a last resort. + +As part of the test setup for these cases, create a `MongoClient` pointed at the test server with the options specified +in the test case and verify that the test server does NOT define a value for `logicalSessionTimeoutMinutes` by sending a +hello command and checking the response. + +## Prose tests + +### 1. Setting both `snapshot` and `causalConsistency` to true is not allowed + +Snapshot sessions tests require server of version 5.0 or higher and replica set or a sharded cluster deployment. + +- `client.startSession(snapshot = true, causalConsistency = true)` +- Assert that an error was raised by driver + +### 2. Pool is LIFO + +This test applies to drivers with session pools. + +- Call `MongoClient.startSession` twice to create two sessions, let us call them `A` and `B`. +- Call `A.endSession`, then `B.endSession`. +- Call `MongoClient.startSession`: the resulting session must have the same session ID as `B`. +- Call `MongoClient.startSession` again: the resulting session must have the same session ID as `A`. + +### 3. `$clusterTime` in commands + +- Register a command-started and a command-succeeded APM listener. If the driver has no APM support, inspect + commands/replies in another idiomatic way, such as monkey-patching or a mock server. +- Send a `ping` command to the server with the generic `runCommand` method. +- Assert that the command passed to the command-started listener includes `$clusterTime` if and only if `maxWireVersion` + > = 6. +- Record the `$clusterTime`, if any, in the reply passed to the command-succeeded APM listener. +- Send another `ping` command. +- Assert that `$clusterTime` in the command passed to the command-started listener, if any, equals the `$clusterTime` in + the previous server reply. + +Repeat the above for: + +- An aggregate command from the `aggregate` helper method +- A find command from the `find` helper method +- An insert command from the `insert_one` helper method + +### 4. Explicit and implicit session arguments + +- Register a command-started APM listener. If the driver has no APM support, inspect commands in another idiomatic way, + such as monkey-patching or a mock server. +- Create `client1` +- Get `database` from `client1` +- Get `collection` from `database` +- Start `session` from `client1` +- Call `collection.insertOne(session,...)` +- Assert that the command passed to the command-started listener contained the session `lsid` from `session`. +- Call `collection.insertOne(,...)` (*without* a session argument) +- Assert that the command passed to the command-started listener contained a session `lsid`. + +Repeat the above for all methods that take a session parameter. + +### 5. Session argument is for the right client + +- Create `client1` and `client2` +- Get `database` from `client1` +- Get `collection` from `database` +- Start `session` from `client2` +- Call `collection.insertOne(session,...)` +- Assert that an error was reported because `session` was not started from `client1` + +Repeat the above for all methods that take a session parameter. + +### 6. No further operations can be performed using a session after `endSession` has been called + +- Start a `session` +- End the `session` +- Call `collection.InsertOne(session, ...)` +- Assert that the proper error was reported + +Repeat the above for all methods that take a session parameter. + +If your driver implements a platform dependent idiomatic disposal pattern, test that also (if the idiomatic disposal +pattern calls `endSession` it would be sufficient to only test the disposal pattern since that ends up calling +`endSession`). + +### 7. Authenticating as multiple users suppresses implicit sessions + +Skip this test if your driver does not allow simultaneous authentication with multiple users. + +- Authenticate as two users +- Call `findOne` with no explicit session +- Capture the command sent to the server +- Assert that the command sent to the server does not have an `lsid` field + +### 8. Client-side cursor that exhausts the results on the initial query immediately returns the implicit session to the pool + +- Insert two documents into a collection +- Execute a find operation on the collection and iterate past the first document +- Assert that the implicit session is returned to the pool. This can be done in several ways: + - Track in-use count in the server session pool and assert that the count has dropped to zero + - Track the lsid used for the find operation (e.g. with APM) and then do another operation and assert that the same + lsid is used as for the find operation. + +### 9. Client-side cursor that exhausts the results after a `getMore` immediately returns the implicit session to the pool + +- Insert five documents into a collection +- Execute a find operation on the collection with batch size of 3 +- Iterate past the first four documents, forcing the final `getMore` operation +- Assert that the implicit session is returned to the pool prior to iterating past the last document + +### 10. No remaining sessions are checked out after each functional test + +At the end of every individual functional test of the driver, there SHOULD be an assertion that there are no remaining +sessions checked out from the pool. This may require changes to existing tests to ensure that they close any explicit +client sessions and any unexhausted cursors. + +### 11. For every combination of topology and readPreference, ensure that `find` and `getMore` both send the same session id + +- Insert three documents into a collection +- Execute a `find` operation on the collection with a batch size of 2 +- Assert that the server receives a non-zero lsid +- Iterate through enough documents (3) to force a `getMore` +- Assert that the server receives a non-zero lsid equal to the lsid that `find` sent. + +### 12. Session pool can be cleared after forking without calling `endSession` + +Skip this test if your driver does not allow forking. + +- Create ClientSession +- Record its lsid +- Delete it (so the lsid is pushed into the pool) +- Fork +- In the parent, create a ClientSession and assert its lsid is the same. +- In the child, create a ClientSession and assert its lsid is different. + +### 13. Existing sessions are not checked into a cleared pool after forking + +Skip this test if your driver does not allow forking. + +- Create ClientSession +- Record its lsid +- Fork +- In the parent, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is the same. +- In the child, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is different. + +### 14. Implicit sessions only allocate their server session after a successful connection checkout + +- Create a MongoClient with the following options: `maxPoolSize=1` and `retryWrites=true`. If testing against a sharded + deployment, the test runner MUST ensure that the MongoClient connects to only a single mongos host. +- Attach a command started listener that collects each command's lsid +- Initiate the following concurrent operations + - `insertOne({ }),` + - `deleteOne({ }),` + - `updateOne({ }, { $set: { a: 1 } }),` + - `bulkWrite([{ updateOne: { filter: { }, update: { $set: { a: 1 } } } }]),` + - `findOneAndDelete({ }),` + - `findOneAndUpdate({ }, { $set: { a: 1 } }),` + - `findOneAndReplace({ }, { a: 1 }),` + - `find().toArray()` +- Wait for all operations to complete successfully +- Assert the following across at least 5 retries of the above test: + - Drivers MUST assert that exactly one session is used for all operations at least once across the retries of this + test. + - Note that it's possible, although rare, for >1 server session to be used because the session is not released until + after the connection is checked in. + - Drivers MUST assert that the number of allocated sessions is strictly less than the number of concurrent operations + in every retry of this test. In this instance it would be less than (but NOT equal to) 8. + +### 15. `lsid` is added inside `$query` when using OP_QUERY + +This test only applies to drivers that have not implemented OP_MSG and still use OP_QUERY. + +- For a command to a mongos that includes a readPreference, verify that the `lsid` on query commands is added inside the + `$query` field, and NOT as a top-level field. + +### 16. Authenticating as a second user after starting a session results in a server error + +This test only applies to drivers that allow authentication to be changed on the fly. + +- Authenticate as the first user +- Start a session by calling `startSession` +- Authenticate as a second user +- Call `findOne` using the session as an explicit session +- Assert that the driver returned an error because multiple users are authenticated + +### 17. Driver verifies that the session is owned by the current user + +This test only applies to drivers that allow authentication to be changed on the fly. + +- Authenticate as user A +- Start a session by calling `startSession` +- Logout user A +- Authenticate as user B +- Call `findOne` using the session as an explicit session +- Assert that the driver returned an error because the session is owned by a different user + +### 18. Implicit session is ignored if connection does not support sessions + +Refer to [Testing against servers that do not support sessions](#testing-against-servers-that-do-not-support-sessions) +and configure a `MongoClient` with command monitoring enabled. + +- Send a read command to the server (e.g., `findOne`), ignoring any errors from the server response +- Check the corresponding `commandStarted` event: verify that `lsid` is not set +- Send a write command to the server (e.g., `insertOne`), ignoring any errors from the server response +- Check the corresponding `commandStarted` event: verify that lsid is not set + +### 19. Explicit session raises an error if connection does not support sessions + +Refer to [Testing against servers that do not support sessions](#testing-against-servers-that-do-not-support-sessions) +and configure a `MongoClient` with default options. + +- Create a new explicit session by calling `startSession` (this MUST NOT error) +- Attempt to send a read command to the server (e.g., `findOne`) with the explicit session passed in +- Assert that a client-side error is generated indicating that sessions are not supported +- Attempt to send a write command to the server (e.g., `insertOne`) with the explicit session passed in +- Assert that a client-side error is generated indicating that sessions are not supported + +### 20. Drivers do not gossip `$clusterTime` on SDAM commands. + +- Skip this test when connected to a deployment that does not support cluster times +- Create a client, C1, directly connected to a writable server and a small heartbeatFrequencyMS + - `c1 = MongoClient(directConnection=True, heartbeatFrequencyMS=10)` +- Run a ping command using C1 and record the `$clusterTime` in the response, as `clusterTime`. + - `clusterTime = c1.admin.command({"ping": 1})["$clusterTime"]` +- Using a separate client, C2, run an insert to advance the cluster time + - `c2.test.test.insert_one({"advance": "$clusterTime"})` +- Next, wait until the client C1 processes the next pair of SDAM heartbeat started + succeeded events. + - If possible, assert the SDAM heartbeats do not send `$clusterTime` +- Run a ping command using C1 and assert that `$clusterTime` sent is the same as the `clusterTime` recorded earlier. + This assertion proves that C1's `$clusterTime` was not advanced by gossiping through SDAM. + +## Changelog + +- 2025-02-24: Test drivers do not gossip $clusterTime on SDAM. +- 2024-05-08: Migrated from reStructuredText to Markdown. +- 2019-05-15: Initial version. +- 2021-06-15: Added snapshot-session tests. Introduced legacy and unified folders. +- 2021-07-30: Use numbering for prose test +- 2022-02-11: Convert legacy tests to unified format +- 2022-06-13: Relocate prose test from spec document and apply new ordering +- 2023-02-24: Fix formatting and add new prose tests 18 and 19 diff --git a/src/test/spec/json/sessions/README.rst b/src/test/spec/json/sessions/README.rst deleted file mode 100644 index e1b565590..000000000 --- a/src/test/spec/json/sessions/README.rst +++ /dev/null @@ -1,276 +0,0 @@ -==================== -Driver Session Tests -==================== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests -meant to exercise a driver's implementation of sessions. These tests utilize the -`Unified Test Format <../../unified-test-format/unified-test-format.rst>`__. - -Snapshot session tests -~~~~~~~~~~~~~~~~~~~~~~ -The default snapshot history window on the server is 5 minutes. Running the test in debug mode, or in any other slow configuration -may lead to `SnapshotTooOld` errors. Drivers can work around this issue by increasing the server's `minSnapshotHistoryWindowInSeconds` parameter, for example: - -.. code:: python - - client.admin.command('setParameter', 1, minSnapshotHistoryWindowInSeconds=600) - -Testing against servers that do not support sessions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Since all regular 3.6+ servers support sessions, the prose tests which test for session non-support SHOULD -use a mongocryptd server as the test server (available with server versions 4.2+); however, if future versions of mongocryptd -support sessions or if mongocryptd is not a viable option for the driver implementing these tests, another server MAY be -substituted as long as it does not return a non-null value for ``logicalSessionTimeoutMinutes``; -in the event that no such server is readily available, a mock server may be used as a last resort. - -As part of the test setup for these cases, create a ``MongoClient`` pointed at the test server with the options -specified in the test case and verify that the test server does NOT define a value for ``logicalSessionTimeoutMinutes`` -by sending a hello command and checking the response. - -Prose tests -=========== - -1. Setting both ``snapshot`` and ``causalConsistency`` to true is not allowed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Snapshot sessions tests require server of version 5.0 or higher and -replica set or a sharded cluster deployment. - -* ``client.startSession(snapshot = true, causalConsistency = true)`` -* Assert that an error was raised by driver - -2. Pool is LIFO -~~~~~~~~~~~~~~~ - -This test applies to drivers with session pools. - -* Call ``MongoClient.startSession`` twice to create two sessions, let us call them ``A`` and ``B``. -* Call ``A.endSession``, then ``B.endSession``. -* Call ``MongoClient.startSession``: the resulting session must have the same session ID as ``B``. -* Call ``MongoClient.startSession`` again: the resulting session must have the same session ID as ``A``. - -3. ``$clusterTime`` in commands -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Turn ``heartbeatFrequencyMS`` up to a very large number. -* Register a command-started and a command-succeeded APM listener. If the driver has no APM support, inspect commands/replies in another idiomatic way, such as monkey-patching or a mock server. -* Send a ``ping`` command to the server with the generic ``runCommand`` method. -* Assert that the command passed to the command-started listener includes ``$clusterTime`` if and only if ``maxWireVersion`` >= 6. -* Record the ``$clusterTime``, if any, in the reply passed to the command-succeeded APM listener. -* Send another ``ping`` command. -* Assert that ``$clusterTime`` in the command passed to the command-started listener, if any, equals the ``$clusterTime`` in the previous server reply. (Turning ``heartbeatFrequencyMS`` up prevents an intervening heartbeat from advancing the ``$clusterTime`` between these final two steps.) - -Repeat the above for: - -* An aggregate command from the ``aggregate`` helper method -* A find command from the ``find`` helper method -* An insert command from the ``insert_one`` helper method - -4. Explicit and implicit session arguments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Register a command-started APM listener. If the driver has no APM support, inspect commands in another idiomatic way, such as monkey-patching or a mock server. -* Create ``client1`` -* Get ``database`` from ``client1`` -* Get ``collection`` from ``database`` -* Start ``session`` from ``client1`` -* Call ``collection.insertOne(session,...)`` -* Assert that the command passed to the command-started listener contained the session ``lsid`` from ``session``. -* Call ``collection.insertOne(,...)`` (*without* a session argument) -* Assert that the command passed to the command-started listener contained a session ``lsid``. - -Repeat the above for all methods that take a session parameter. - -5. Session argument is for the right client -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Create ``client1`` and ``client2`` -* Get ``database`` from ``client1`` -* Get ``collection`` from ``database`` -* Start ``session`` from ``client2`` -* Call ``collection.insertOne(session,...)`` -* Assert that an error was reported because ``session`` was not started from ``client1`` - -Repeat the above for all methods that take a session parameter. - -6. No further operations can be performed using a session after ``endSession`` has been called -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Start a ``session`` -* End the ``session`` -* Call ``collection.InsertOne(session, ...)`` -* Assert that the proper error was reported - -Repeat the above for all methods that take a session parameter. - -If your driver implements a platform dependent idiomatic disposal pattern, test -that also (if the idiomatic disposal pattern calls ``endSession`` it would be -sufficient to only test the disposal pattern since that ends up calling -``endSession``). - -7. Authenticating as multiple users suppresses implicit sessions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Skip this test if your driver does not allow simultaneous authentication with multiple users. - -* Authenticate as two users -* Call ``findOne`` with no explicit session -* Capture the command sent to the server -* Assert that the command sent to the server does not have an ``lsid`` field - -8. Client-side cursor that exhausts the results on the initial query immediately returns the implicit session to the pool -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Insert two documents into a collection -* Execute a find operation on the collection and iterate past the first document -* Assert that the implicit session is returned to the pool. This can be done in several ways: - - * Track in-use count in the server session pool and assert that the count has dropped to zero - * Track the lsid used for the find operation (e.g. with APM) and then do another operation and - assert that the same lsid is used as for the find operation. - -9. Client-side cursor that exhausts the results after a ``getMore`` immediately returns the implicit session to the pool -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Insert five documents into a collection -* Execute a find operation on the collection with batch size of 3 -* Iterate past the first four documents, forcing the final ``getMore`` operation -* Assert that the implicit session is returned to the pool prior to iterating past the last document - -10. No remaining sessions are checked out after each functional test -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -At the end of every individual functional test of the driver, there SHOULD be an -assertion that there are no remaining sessions checked out from the pool. This -may require changes to existing tests to ensure that they close any explicit -client sessions and any unexhausted cursors. - -11. For every combination of topology and readPreference, ensure that ``find`` and ``getMore`` both send the same session id -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Insert three documents into a collection -* Execute a ``find`` operation on the collection with a batch size of 2 -* Assert that the server receives a non-zero lsid -* Iterate through enough documents (3) to force a ``getMore`` -* Assert that the server receives a non-zero lsid equal to the lsid that ``find`` sent. - -12. Session pool can be cleared after forking without calling ``endSession`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Skip this test if your driver does not allow forking. - -* Create ClientSession -* Record its lsid -* Delete it (so the lsid is pushed into the pool) -* Fork -* In the parent, create a ClientSession and assert its lsid is the same. -* In the child, create a ClientSession and assert its lsid is different. - -13. Existing sessions are not checked into a cleared pool after forking -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Skip this test if your driver does not allow forking. - -* Create ClientSession -* Record its lsid -* Fork -* In the parent, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is the same. -* In the child, return the ClientSession to the pool, create a new ClientSession, and assert its lsid is different. - -14. Implicit sessions only allocate their server session after a successful connection checkout -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Create a MongoClient with the following options: ``maxPoolSize=1`` and ``retryWrites=true``. If testing against a sharded deployment, the test runner MUST ensure that the MongoClient connects to only a single mongos host. -* Attach a command started listener that collects each command's lsid -* Initiate the following concurrent operations - - * ``insertOne({ }),`` - * ``deleteOne({ }),`` - * ``updateOne({ }, { $set: { a: 1 } }),`` - * ``bulkWrite([{ updateOne: { filter: { }, update: { $set: { a: 1 } } } }]),`` - * ``findOneAndDelete({ }),`` - * ``findOneAndUpdate({ }, { $set: { a: 1 } }),`` - * ``findOneAndReplace({ }, { a: 1 }),`` - * ``find().toArray()`` - -* Wait for all operations to complete successfully -* Assert the following across at least 5 retries of the above test: - - * Drivers MUST assert that exactly one session is used for all operations at - least once across the retries of this test. - * Note that it's possible, although rare, for >1 server session to be used - because the session is not released until after the connection is checked in. - * Drivers MUST assert that the number of allocated sessions is strictly less - than the number of concurrent operations in every retry of this test. In - this instance it would be less than (but NOT equal to) 8. - -15. ``lsid`` is added inside ``$query`` when using OP_QUERY -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This test only applies to drivers that have not implemented OP_MSG and still use OP_QUERY. - -* For a command to a mongos that includes a readPreference, verify that the - ``lsid`` on query commands is added inside the ``$query`` field, and NOT as a - top-level field. - -16. Authenticating as a second user after starting a session results in a server error -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This test only applies to drivers that allow authentication to be changed on the fly. - -* Authenticate as the first user -* Start a session by calling ``startSession`` -* Authenticate as a second user -* Call ``findOne`` using the session as an explicit session -* Assert that the driver returned an error because multiple users are authenticated - -17. Driver verifies that the session is owned by the current user -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This test only applies to drivers that allow authentication to be changed on the fly. - -* Authenticate as user A -* Start a session by calling ``startSession`` -* Logout user A -* Authenticate as user B -* Call ``findOne`` using the session as an explicit session -* Assert that the driver returned an error because the session is owned by a different user - -18. Implicit session is ignored if connection does not support sessions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Refer to `Testing against servers that do not support sessions`_ and configure a ``MongoClient`` -with command monitoring enabled. - -* Send a read command to the server (e.g., ``findOne``), ignoring any errors from the server response -* Check the corresponding ``commandStarted`` event: verify that ``lsid`` is not set -* Send a write command to the server (e.g., ``insertOne``), ignoring any errors from the server response -* Check the corresponding ``commandStarted`` event: verify that lsid is not set - -19. Explicit session raises an error if connection does not support sessions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Refer to `Testing against servers that do not support sessions`_ and configure a ``MongoClient`` -with default options. - -* Create a new explicit session by calling ``startSession`` (this MUST NOT error) -* Attempt to send a read command to the server (e.g., ``findOne``) with the explicit session passed in -* Assert that a client-side error is generated indicating that sessions are not supported -* Attempt to send a write command to the server (e.g., ``insertOne``) with the explicit session passed in -* Assert that a client-side error is generated indicating that sessions are not supported - -Changelog -========= - -:2019-05-15: Initial version. -:2021-06-15: Added snapshot-session tests. Introduced legacy and unified folders. -:2021-07-30: Use numbering for prose test -:2022-02-11: Convert legacy tests to unified format -:2022-06-13: Relocate prose test from spec document and apply new ordering -:2023-02-24: Fix formatting and add new prose tests 18 and 19 diff --git a/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.json b/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.json index 6aa1da1df..d7a1c6aba 100644 --- a/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.json +++ b/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.json @@ -347,7 +347,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -375,7 +377,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$sessionLsid": "session0" }, @@ -627,7 +631,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$type": "object" }, @@ -655,7 +661,9 @@ "x": 1 } }, - "new": false, + "new": { + "$$unsetOrMatches": false + }, "lsid": { "$$type": "object" }, diff --git a/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.yml b/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.yml index b7f2917ef..0fb032bde 100644 --- a/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.yml +++ b/src/test/spec/json/sessions/driver-sessions-dirty-session-errors.yml @@ -164,7 +164,7 @@ tests: findAndModify: *collection0Name query: { _id: 1 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$sessionLsid: *session0 } txnNumber: 1 readConcern: { $$exists: false } @@ -255,7 +255,7 @@ tests: findAndModify: *collection0Name query: { _id: 1 } update: { $inc: { x: 1 } } - new: false + new: { $$unsetOrMatches: false } lsid: { $$type: object } txnNumber: 1 readConcern: { $$exists: false } diff --git a/src/test/spec/json/testdata/client-side-encryption/data/lookup/key-doc.json b/src/test/spec/json/testdata/client-side-encryption/data/lookup/key-doc.json new file mode 100644 index 000000000..566b56c35 --- /dev/null +++ b/src/test/spec/json/testdata/client-side-encryption/data/lookup/key-doc.json @@ -0,0 +1,30 @@ +{ + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } +} diff --git a/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle.json b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle.json new file mode 100644 index 000000000..29ac9ad5d --- /dev/null +++ b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle.json @@ -0,0 +1,19 @@ +{ + "properties": { + "csfle": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" +} diff --git a/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle2.json b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle2.json new file mode 100644 index 000000000..3f1c02781 --- /dev/null +++ b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-csfle2.json @@ -0,0 +1,19 @@ +{ + "properties": { + "csfle2": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" +} diff --git a/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe.json b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe.json new file mode 100644 index 000000000..9428ea1b4 --- /dev/null +++ b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe.json @@ -0,0 +1,20 @@ +{ + "escCollection": "enxcol_.qe.esc", + "ecocCollection": "enxcol_.qe.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "qe", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + } + ] +} diff --git a/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe2.json b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe2.json new file mode 100644 index 000000000..77d5bd37c --- /dev/null +++ b/src/test/spec/json/testdata/client-side-encryption/data/lookup/schema-qe2.json @@ -0,0 +1,20 @@ +{ + "escCollection": "enxcol_.qe2.esc", + "ecocCollection": "enxcol_.qe2.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "qe2", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + } + ] +} diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Date.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Date.json index 97a2b2d4e..defa6e37f 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Date.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Date.json @@ -10,10 +10,13 @@ "path": "encryptedDate", "bsonType": "date", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" }, @@ -30,4 +33,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalNoPrecision.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalNoPrecision.json index 4d284475f..dbe28e9c1 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalNoPrecision.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalNoPrecision.json @@ -10,14 +10,17 @@ "path": "encryptedDecimalNoPrecision", "bsonType": "decimal", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" } } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalPrecision.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalPrecision.json index 53449182b..538ab20f0 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalPrecision.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DecimalPrecision.json @@ -10,10 +10,13 @@ "path": "encryptedDecimalPrecision", "bsonType": "decimal", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" }, @@ -29,4 +32,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoubleNoPrecision.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoubleNoPrecision.json index b478a772d..fb4f46d37 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoubleNoPrecision.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoubleNoPrecision.json @@ -10,14 +10,17 @@ "path": "encryptedDoubleNoPrecision", "bsonType": "double", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" } } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoublePrecision.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoublePrecision.json index 395a36968..07d1c84d6 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoublePrecision.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-DoublePrecision.json @@ -10,10 +10,13 @@ "path": "encryptedDoublePrecision", "bsonType": "double", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" }, @@ -29,4 +32,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Int.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Int.json index 61b7082df..4f0b4854e 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Int.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Int.json @@ -10,10 +10,13 @@ "path": "encryptedInt", "bsonType": "int", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" }, @@ -26,4 +29,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Long.json b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Long.json index b18b84b6e..32fe1ea15 100644 --- a/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Long.json +++ b/src/test/spec/json/testdata/client-side-encryption/data/range-encryptedFields-Long.json @@ -10,10 +10,13 @@ "path": "encryptedLong", "bsonType": "long", "queries": { - "queryType": "rangePreview", + "queryType": "range", "contention": { "$numberLong": "0" }, + "trimFactor": { + "$numberInt": "1" + }, "sparsity": { "$numberLong": "1" }, @@ -26,4 +29,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/test/spec/json/transactions-convenient-api/README.md b/src/test/spec/json/transactions-convenient-api/README.md new file mode 100644 index 000000000..a797a3182 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/README.md @@ -0,0 +1,48 @@ +# Convenient API for Transactions Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +the Convenient API for Transactions spec. These tests utilize the +[Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +## Prose Tests + +### Callback Raises a Custom Error + +Write a callback that raises a custom exception or error that does not include either UnknownTransactionCommitResult or +TransientTransactionError error labels. Execute this callback using `withTransaction` and assert that the callback's +error bypasses any retry logic within `withTransaction` and is propagated to the caller of `withTransaction`. + +### Callback Returns a Value + +Write a callback that returns a custom value (e.g. boolean, string, object). Execute this callback using +`withTransaction` and assert that the callback's return value is propagated to the caller of `withTransaction`. + +### Retry Timeout is Enforced + +Drivers should test that `withTransaction` enforces a non-configurable timeout before retrying both commits and entire +transactions. Specifically, three cases should be checked: + +- If the callback raises an error with the TransientTransactionError label and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. +- If committing raises an error with the UnknownTransactionCommitResult label, and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. +- If committing raises an error with the TransientTransactionError label and the retry timeout has been exceeded, + `withTransaction` should propagate the error to its caller. This case may occur if the commit was internally retried + against a new primary after a failover and the second primary returned a NoSuchTransaction error response. + +If possible, drivers should implement these tests without requiring the test runner to block for the full duration of +the retry timeout. This might be done by internally modifying the timeout value used by `withTransaction` with some +private API or using a mock timer. + +## Changelog + +- 2024-09-06: Migrated from reStructuredText to Markdown. +- 2024-02-08: Converted legacy tests to unified format. +- 2021-04-29: Remove text about write concern timeouts from prose test. diff --git a/src/test/spec/json/transactions-convenient-api/README.rst b/src/test/spec/json/transactions-convenient-api/README.rst deleted file mode 100644 index 4708a4462..000000000 --- a/src/test/spec/json/transactions-convenient-api/README.rst +++ /dev/null @@ -1,222 +0,0 @@ -===================================== -Convenient API for Transactions Tests -===================================== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in this directory are platform-independent tests that -drivers can use to prove their conformance to the Convenient API for -Transactions spec. They are designed with the intention of sharing some -test-runner code with the CRUD, Command Monitoring, and Transaction spec tests. - -Several prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Server Fail Point -================= - -See: `Server Fail Point <../../transactions/tests#server-fail-point>`_ in the -Transactions spec test suite. - -Test Format -=========== - -Each YAML file has the following keys: - -- ``runOn`` (optional): An array of server version and/or topology requirements - for which the tests can be run. If the test environment satisfies one or more - of these requirements, the tests may be executed; otherwise, this file should - be skipped. If this field is omitted, the tests can be assumed to have no - particular requirements and should be executed. Each element will have some or - all of the following fields: - - - ``minServerVersion`` (optional): The minimum server version (inclusive) - required to successfully run the tests. If this field is omitted, it should - be assumed that there is no lower bound on the required server version. - - - ``maxServerVersion`` (optional): The maximum server version (inclusive) - against which the tests can be run successfully. If this field is omitted, - it should be assumed that there is no upper bound on the required server - version. - - - ``topology`` (optional): An array of server topologies against which the - tests can be run successfully. Valid topologies are "single", "replicaset", - and "sharded". If this field is omitted, the default is all topologies (i.e. - ``["single", "replicaset", "sharded"]``). - -- ``database_name`` and ``collection_name``: The database and collection to use - for testing. - -- ``data``: The data that should exist in the collection under test before each - test run. - -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some or all of the following fields: - - - ``description``: The name of the test. - - - ``skipReason`` (optional): If present, the test should be skipped and the - string value will specify a reason. - - - ``failPoint`` (optional): The ``configureFailPoint`` command document to run - to configure a fail point on the primary server. This option and - ``useMultipleMongoses: true`` are mutually exclusive. - - - ``useMultipleMongoses`` (optional): If ``true``, the MongoClient for this - test should be initialized with multiple mongos seed addresses. If ``false`` - or omitted, only a single mongos address should be specified. This field has - no effect for non-sharded topologies. - - - ``clientOptions`` (optional): Names and values of options to pass to - ``MongoClient()``. - - - ``sessionOptions`` (optional): Names and values of options to pass to - ``MongoClient.startSession()``. - - - ``operations``: Array of documents, each describing an operation to be - executed. Each document has the following fields: - - - ``name``: The name of the operation on ``object``. - - - ``object``: The name of the object on which to perform the operation. The - value will be either "collection" or "session0". - - - ``arguments`` (optional): Names and values of arguments to pass to the - operation. - - - ``result`` (optional): The return value from the operation. This will - correspond to an operation's result object as defined in the CRUD - specification. If the operation is expected to return an error, the - ``result`` is a single document that has one or more of the following - fields: - - - ``errorContains``: A substring of the expected error message. - - - ``errorCodeName``: The expected "codeName" field in the server - error response. - - - ``errorLabelsContain``: A list of error label strings that the - error is expected to have. - - - ``errorLabelsOmit``: A list of error label strings that the - error is expected not to have. - - - ``expectations`` (optional): List of command-started events. - - - ``outcome``: Document describing the return value and/or expected state of - the collection after the operation is executed. Contains the following - fields: - - - ``collection``: - - - ``data``: The data that should exist in the collection after the - operations have run. - -``withTransaction`` Operation -````````````````````````````` - -These tests introduce a ``withTransaction`` operation, which may have the -following fields: - -- ``callback``: Document containing the following field: - - - ``operations``: Array of documents, each describing an operation to be - executed. Elements in this array will follow the same structure as the - ``operations`` field defined above (and in the CRUD and Transactions specs). - - Note that drivers are expected to evaluate ``error`` and ``result`` - assertions when executing operations within ``callback.operations``. - -- ``options`` (optional): Names and values of options to pass to - ``withTransaction()``, which will in turn be used for ``startTransaction()``. - -Use as Integration Tests -======================== - -Testing against a replica set will require server version 4.0.0 or later. The -replica set should include a primary, a secondary, and an arbiter. Including a -secondary ensures that server selection in a transaction works properly. -Including an arbiter helps ensure that no new bugs have been introduced related -to arbiters. - -Testing against a sharded cluster will require server version 4.1.6 or later. -A driver that implements support for sharded transactions MUST also run these -tests against a MongoDB sharded cluster with multiple mongoses. Including -multiple mongoses (and initializing the MongoClient with multiple mongos seeds!) -ensures that mongos transaction pinning works properly. - -See: `Use as Integration Tests <../../transactions/tests#use-as-integration-tests>`_ -in the Transactions spec test suite for instructions on executing each test. - -Take note of the following: - -- Most tests will consist of a single "withTransaction" operation to be invoked - on the "session0" object. The ``callback`` argument of that operation will - resemble the ``operations`` array found in transaction spec tests. - -Command-Started Events -`````````````````````` - -See: `Command-Started Events <../../transactions/tests#command-started-events>`_ -in the Transactions spec test suite for instructions on asserting -command-started events. - -Prose Tests -=========== - -Callback Raises a Custom Error -`````````````````````````````` - -Write a callback that raises a custom exception or error that does not include -either UnknownTransactionCommitResult or TransientTransactionError error labels. -Execute this callback using ``withTransaction`` and assert that the callback's -error bypasses any retry logic within ``withTransaction`` and is propagated to -the caller of ``withTransaction``. - -Callback Returns a Value -```````````````````````` - -Write a callback that returns a custom value (e.g. boolean, string, object). -Execute this callback using ``withTransaction`` and assert that the callback's -return value is propagated to the caller of ``withTransaction``. - -Retry Timeout is Enforced -````````````````````````` - -Drivers should test that ``withTransaction`` enforces a non-configurable timeout -before retrying both commits and entire transactions. Specifically, three cases -should be checked: - - * If the callback raises an error with the TransientTransactionError label and - the retry timeout has been exceeded, ``withTransaction`` should propagate the - error to its caller. - * If committing raises an error with the UnknownTransactionCommitResult label, - and the retry timeout has been exceeded, ``withTransaction`` should - propagate the error to its caller. - * If committing raises an error with the TransientTransactionError label and - the retry timeout has been exceeded, ``withTransaction`` should propagate the - error to its caller. This case may occur if the commit was internally retried - against a new primary after a failover and the second primary returned a - NoSuchTransaction error response. - - If possible, drivers should implement these tests without requiring the test - runner to block for the full duration of the retry timeout. This might be done - by internally modifying the timeout value used by ``withTransaction`` with some - private API or using a mock timer. - -Changelog -========= - -:2021-04-29: Remove text about write concern timeouts from prose test. - -:2019-03-01: Add top-level ``runOn`` field to denote server version and/or - topology requirements requirements for the test file. Removes the - ``minServerVersion`` top-level field, which is now expressed within - ``runOn`` elements. - - Add test-level ``useMultipleMongoses`` field. diff --git a/src/test/spec/json/transactions-convenient-api/callback-aborts.json b/src/test/spec/json/transactions-convenient-api/callback-aborts.json deleted file mode 100644 index 2a3038e8b..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-aborts.json +++ /dev/null @@ -1,244 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "withTransaction succeeds if callback aborts", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "withTransaction succeeds if callback aborts with no ops", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "abortTransaction", - "object": "session0" - } - ] - } - } - } - ], - "expectations": [], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "withTransaction still succeeds if callback aborts and runs extra op", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "autocommit": null, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/callback-aborts.yml b/src/test/spec/json/transactions-convenient-api/callback-aborts.yml deleted file mode 100644 index fcb4f3916..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-aborts.yml +++ /dev/null @@ -1,170 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - # Session state will be ABORTED when callback returns to withTransaction - description: withTransaction succeeds if callback aborts - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: abortTransaction - object: session0 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: abortTransaction - database_name: admin - outcome: - collection: - data: [] - - - # Session state will be ABORTED when callback returns to withTransaction - description: withTransaction succeeds if callback aborts with no ops - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: abortTransaction - object: session0 - expectations: [] - outcome: - collection: - data: [] - - - # Session state will be NO_TXN when callback returns to withTransaction - description: withTransaction still succeeds if callback aborts and runs extra op - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: abortTransaction - object: session0 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 2 } - result: - insertedId: 2 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: abortTransaction - database_name: admin - - - command_started_event: - command: - # This test is agnostic about retryWrites, so we do not assert the - # txnNumber. If retryWrites=true, the txnNumber will be incremented - # from the value used in the previous transaction; otherwise, the - # field will not be present at all. - insert: *collection_name - documents: - - { _id: 2 } - ordered: true - lsid: session0 - # omitted fields - autocommit: ~ - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - outcome: - collection: - data: - - { _id: 2 } diff --git a/src/test/spec/json/transactions-convenient-api/callback-commits.json b/src/test/spec/json/transactions-convenient-api/callback-commits.json deleted file mode 100644 index 4abbbdd0e..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-commits.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "withTransaction succeeds if callback commits", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "withTransaction still succeeds if callback commits and runs extra op", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "lsid": "session0", - "autocommit": null, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/callback-commits.yml b/src/test/spec/json/transactions-convenient-api/callback-commits.yml deleted file mode 100644 index 6eb6bc6fc..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-commits.yml +++ /dev/null @@ -1,204 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - # Session state will be COMMITTED when callback returns to withTransaction - description: withTransaction succeeds if callback commits - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 2 } - result: - insertedId: 2 - - - name: commitTransaction - object: session0 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 2 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - { _id: 2 } - - - # Session state will be NO_TXN when callback returns to withTransaction - description: withTransaction still succeeds if callback commits and runs extra op - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 2 } - result: - insertedId: 2 - - - name: commitTransaction - object: session0 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 3 } - result: - insertedId: 3 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 2 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - # This test is agnostic about retryWrites, so we do not assert the - # txnNumber. If retryWrites=true, the txnNumber will be incremented - # from the value used in the previous transaction; otherwise, the - # field will not be present at all. - insert: *collection_name - documents: - - { _id: 3 } - ordered: true - lsid: session0 - # omitted fields - autocommit: ~ - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - outcome: - collection: - data: - - { _id: 1 } - - { _id: 2 } - - { _id: 3 } diff --git a/src/test/spec/json/transactions-convenient-api/callback-retry.json b/src/test/spec/json/transactions-convenient-api/callback-retry.json deleted file mode 100644 index a0391c1b5..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-retry.json +++ /dev/null @@ -1,315 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "callback succeeds after multiple connection errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "callback is not retried after non-transient error (DuplicateKeyError)", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ] - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ], - "errorContains": "E11000" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/callback-retry.yml b/src/test/spec/json/transactions-convenient-api/callback-retry.yml deleted file mode 100644 index edff016bd..000000000 --- a/src/test/spec/json/transactions-convenient-api/callback-retry.yml +++ /dev/null @@ -1,215 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: callback succeeds after multiple connection errors - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["insert"] - closeConnection: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - # We do not assert the result here, as insertOne will fail for - # the first two executions of the callback before ultimately - # succeeding and returning a result. Asserting the state of the - # output collection after the test is sufficient. - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: abortTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # second transaction will be causally consistent with the first - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "2" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "2" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: abortTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # third transaction will be causally consistent with the second - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "3" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "3" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - - description: callback is not retried after non-transient error (DuplicateKeyError) - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - # DuplicateKey error code included in the bulk write error message - # returned by the server - errorContains: E11000 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: abortTransaction - database_name: admin - outcome: - collection: - data: [] diff --git a/src/test/spec/json/transactions-convenient-api/commit-retry.json b/src/test/spec/json/transactions-convenient-api/commit-retry.json deleted file mode 100644 index 02e38460d..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-retry.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commitTransaction succeeds after multiple connection errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction retry only overwrites write concern w option", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - }, - "options": { - "writeConcern": { - "w": 2, - "j": true, - "wtimeout": 5000 - } - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 2, - "j": true, - "wtimeout": 5000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "j": true, - "wtimeout": 5000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "j": true, - "wtimeout": 5000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commit is retried after commitTransaction UnknownTransactionCommitResult (NotWritablePrimary)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 10107, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commit is not retried after MaxTimeMSExpired error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 50 - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - }, - "options": { - "maxCommitTimeMS": 60000 - } - }, - "result": { - "errorCodeName": "MaxTimeMSExpired", - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "maxTimeMS": 60000, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/commit-retry.yml b/src/test/spec/json/transactions-convenient-api/commit-retry.yml deleted file mode 100644 index 74c03dd9f..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-retry.yml +++ /dev/null @@ -1,325 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: commitTransaction succeeds after multiple connection errors - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - operations: - - &withTransaction - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - - description: commitTransaction retry only overwrites write concern w option - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - options: - writeConcern: { w: 2, j: true, wtimeout: 5000 } - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 2, j: true, wtimeout: 5000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, j: true, wtimeout: 5000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, j: true, wtimeout: 5000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - - description: commit is retried after commitTransaction UnknownTransactionCommitResult (NotWritablePrimary) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 10107 # NotWritablePrimary - errorLabels: ["RetryableWriteError"] # SPEC-1565 - closeConnection: false - operations: - - *withTransaction - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - - description: commit is not retried after MaxTimeMSExpired error - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 50 # MaxTimeMSExpired - operations: - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - options: - maxCommitTimeMS: 60000 - result: - errorCodeName: MaxTimeMSExpired - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - maxTimeMS: 60000 - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - # In reality, the outcome of the commit is unknown but we fabricate - # the error with failCommand.errorCode which does not apply the commit - # operation. - data: [] diff --git a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.json b/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.json deleted file mode 100644 index 7663bb54e..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.6", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "transaction is retried after commitTransaction TransientTransactionError (PreparedTransactionInProgress)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 267, - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.yml b/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.yml deleted file mode 100644 index 10f51d079..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror-4.2.yml +++ /dev/null @@ -1,139 +0,0 @@ -runOn: - - - minServerVersion: "4.1.6" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -# These tests use error codes where the TransientTransactionError label will be -# applied to the error response for commitTransaction. This will cause the -# entire transaction to be retried instead of commitTransaction. -# -# See: https://github.com/mongodb/mongo/blob/r4.1.6/src/mongo/db/handle_request_response.cpp -tests: - - - description: transaction is retried after commitTransaction TransientTransactionError (PreparedTransactionInProgress) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 267 # PreparedTransactionInProgress - closeConnection: false - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # second transaction will be causally consistent with the first - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "2" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "2" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # third transaction will be causally consistent with the second - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "3" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "3" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } diff --git a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.json b/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.json deleted file mode 100644 index 18becbe09..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.json +++ /dev/null @@ -1,725 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "transaction is retried after commitTransaction TransientTransactionError (LockTimeout)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 24, - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "transaction is retried after commitTransaction TransientTransactionError (WriteConflict)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 112, - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "transaction is retried after commitTransaction TransientTransactionError (SnapshotUnavailable)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 246, - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "transaction is retried after commitTransaction TransientTransactionError (NoSuchTransaction)", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 251, - "closeConnection": false - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.yml b/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.yml deleted file mode 100644 index db1f64ae5..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-transienttransactionerror.yml +++ /dev/null @@ -1,175 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -# These tests use error codes where the TransientTransactionError label will be -# applied to the error response for commitTransaction. This will cause the -# entire transaction to be retried instead of commitTransaction. -# -# See: https://github.com/mongodb/mongo/blob/r4.1.6/src/mongo/db/handle_request_response.cpp -tests: - - - description: transaction is retried after commitTransaction TransientTransactionError (LockTimeout) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 24 # LockTimeout - closeConnection: false - operations: &operations - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - expectations: &expectations - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # second transaction will be causally consistent with the first - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "2" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "2" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - # third transaction will be causally consistent with the second - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented when retrying the transaction - txnNumber: { $numberLong: "3" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "3" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: &outcome - collection: - data: - - { _id: 1 } - - - description: transaction is retried after commitTransaction TransientTransactionError (WriteConflict) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 112 # WriteConflict - closeConnection: false - operations: *operations - expectations: *expectations - outcome: *outcome - - - description: transaction is retried after commitTransaction TransientTransactionError (SnapshotUnavailable) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 246 # SnapshotUnavailable - closeConnection: false - operations: *operations - expectations: *expectations - outcome: *outcome - - - description: transaction is retried after commitTransaction TransientTransactionError (NoSuchTransaction) - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 251 # NoSuchTransaction - closeConnection: false - operations: *operations - expectations: *expectations - outcome: *outcome diff --git a/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.json b/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.json deleted file mode 100644 index fbad64554..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.json +++ /dev/null @@ -1,602 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commitTransaction is retried after WriteConcernFailed timeout error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 64, - "codeName": "WriteConcernFailed", - "errmsg": "waiting for replication timed out", - "errInfo": { - "wtimeout": true - } - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction is retried after WriteConcernFailed non-timeout error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 64, - "codeName": "WriteConcernFailed", - "errmsg": "multiple errors reported" - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction is not retried after UnknownReplWriteConcern error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 79, - "codeName": "UnknownReplWriteConcern", - "errmsg": "No write concern mode named 'foo' found in replica set configuration" - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - }, - "result": { - "errorCodeName": "UnknownReplWriteConcern", - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction is not retried after UnsatisfiableWriteConcern error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 100, - "codeName": "UnsatisfiableWriteConcern", - "errmsg": "Not enough data-bearing nodes" - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - }, - "result": { - "errorCodeName": "UnsatisfiableWriteConcern", - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction is not retried after MaxTimeMSExpired error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 50, - "codeName": "MaxTimeMSExpired", - "errmsg": "operation exceeded time limit" - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - }, - "result": { - "errorCodeName": "MaxTimeMSExpired", - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.yml b/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.yml deleted file mode 100644 index 5c5f364d5..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit-writeconcernerror.yml +++ /dev/null @@ -1,216 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: commitTransaction is retried after WriteConcernFailed timeout error - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - # Do not specify closeConnection: false, since that would conflict - # with writeConcernError (see: SERVER-39292) - writeConcernError: - code: 64 - codeName: WriteConcernFailed - errmsg: "waiting for replication timed out" - errInfo: { wtimeout: true } - operations: - - &operation - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - expectations: &expectations_with_retries - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # commitTransaction applies w:majority on retries (SPEC-1185) - writeConcern: { w: majority, wtimeout: 10000 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - # The write operation is still applied despite the write concern error - outcome: &outcome - collection: - data: - - { _id: 1 } - - - # This test configures the fail point to return an error with the - # WriteConcernFailed code but without errInfo that would identify it as a - # wtimeout error. This tests that drivers do not assume that all - # WriteConcernFailed errors are due to a replication timeout. - description: commitTransaction is retried after WriteConcernFailed non-timeout error - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - # Do not specify closeConnection: false, since that would conflict - # with writeConcernError (see: SERVER-39292) - writeConcernError: - code: 64 - codeName: WriteConcernFailed - errmsg: "multiple errors reported" - operations: - - *operation - expectations: *expectations_with_retries - outcome: *outcome - - - description: commitTransaction is not retried after UnknownReplWriteConcern error - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 79 - codeName: UnknownReplWriteConcern - errmsg: "No write concern mode named 'foo' found in replica set configuration" - operations: - - <<: *operation - result: - errorCodeName: UnknownReplWriteConcern - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - expectations: &expectations_without_retries - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - # failCommand with writeConcernError still applies the write operation(s) - outcome: *outcome - - - - description: commitTransaction is not retried after UnsatisfiableWriteConcern error - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 100 - codeName: UnsatisfiableWriteConcern - errmsg: "Not enough data-bearing nodes" - operations: - - <<: *operation - result: - errorCodeName: UnsatisfiableWriteConcern - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - expectations: *expectations_without_retries - # failCommand with writeConcernError still applies the write operation(s) - outcome: *outcome - - - - description: commitTransaction is not retried after MaxTimeMSExpired error - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 50 - codeName: MaxTimeMSExpired - errmsg: "operation exceeded time limit" - operations: - - <<: *operation - result: - errorCodeName: MaxTimeMSExpired - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - expectations: *expectations_without_retries - # failCommand with writeConcernError still applies the write operation(s) - outcome: *outcome diff --git a/src/test/spec/json/transactions-convenient-api/commit.json b/src/test/spec/json/transactions-convenient-api/commit.json deleted file mode 100644 index 0a7451db9..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit.json +++ /dev/null @@ -1,286 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "withTransaction commits after callback returns", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "withTransaction commits after callback returns (second transaction)", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": { - "afterClusterTime": 42 - }, - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/commit.yml b/src/test/spec/json/transactions-convenient-api/commit.yml deleted file mode 100644 index b84a3e8bd..000000000 --- a/src/test/spec/json/transactions-convenient-api/commit.yml +++ /dev/null @@ -1,193 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: withTransaction commits after callback returns - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 2 } - result: - insertedId: 2 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 2 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - { _id: 2 } - - - # In this scenario, the callback commits the transaction originally started - # by withTransaction and starts a second transaction before returning. Since - # withTransaction only examines the session's state, it should commit that - # second transaction after the callback returns. - description: withTransaction commits after callback returns (second transaction) - useMultipleMongoses: true - operations: - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - - name: commitTransaction - object: session0 - - - name: startTransaction - object: session0 - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 2 } - result: - insertedId: 2 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 2 } - ordered: true - lsid: session0 - # second transaction will be causally consistent with the first - readConcern: { afterClusterTime: 42 } - # txnNumber is incremented for the second transaction - txnNumber: { $numberLong: "2" } - startTransaction: true - autocommit: false - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "2" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: - collection: - data: - - { _id: 1 } - - { _id: 2 } diff --git a/src/test/spec/json/transactions-convenient-api/transaction-options.json b/src/test/spec/json/transactions-convenient-api/transaction-options.json deleted file mode 100644 index 6deff43cf..000000000 --- a/src/test/spec/json/transactions-convenient-api/transaction-options.json +++ /dev/null @@ -1,577 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "withTransaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "withTransaction and no transaction options set", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "readConcern": null, - "startTransaction": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "withTransaction inherits transaction options from client", - "useMultipleMongoses": true, - "clientOptions": { - "readConcernLevel": "local", - "w": 1 - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "local" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 1 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "withTransaction inherits transaction options from defaultTransactionOptions", - "useMultipleMongoses": true, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": 1 - } - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 1 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "withTransaction explicit transaction options", - "useMultipleMongoses": true, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - }, - "options": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": 1 - } - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 1 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "withTransaction explicit transaction options override defaultTransactionOptions", - "useMultipleMongoses": true, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "snapshot" - }, - "writeConcern": { - "w": "majority" - } - } - } - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - }, - "options": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": 1 - } - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 1 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "withTransaction explicit transaction options override client options", - "useMultipleMongoses": true, - "clientOptions": { - "readConcernLevel": "local", - "w": "majority" - }, - "operations": [ - { - "name": "withTransaction", - "object": "session0", - "arguments": { - "callback": { - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ] - }, - "options": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": 1 - } - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "withTransaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": { - "w": 1 - }, - "readConcern": null, - "startTransaction": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions-convenient-api/transaction-options.yml b/src/test/spec/json/transactions-convenient-api/transaction-options.yml deleted file mode 100644 index 15bdc65bf..000000000 --- a/src/test/spec/json/transactions-convenient-api/transaction-options.yml +++ /dev/null @@ -1,274 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "withTransaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: withTransaction and no transaction options set - useMultipleMongoses: true - operations: &operations - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - # omitted fields - readConcern: ~ - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - # omitted fields - readConcern: ~ - startTransaction: ~ - writeConcern: ~ - command_name: commitTransaction - database_name: admin - outcome: &outcome - collection: - data: - - { _id: 1 } - - - description: withTransaction inherits transaction options from client - useMultipleMongoses: true - clientOptions: - readConcernLevel: local - w: 1 - operations: *operations - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - readConcern: { level: local } - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 1 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: *outcome - - - description: withTransaction inherits transaction options from defaultTransactionOptions - useMultipleMongoses: true - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: { level: majority } - writeConcern: { w: 1 } - operations: *operations - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - readConcern: { level: majority } - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 1 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: *outcome - - - description: withTransaction explicit transaction options - useMultipleMongoses: true - operations: &operations_explicit_transactionOptions - - - name: withTransaction - object: session0 - arguments: - callback: - operations: - - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - options: - readConcern: { level: majority } - writeConcern: { w: 1 } - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - readConcern: { level: majority } - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 1 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: *outcome - - - description: withTransaction explicit transaction options override defaultTransactionOptions - useMultipleMongoses: true - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: { level: snapshot } - writeConcern: { w: majority } - operations: *operations_explicit_transactionOptions - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - readConcern: { level: majority } - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 1 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: *outcome - - - description: withTransaction explicit transaction options override client options - useMultipleMongoses: true - clientOptions: - readConcernLevel: local - w: majority - operations: *operations_explicit_transactionOptions - expectations: - - - command_started_event: - command: - insert: *collection_name - documents: - - { _id: 1 } - ordered: true - lsid: session0 - txnNumber: { $numberLong: "1" } - startTransaction: true - autocommit: false - readConcern: { level: majority } - # omitted fields - writeConcern: ~ - command_name: insert - database_name: *database_name - - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: { $numberLong: "1" } - autocommit: false - writeConcern: { w: 1 } - # omitted fields - readConcern: ~ - startTransaction: ~ - command_name: commitTransaction - database_name: admin - outcome: *outcome diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.json b/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.json new file mode 100644 index 000000000..206428715 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.json @@ -0,0 +1,344 @@ +{ + "description": "callback-aborts", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction succeeds if callback aborts", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "abortTransaction", + "object": "session0" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ] + }, + { + "description": "withTransaction succeeds if callback aborts with no ops", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "abortTransaction", + "object": "session0" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ] + }, + { + "description": "withTransaction still succeeds if callback aborts and runs extra op", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "abortTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "autocommit": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 2 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.yml b/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.yml new file mode 100644 index 000000000..9414040ec --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-aborts.yml @@ -0,0 +1,178 @@ +description: callback-aborts + +schemaVersion: '1.3' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + # Session state will be ABORTED when callback returns to withTransaction + description: withTransaction succeeds if callback aborts + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: abortTransaction + object: *session0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + - + # Session state will be ABORTED when callback returns to withTransaction + description: withTransaction succeeds if callback aborts with no ops + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: abortTransaction + object: *session0 + expectEvents: + - client: *client0 + events: [] + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + - + # Session state will be NO_TXN when callback returns to withTransaction + description: withTransaction still succeeds if callback aborts and runs extra op + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: abortTransaction + object: *session0 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - commandStartedEvent: + command: + # This test is agnostic about retryWrites, so we do not assert the + # txnNumber. If retryWrites=true, the txnNumber will be incremented + # from the value used in the previous transaction; otherwise, the + # field will not be present at all. + insert: *collectionName + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # omitted fields + autocommit: { $$exists: false } + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 2 } diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-commits.json b/src/test/spec/json/transactions-convenient-api/unified/callback-commits.json new file mode 100644 index 000000000..06f791e9a --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-commits.json @@ -0,0 +1,423 @@ +{ + "description": "callback-commits", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction succeeds if callback commits", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session0" + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "withTransaction still succeeds if callback commits and runs extra op", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "autocommit": { + "$$exists": false + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-commits.yml b/src/test/spec/json/transactions-convenient-api/unified/callback-commits.yml new file mode 100644 index 000000000..b5cbb0415 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-commits.yml @@ -0,0 +1,209 @@ +description: callback-commits + +schemaVersion: '1.3' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + # Session state will be COMMITTED when callback returns to withTransaction + description: withTransaction succeeds if callback commits + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - name: commitTransaction + object: *session0 + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - { _id: 2 } + - + # Session state will be NO_TXN when callback returns to withTransaction + description: withTransaction still succeeds if callback commits and runs extra op + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - name: commitTransaction + object: *session0 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 3 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + # This test is agnostic about retryWrites, so we do not assert the + # txnNumber. If retryWrites=true, the txnNumber will be incremented + # from the value used in the previous transaction; otherwise, the + # field will not be present at all. + insert: *collectionName + documents: + - { _id: 3 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # omitted fields + autocommit: { $$exists: false } + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-retry.json b/src/test/spec/json/transactions-convenient-api/unified/callback-retry.json new file mode 100644 index 000000000..277dfa18e --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-retry.json @@ -0,0 +1,472 @@ +{ + "description": "callback-retry", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client1", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "callback succeeds after multiple connection errors", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "ignoreResultAndError": true + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "callback is not retried after non-transient error (DuplicateKeyError)", + "operations": [ + { + "name": "withTransaction", + "object": "session1", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection1", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "insertOne", + "object": "collection1", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ], + "errorContains": "E11000" + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/callback-retry.yml b/src/test/spec/json/transactions-convenient-api/unified/callback-retry.yml new file mode 100644 index 000000000..2a89de080 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/callback-retry.yml @@ -0,0 +1,249 @@ +description: callback-retry + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + # Define a second set of entities for useMultipleMongoses:true + - client: + id: &client1 client1 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database1 database1 + client: *client1 + databaseName: *databaseName + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collectionName + - session: + id: &session1 session1 + client: *client1 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: callback succeeds after multiple connection errors + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309) + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ insert ] + closeConnection: true + - name: withTransaction + object: *session0 + arguments: + callback: + - + # We do not assert the result here, as insertOne will fail for + # the first two executions of the callback before ultimately + # succeeding and returning a result. Asserting the state of the + # output collection after the test is sufficient. + name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + ignoreResultAndError: true + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # second transaction will be causally consistent with the first + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "2" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "2" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # third transaction will be causally consistent with the second + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "3" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "3" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + description: callback is not retried after non-transient error (DuplicateKeyError) + operations: + - name: withTransaction + object: *session1 + arguments: + callback: + - name: insertOne + object: *collection1 + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: insertOne + object: *collection1 + arguments: + session: *session1 + document: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + # DuplicateKey error code included in the bulk write error message + # returned by the server + errorContains: E11000 + expectEvents: + - client: *client1 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.json b/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.json new file mode 100644 index 000000000..c6a4e44d6 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.json @@ -0,0 +1,231 @@ +{ + "description": "commit-retry-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commit is retried after commitTransaction UnknownTransactionCommitResult (NotWritablePrimary)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 10107, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.yml new file mode 100644 index 000000000..f6f61375b --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-retry-errorLabels.yml @@ -0,0 +1,118 @@ +description: commit-retry-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option + topologies: [ replicaset, sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: commit is retried after commitTransaction UnknownTransactionCommitResult (NotWritablePrimary) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 10107 # NotWritablePrimary + errorLabels: ["RetryableWriteError"] # SPEC-1565 + closeConnection: false + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-retry.json b/src/test/spec/json/transactions-convenient-api/unified/commit-retry.json new file mode 100644 index 000000000..928f0167e --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-retry.json @@ -0,0 +1,552 @@ +{ + "description": "commit-retry", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction succeeds after multiple connection errors", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction retry only overwrites write concern w option", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "writeConcern": { + "w": 2, + "journal": true, + "wtimeoutMS": 5000 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 2, + "j": true, + "wtimeout": 5000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "j": true, + "wtimeout": 5000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "j": true, + "wtimeout": 5000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commit is not retried after MaxTimeMSExpired error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 50 + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "maxCommitTimeMS": 60000 + }, + "expectError": { + "errorCodeName": "MaxTimeMSExpired", + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "maxTimeMS": 60000, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-retry.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-retry.yml new file mode 100644 index 000000000..22d212548 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-retry.yml @@ -0,0 +1,279 @@ +description: commit-retry + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: commitTransaction succeeds after multiple connection errors + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309) + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + closeConnection: true + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + description: commitTransaction retry only overwrites write concern w option + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309) + runOnRequirements: + - serverless: forbid + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + closeConnection: true + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + writeConcern: { w: 2, journal: true, wtimeoutMS: 5000 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 2, j: true, wtimeout: 5000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, j: true, wtimeout: 5000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, j: true, wtimeout: 5000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + description: commit is not retried after MaxTimeMSExpired error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ commitTransaction ] + errorCode: 50 # MaxTimeMSExpired + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + maxCommitTimeMS: 60000 + expectError: + errorCodeName: MaxTimeMSExpired + errorLabelsContain: ["UnknownTransactionCommitResult"] + errorLabelsOmit: ["TransientTransactionError"] + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + maxTimeMS: 60000 + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + # In reality, the outcome of the commit is unknown but we fabricate + # the error with failCommand.errorCode which does not apply the commit + # operation. + documents: [] diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.json b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.json new file mode 100644 index 000000000..0f5a78245 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.json @@ -0,0 +1,294 @@ +{ + "description": "commit-transienttransactionerror-4.2", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.1.6", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "transaction is retried after commitTransaction TransientTransactionError (PreparedTransactionInProgress)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 267, + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.yml new file mode 100644 index 000000000..36cf90b91 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror-4.2.yml @@ -0,0 +1,159 @@ +description: commit-transienttransactionerror-4.2 + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: '4.1.6' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +# These tests use error codes where the TransientTransactionError label will be +# applied to the error response for commitTransaction. This will cause the +# entire transaction to be retried instead of commitTransaction. +# +# See: https://github.com/mongodb/mongo/blob/r4.1.6/src/mongo/db/handle_request_response.cpp +tests: + - + description: transaction is retried after commitTransaction TransientTransactionError (PreparedTransactionInProgress) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 267 # PreparedTransactionInProgress + closeConnection: false + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # second transaction will be causally consistent with the first + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "2" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "2" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # third transaction will be causally consistent with the second + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "3" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "3" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.json b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.json new file mode 100644 index 000000000..dd5158d81 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.json @@ -0,0 +1,996 @@ +{ + "description": "commit-transienttransactionerror", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "transaction is retried after commitTransaction TransientTransactionError (LockTimeout)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 24, + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "transaction is retried after commitTransaction TransientTransactionError (WriteConflict)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 112, + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "transaction is retried after commitTransaction TransientTransactionError (SnapshotUnavailable)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 246, + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "transaction is retried after commitTransaction TransientTransactionError (NoSuchTransaction)", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 251, + "closeConnection": false + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.yml new file mode 100644 index 000000000..befd8f103 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-transienttransactionerror.yml @@ -0,0 +1,212 @@ +description: commit-transienttransactionerror + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +# These tests use error codes where the TransientTransactionError label will be +# applied to the error response for commitTransaction. This will cause the +# entire transaction to be retried instead of commitTransaction. +# +# See: https://github.com/mongodb/mongo/blob/r4.1.6/src/mongo/db/handle_request_response.cpp +tests: + - + description: transaction is retried after commitTransaction TransientTransactionError (LockTimeout) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 24 # LockTimeout + closeConnection: false + - &withTransaction + name: withTransaction + object: *session0 + arguments: + callback: + - + name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: &expectEvents + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # second transaction will be causally consistent with the first + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "2" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "2" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # third transaction will be causally consistent with the second + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented when retrying the transaction + txnNumber: { $numberLong: "3" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "3" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + description: transaction is retried after commitTransaction TransientTransactionError (WriteConflict) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 112 # WriteConflict + closeConnection: false + - *withTransaction + expectEvents: *expectEvents + outcome: *outcome + - + description: transaction is retried after commitTransaction TransientTransactionError (SnapshotUnavailable) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 246 # SnapshotUnavailable + closeConnection: false + - *withTransaction + expectEvents: *expectEvents + outcome: *outcome + - + description: transaction is retried after commitTransaction TransientTransactionError (NoSuchTransaction) + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + errorCode: 251 # NoSuchTransaction + closeConnection: false + - *withTransaction + expectEvents: *expectEvents + outcome: *outcome diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json new file mode 100644 index 000000000..568f7ede4 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.json @@ -0,0 +1,812 @@ +{ + "description": "commit-writeconcernerror", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction is retried after WriteConcernTimeout timeout error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 64, + "errmsg": "waiting for replication timed out", + "errInfo": { + "wtimeout": true + } + } + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction is retried after WriteConcernTimeout non-timeout error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 64, + "errmsg": "multiple errors reported" + } + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction is not retried after UnknownReplWriteConcern error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 79, + "codeName": "UnknownReplWriteConcern", + "errmsg": "No write concern mode named 'foo' found in replica set configuration" + } + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + }, + "expectError": { + "errorCodeName": "UnknownReplWriteConcern", + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction is not retried after UnsatisfiableWriteConcern error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 100, + "codeName": "UnsatisfiableWriteConcern", + "errmsg": "Not enough data-bearing nodes" + } + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + }, + "expectError": { + "errorCodeName": "UnsatisfiableWriteConcern", + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction is not retried after MaxTimeMSExpired error", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 50, + "codeName": "MaxTimeMSExpired", + "errmsg": "operation exceeded time limit" + } + } + } + } + }, + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + }, + "expectError": { + "errorCodeName": "MaxTimeMSExpired", + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml new file mode 100644 index 000000000..408f57fde --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit-writeconcernerror.yml @@ -0,0 +1,248 @@ +description: commit-writeconcernerror + +schemaVersion: '1.3' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: commitTransaction is retried after WriteConcernTimeout timeout error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + # Do not specify closeConnection: false, since that would conflict + # with writeConcernError (see: SERVER-39292) + writeConcernError: + code: 64 + errmsg: "waiting for replication timed out" + errInfo: { wtimeout: true } + - &operation + name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: &expectEvents_with_retries + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # commitTransaction applies w:majority on retries (SPEC-1185) + writeConcern: { w: majority, wtimeout: 10000 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + # The write operation is still applied despite the write concern error + outcome: &outcome + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + # This test configures the fail point to return an error with the + # WriteConcernTimeout code but without errInfo that would identify it as a + # wtimeout error. This tests that drivers do not assume that all + # WriteConcernTimeout errors are due to a replication timeout. + description: commitTransaction is retried after WriteConcernTimeout non-timeout error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: [ commitTransaction ] + # Do not specify closeConnection: false, since that would conflict + # with writeConcernError (see: SERVER-39292) + writeConcernError: + code: 64 + errmsg: "multiple errors reported" + - *operation + expectEvents: *expectEvents_with_retries + outcome: *outcome + - + description: commitTransaction is not retried after UnknownReplWriteConcern error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ commitTransaction ] + writeConcernError: + code: 79 + codeName: UnknownReplWriteConcern + errmsg: "No write concern mode named 'foo' found in replica set configuration" + - <<: *operation + expectError: + errorCodeName: UnknownReplWriteConcern + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + expectEvents: &expectEvents_without_retries + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + # failCommand with writeConcernError still applies the write operation(s) + outcome: *outcome + - + description: commitTransaction is not retried after UnsatisfiableWriteConcern error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ commitTransaction ] + writeConcernError: + code: 100 + codeName: UnsatisfiableWriteConcern + errmsg: "Not enough data-bearing nodes" + - <<: *operation + expectError: + errorCodeName: UnsatisfiableWriteConcern + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + expectEvents: *expectEvents_without_retries + # failCommand with writeConcernError still applies the write operation(s) + outcome: *outcome + - + description: commitTransaction is not retried after MaxTimeMSExpired error + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ commitTransaction ] + writeConcernError: + code: 50 + codeName: MaxTimeMSExpired + errmsg: "operation exceeded time limit" + - <<: *operation + expectError: + errorCodeName: MaxTimeMSExpired + errorLabelsContain: ["UnknownTransactionCommitResult"] + errorLabelsOmit: ["TransientTransactionError"] + expectEvents: *expectEvents_without_retries + # failCommand with writeConcernError still applies the write operation(s) + outcome: *outcome diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit.json b/src/test/spec/json/transactions-convenient-api/unified/commit.json new file mode 100644 index 000000000..5684d5ee8 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit.json @@ -0,0 +1,398 @@ +{ + "description": "commit", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction commits after callback returns", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "withTransaction commits after callback returns (second transaction)", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "name": "commitTransaction", + "object": "session0" + }, + { + "name": "startTransaction", + "object": "session0" + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/commit.yml b/src/test/spec/json/transactions-convenient-api/unified/commit.yml new file mode 100644 index 000000000..774ddb1b0 --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/commit.yml @@ -0,0 +1,199 @@ +description: commit + +schemaVersion: '1.3' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: withTransaction commits after callback returns + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - { _id: 2 } + - + # In this scenario, the callback commits the transaction originally started + # by withTransaction and starts a second transaction before returning. Since + # withTransaction only examines the session's state, it should commit that + # second transaction after the callback returns. + description: withTransaction commits after callback returns (second transaction) + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - name: commitTransaction + object: *session0 + - name: startTransaction + object: *session0 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + # second transaction will be causally consistent with the first + readConcern: { afterClusterTime: { $$exists: true } } + # txnNumber is incremented for the second transaction + txnNumber: { $numberLong: "2" } + startTransaction: true + autocommit: false + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "2" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - { _id: 2 } diff --git a/src/test/spec/json/transactions-convenient-api/unified/transaction-options.json b/src/test/spec/json/transactions-convenient-api/unified/transaction-options.json new file mode 100644 index 000000000..b1a74c5fd --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/transaction-options.json @@ -0,0 +1,819 @@ +{ + "description": "transaction-options", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "withTransaction and no transaction options set", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "withTransaction inherits transaction options from client", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": true, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "name": "withTransaction", + "object": "session1", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection1", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "withTransaction inherits transaction options from defaultTransactionOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "session": { + "id": "session1", + "client": "client0", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": 1 + } + } + } + } + } + ] + } + }, + { + "name": "withTransaction", + "object": "session1", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "withTransaction explicit transaction options", + "operations": [ + { + "name": "withTransaction", + "object": "session0", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "withTransaction explicit transaction options override defaultTransactionOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "session": { + "id": "session1", + "client": "client0", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "snapshot" + }, + "writeConcern": { + "w": "majority" + } + } + } + } + } + ] + } + }, + { + "name": "withTransaction", + "object": "session1", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "withTransaction explicit transaction options override client options", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": true, + "uriOptions": { + "readConcernLevel": "local", + "w": "majority" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "withTransaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "name": "withTransaction", + "object": "session1", + "arguments": { + "callback": [ + { + "name": "insertOne", + "object": "collection1", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": 1 + } + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "withTransaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "w": 1 + }, + "readConcern": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "withTransaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions-convenient-api/unified/transaction-options.yml b/src/test/spec/json/transactions-convenient-api/unified/transaction-options.yml new file mode 100644 index 000000000..fc6674c9e --- /dev/null +++ b/src/test/spec/json/transactions-convenient-api/unified/transaction-options.yml @@ -0,0 +1,379 @@ +description: transaction-options + +schemaVersion: '1.9' + +runOnRequirements: + - minServerVersion: '4.0' + topologies: [ replicaset ] + - minServerVersion: 4.1.8 + topologies: [ sharded, load-balanced ] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &databaseName withTransaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collectionName test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collectionName + databaseName: *databaseName + documents: [] + +tests: + - + description: withTransaction and no transaction options set + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + # omitted fields + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collectionName + databaseName: *databaseName + documents: + - { _id: 1 } + - + description: withTransaction inherits transaction options from client + operations: + - object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: true + uriOptions: + readConcernLevel: local + w: 1 + observeEvents: [ commandStartedEvent ] + - database: + id: &database1 database1 + client: *client1 + databaseName: *databaseName + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collectionName + - session: + id: &session1 session1 + client: *client1 + - name: withTransaction + object: *session1 + arguments: + callback: + - name: insertOne + object: *collection1 + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client1 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + readConcern: { level: local } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: withTransaction inherits transaction options from defaultTransactionOptions + operations: + - object: testRunner + name: createEntities + arguments: + entities: + - session: + id: &session1 session1 + client: *client0 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: majority } + writeConcern: { w: 1 } + - name: withTransaction + object: *session1 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + readConcern: { level: majority } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: withTransaction explicit transaction options + operations: + - name: withTransaction + object: *session0 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + readConcern: { level: majority } + writeConcern: { w: 1 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + readConcern: { level: majority } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: withTransaction explicit transaction options override defaultTransactionOptions + operations: + - object: testRunner + name: createEntities + arguments: + entities: + - session: + id: &session1 session1 + client: *client0 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: snapshot } + writeConcern: { w: majority } + - name: withTransaction + object: *session1 + arguments: + callback: + - name: insertOne + object: *collection0 + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + readConcern: { level: majority } + writeConcern: { w: 1 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + readConcern: { level: majority } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: withTransaction explicit transaction options override client options + operations: + - object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: true + uriOptions: + readConcernLevel: local + w: majority + observeEvents: [ commandStartedEvent ] + - database: + id: &database1 database1 + client: *client1 + databaseName: *databaseName + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collectionName + - session: + id: &session1 session1 + client: *client1 + - name: withTransaction + object: *session1 + arguments: + callback: + - name: insertOne + object: *collection1 + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + readConcern: { level: majority } + writeConcern: { w: 1 } + expectEvents: + - client: *client1 + events: + - commandStartedEvent: + command: + insert: *collectionName + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + startTransaction: true + autocommit: false + readConcern: { level: majority } + # omitted fields + writeConcern: { $$exists: false } + commandName: insert + databaseName: *databaseName + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: "1" } + autocommit: false + writeConcern: { w: 1 } + # omitted fields + readConcern: { $$exists: false } + startTransaction: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome diff --git a/src/test/spec/json/transactions/README.md b/src/test/spec/json/transactions/README.md new file mode 100644 index 000000000..212d707ca --- /dev/null +++ b/src/test/spec/json/transactions/README.md @@ -0,0 +1,100 @@ +# Transactions Tests + +______________________________________________________________________ + +## Introduction + +The YAML and JSON files in this directory are platform-independent tests meant to exercise a driver's implementation of +transactions. These tests utilize the [Unified Test Format](../../unified-test-format/unified-test-format.md). + +Several prose tests, which are not easily expressed in YAML, are also presented in this file. Those tests will need to +be manually implemented by each driver. + +## Mongos Pinning Prose Tests + +The following tests ensure that a ClientSession is properly unpinned after a sharded transaction. Initialize these tests +with a MongoClient connected to multiple mongoses. + +These tests use a cursor's address field to track which server an operation was run on. If this is not possible in your +driver, use command monitoring instead. + +1. Test that starting a new transaction on a pinned ClientSession unpins the session and normal server selection is + performed for the next operation. + + ```python + @require_server_version(4, 1, 6) + @require_mongos_count_at_least(2) + def test_unpin_for_next_transaction(self): + # Increase localThresholdMS and wait until both nodes are discovered + # to avoid false positives. + client = MongoClient(mongos_hosts, localThresholdMS=1000) + wait_until(lambda: len(client.nodes) > 1) + # Create the collection. + client.test.test.insert_one({}) + with client.start_session() as s: + # Session is pinned to Mongos. + with s.start_transaction(): + client.test.test.insert_one({}, session=s) + + addresses = set() + for _ in range(50): + with s.start_transaction(): + cursor = client.test.test.find({}, session=s) + assert next(cursor) + addresses.add(cursor.address) + + assert len(addresses) > 1 + ``` + +2. Test non-transaction operations using a pinned ClientSession unpins the session and normal server selection is + performed. + + ```python + @require_server_version(4, 1, 6) + @require_mongos_count_at_least(2) + def test_unpin_for_non_transaction_operation(self): + # Increase localThresholdMS and wait until both nodes are discovered + # to avoid false positives. + client = MongoClient(mongos_hosts, localThresholdMS=1000) + wait_until(lambda: len(client.nodes) > 1) + # Create the collection. + client.test.test.insert_one({}) + with client.start_session() as s: + # Session is pinned to Mongos. + with s.start_transaction(): + client.test.test.insert_one({}, session=s) + + addresses = set() + for _ in range(50): + cursor = client.test.test.find({}, session=s) + assert next(cursor) + addresses.add(cursor.address) + + assert len(addresses) > 1 + ``` + +3. Test that `PoolClearedError` has `TransientTransactionError` label. Since there is no simple way to trigger + `PoolClearedError`, this test should be implemented in a way that suites each driver the best. + +## Options Inside Transaction Prose Tests. + +These prose tests ensure drivers handle options inside a transaction where the unified tests do not suffice. Ensure +these tests do not run against a standalone server. + +### 1.0 Write concern not inherited from collection object inside transaction. + +- Create a MongoClient running against a configured sharded/replica set/load balanced cluster. +- Start a new session on the client. +- Start a transaction on the session. +- Instantiate a collection object in the driver with a default write concern of `{ w: 0 }`. +- Insert the document `{ n: 1 }` on the instantiated collection. +- Commit the transaction. +- End the session. +- Ensure the document was inserted and no error was thrown from the transaction. + +## Changelog + +- 2024-10-31: Add test for PoolClearedError. +- 2024-02-15: Migrated from reStructuredText to Markdown. +- 2024-02-07: Converted legacy transaction tests to unified format and moved the legacy test format docs to a separate + file. diff --git a/src/test/spec/json/transactions/README.rst b/src/test/spec/json/transactions/README.rst deleted file mode 100644 index 1542b9a67..000000000 --- a/src/test/spec/json/transactions/README.rst +++ /dev/null @@ -1,643 +0,0 @@ -================== -Transactions Tests -================== - -.. contents:: - ----- - -Introduction -============ - -The YAML and JSON files in the ``legacy`` and ``unified`` sub-directories are -platform-independent tests that drivers can use to prove their conformance to -the Transactions Spec. The tests in the ``legacy`` directory are designed with -the intention of sharing some test-runner code with the CRUD Spec tests and the -Command Monitoring Spec tests. The format for these tests and instructions for -executing them are provided in the following sections. Tests in the -``unified`` directory are written using the `Unified Test Format -<../../unified-test-format/unified-test-format.rst>`_. - -Several prose tests, which are not easily expressed in YAML, are also presented -in this file. Those tests will need to be manually implemented by each driver. - -Server Fail Point -================= - -failCommand -``````````` - -Some tests depend on a server fail point, expressed in the ``failPoint`` field. -For example the ``failCommand`` fail point allows the client to force the -server to return an error. Keep in mind that the fail point only triggers for -commands listed in the "failCommands" field. See `SERVER-35004`_ and -`SERVER-35083`_ for more information. - -.. _SERVER-35004: https://jira.mongodb.org/browse/SERVER-35004 -.. _SERVER-35083: https://jira.mongodb.org/browse/SERVER-35083 - -The ``failCommand`` fail point may be configured like so:: - - db.adminCommand({ - configureFailPoint: "failCommand", - mode: , - data: { - failCommands: ["commandName", "commandName2"], - closeConnection: , - errorCode: , - writeConcernError: , - appName: , - blockConnection: , - blockTimeMS: , - } - }); - -``mode`` is a generic fail point option and may be assigned a string or document -value. The string values ``"alwaysOn"`` and ``"off"`` may be used to enable or -disable the fail point, respectively. A document may be used to specify either -``times`` or ``skip``, which are mutually exclusive: - -- ``{ times: }`` may be used to limit the number of times the fail - point may trigger before transitioning to ``"off"``. -- ``{ skip: }`` may be used to defer the first trigger of a fail - point, after which it will transition to ``"alwaysOn"``. - -The ``data`` option is a document that may be used to specify options that -control the fail point's behavior. ``failCommand`` supports the following -``data`` options, which may be combined if desired: - -- ``failCommands``: Required, the list of command names to fail. -- ``closeConnection``: Boolean option, which defaults to ``false``. If - ``true``, the command will not be executed, the connection will be closed, and - the client will see a network error. -- ``errorCode``: Integer option, which is unset by default. If set, the command - will not be executed and the specified command error code will be returned as - a command error. -- ``appName``: A string to filter which MongoClient should be affected by - the failpoint. `New in mongod 4.4.0-rc2 `_. -- ``blockConnection``: Whether the server should block the affected commands. - Default false. -- ``blockTimeMS``: The number of milliseconds the affect commands should be - blocked for. Required when blockConnection is true. - `New in mongod 4.3.4 `_. - -Speeding Up Tests -================= - -See `Speeding Up Tests <../../retryable-reads/tests/README.rst#speeding-up-tests>`_ in the retryable reads spec tests. - -Test Format -=========== - -Each YAML file has the following keys: - -- ``runOn`` (optional): An array of server version and/or topology requirements - for which the tests can be run. If the test environment satisfies one or more - of these requirements, the tests may be executed; otherwise, this file should - be skipped. If this field is omitted, the tests can be assumed to have no - particular requirements and should be executed. Each element will have some or - all of the following fields: - - - ``minServerVersion`` (optional): The minimum server version (inclusive) - required to successfully run the tests. If this field is omitted, it should - be assumed that there is no lower bound on the required server version. - - - ``maxServerVersion`` (optional): The maximum server version (inclusive) - against which the tests can be run successfully. If this field is omitted, - it should be assumed that there is no upper bound on the required server - version. - - - ``topology`` (optional): An array of server topologies against which the - tests can be run successfully. Valid topologies are "single", "replicaset", - and "sharded". If this field is omitted, the default is all topologies (i.e. - ``["single", "replicaset", "sharded"]``). - - - ``serverless``: (optional): Whether or not the test should be run on Atlas - Serverless instances. Valid values are "require", "forbid", and "allow". If - "require", the test MUST only be run on Atlas Serverless instances. If - "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted - or "allow", this option has no effect. - - The test runner MUST be informed whether or not Atlas Serverless is being - used in order to determine if this requirement is met (e.g. through an - environment variable or configuration option). - - Note: the Atlas Serverless proxy imitates mongos, so the test runner is not - capable of determining if Atlas Serverless is in use by issuing commands - such as ``buildInfo`` or ``hello``. Furthermore, connections to Atlas - Serverless use a load balancer, so the topology will appear as - "load-balanced". - -- ``database_name`` and ``collection_name``: The database and collection to use - for testing. - -- ``data``: The data that should exist in the collection under test before each - test run. - -- ``tests``: An array of tests that are to be run independently of each other. - Each test will have some or all of the following fields: - - - ``description``: The name of the test. - - - ``skipReason``: Optional, string describing why this test should be - skipped. - - - ``useMultipleMongoses`` (optional): If ``true``, and the topology type is - ``Sharded``, the MongoClient for this test should be initialized with multiple - mongos seed addresses. If ``false`` or omitted, only a single mongos address - should be specified. - - If ``true``, the topology type is ``LoadBalanced``, and Atlas Serverless is - not being used, the MongoClient for this test should be initialized with the - URI of the load balancer fronting multiple servers. If ``false`` or omitted, - the MongoClient for this test should be initialized with the URI of the load - balancer fronting a single server. - - ``useMultipleMongoses`` only affects ``Sharded`` and ``LoadBalanced`` - topologies (excluding Atlas Serverless). - - - ``clientOptions``: Optional, parameters to pass to MongoClient(). - - - ``failPoint``: Optional, a server failpoint to enable expressed as the - configureFailPoint command to run on the admin database. This option and - ``useMultipleMongoses: true`` are mutually exclusive. - - - ``sessionOptions``: Optional, map of session names (e.g. "session0") to - parameters to pass to MongoClient.startSession() when creating that session. - - - ``operations``: Array of documents, each describing an operation to be - executed. Each document has the following fields: - - - ``name``: The name of the operation on ``object``. - - - ``object``: The name of the object to perform the operation on. Can be - "database", "collection", "session0", "session1", or "testRunner". See - the "targetedFailPoint" operation in `Special Test Operations`_. - - - ``collectionOptions``: Optional, parameters to pass to the Collection() - used for this operation. - - - ``databaseOptions``: Optional, parameters to pass to the Database() - used for this operation. - - - ``command_name``: Present only when ``name`` is "runCommand". The name - of the command to run. Required for languages that are unable preserve - the order keys in the "command" argument when parsing JSON/YAML. - - - ``arguments``: Optional, the names and values of arguments. - - - ``error``: Optional. If true, the test should expect an error or - exception. This could be a server-generated or a driver-generated error. - - - ``result``: The return value from the operation, if any. This field may - be a single document or an array of documents in the case of a - multi-document read. If the operation is expected to return an error, the - ``result`` is a single document that has one or more of the following - fields: - - - ``errorContains``: A substring of the expected error message. - - - ``errorCodeName``: The expected "codeName" field in the server - error response. - - - ``errorLabelsContain``: A list of error label strings that the - error is expected to have. - - - ``errorLabelsOmit``: A list of error label strings that the - error is expected not to have. - - - ``expectations``: Optional list of command-started events. - - - ``outcome``: Document describing the return value and/or expected state of - the collection after the operation is executed. Contains the following - fields: - - - ``collection``: - - - ``data``: The data that should exist in the collection after the - operations have run, sorted by "_id". - -Use as Integration Tests -======================== - -Run a MongoDB replica set with a primary, a secondary, and an arbiter, -**server version 4.0.0 or later**. (Including a secondary ensures that -server selection in a transaction works properly. Including an arbiter helps -ensure that no new bugs have been introduced related to arbiters.) - -A driver that implements support for sharded transactions MUST also run these -tests against a MongoDB sharded cluster with multiple mongoses and -**server version 4.2 or later**. Some tests require -initializing the MongoClient with multiple mongos seeds to ensures that mongos -transaction pinning and the recoveryToken works properly. - -Load each YAML (or JSON) file using a Canonical Extended JSON parser. - -Then for each element in ``tests``: - -#. If the ``skipReason`` field is present, skip this test completely. -#. Create a MongoClient and call - ``client.admin.runCommand({killAllSessions: []})`` to clean up any open - transactions from previous test failures. Ignore a command failure with - error code 11601 ("Interrupted") to work around `SERVER-38335`_. - - - Running ``killAllSessions`` cleans up any open transactions from - a previously failed test to prevent the current test from blocking. - It is sufficient to run this command once before starting the test suite - and once after each failed test. - - When testing against a sharded cluster run this command on ALL mongoses. - -#. Create a collection object from the MongoClient, using the ``database_name`` - and ``collection_name`` fields of the YAML file. -#. Drop the test collection, using writeConcern "majority". -#. Execute the "create" command to recreate the collection, using writeConcern - "majority". (Creating the collection inside a transaction is prohibited, so - create it explicitly.) -#. If the YAML file contains a ``data`` array, insert the documents in ``data`` - into the test collection, using writeConcern "majority". -#. When testing against a sharded cluster run a ``distinct`` command on the - newly created collection on all mongoses. For an explanation see, - `Why do tests that run distinct sometimes fail with StaleDbVersion?`_ -#. If ``failPoint`` is specified, its value is a configureFailPoint command. - Run the command on the admin database to enable the fail point. -#. Create a **new** MongoClient ``client``, with Command Monitoring listeners - enabled. (Using a new MongoClient for each test ensures a fresh session pool - that hasn't executed any transactions previously, so the tests can assert - actual txnNumbers, starting from 1.) Pass this test's ``clientOptions`` if - present. - - - When testing against a sharded cluster and ``useMultipleMongoses`` is - ``true`` the client MUST be created with multiple (valid) mongos seed - addreses. - -#. Call ``client.startSession`` twice to create ClientSession objects - ``session0`` and ``session1``, using the test's "sessionOptions" if they - are present. Save their lsids so they are available after calling - ``endSession``, see `Logical Session Id`_. -#. For each element in ``operations``: - - - If the operation ``name`` is a special test operation type, execute it and - go to the next operation, otherwise proceed to the next step. - - Enter a "try" block or your programming language's closest equivalent. - - Create a Database object from the MongoClient, using the ``database_name`` - field at the top level of the test file. - - Create a Collection object from the Database, using the - ``collection_name`` field at the top level of the test file. - If ``collectionOptions`` or ``databaseOptions`` is present, create the - Collection or Database object with the provided options, respectively. - Otherwise create the object with the default options. - - Execute the named method on the provided ``object``, passing the - arguments listed. Pass ``session0`` or ``session1`` to the method, - depending on which session's name is in the arguments list. - If ``arguments`` contains no "session", pass no explicit session to the - method. - - If the driver throws an exception / returns an error while executing this - series of operations, store the error message and server error code. - - If the operation's ``error`` field is ``true``, verify that the method - threw an exception or returned an error. - - If the result document has an "errorContains" field, verify that the - method threw an exception or returned an error, and that the value of the - "errorContains" field matches the error string. "errorContains" is a - substring (case-insensitive) of the actual error message. - - If the result document has an "errorCodeName" field, verify that the - method threw a command failed exception or returned an error, and that - the value of the "errorCodeName" field matches the "codeName" in the - server error response. - - If the result document has an "errorLabelsContain" field, verify that the - method threw an exception or returned an error. Verify that all of the - error labels in "errorLabelsContain" are present in the error or exception - using the ``hasErrorLabel`` method. - - If the result document has an "errorLabelsOmit" field, verify that the - method threw an exception or returned an error. Verify that none of the - error labels in "errorLabelsOmit" are present in the error or exception - using the ``hasErrorLabel`` method. - - If the operation returns a raw command response, eg from ``runCommand``, - then compare only the fields present in the expected result document. - Otherwise, compare the method's return value to ``result`` using the same - logic as the CRUD Spec Tests runner. - -#. Call ``session0.endSession()`` and ``session1.endSession``. -#. If the test includes a list of command-started events in ``expectations``, - compare them to the actual command-started events using the - same logic as the Command Monitoring Spec Tests runner, plus the rules in - the Command-Started Events instructions below. -#. If ``failPoint`` is specified, disable the fail point to avoid spurious - failures in subsequent tests. The fail point may be disabled like so:: - - db.adminCommand({ - configureFailPoint: , - mode: "off" - }); - -#. For each element in ``outcome``: - - - If ``name`` is "collection", verify that the test collection contains - exactly the documents in the ``data`` array. Ensure this find reads the - latest data by using **primary read preference** with - **local read concern** even when the MongoClient is configured with - another read preference or read concern. - Note the server does not guarantee that documents returned by a find - command will be in inserted order. This find MUST sort by ``{_id:1}``. - -.. _SERVER-38335: https://jira.mongodb.org/browse/SERVER-38335 - -Special Test Operations -``````````````````````` - -Certain operations that appear in the "operations" array do not correspond to -API methods but instead represent special test operations. Such operations are -defined on the "testRunner" object and documented here: - -targetedFailPoint -~~~~~~~~~~~~~~~~~ - -The "targetedFailPoint" operation instructs the test runner to configure a fail -point on a specific mongos. The mongos to run the ``configureFailPoint`` is -determined by the "session" argument (either "session0" or "session1"). -The session must already be pinned to a mongos server. The "failPoint" argument -is the ``configureFailPoint`` command to run. - -If a test uses ``targetedFailPoint``, disable the fail point after running -all ``operations`` to avoid spurious failures in subsequent tests. The fail -point may be disabled like so:: - - db.adminCommand({ - configureFailPoint: , - mode: "off" - }); - -Here is an example which instructs the test runner to enable the failCommand -fail point on the mongos server which "session0" is pinned to:: - - # Enable the fail point only on the Mongos that session0 is pinned to. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - -Tests that use the "targetedFailPoint" operation do not include -``configureFailPoint`` commands in their command expectations. Drivers MUST -ensure that ``configureFailPoint`` commands do not appear in the list of logged -commands, either by manually filtering it from the list of observed commands or -by using a different MongoClient to execute ``configureFailPoint``. - -assertSessionTransactionState -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The "assertSessionTransactionState" operation instructs the test runner to -assert that the transaction state of the given session is equal to the -specified value. The possible values are as follows: ``none``, ``starting``, -``in_progress``, ``committed``, ``aborted``:: - - - name: assertSessionTransactionState - object: testRunner - arguments: - session: session0 - state: in_progress - -assertSessionPinned -~~~~~~~~~~~~~~~~~~~ - -The "assertSessionPinned" operation instructs the test runner to assert that -the given session is pinned to a mongos:: - - - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - -assertSessionUnpinned -~~~~~~~~~~~~~~~~~~~~~ - -The "assertSessionUnpinned" operation instructs the test runner to assert that -the given session is not pinned to a mongos:: - - - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - -assertCollectionExists -~~~~~~~~~~~~~~~~~~~~~~ - -The "assertCollectionExists" operation instructs the test runner to assert that -the given collection exists in the database:: - - - name: assertCollectionExists - object: testRunner - arguments: - database: db - collection: test - -Use a ``listCollections`` command to check whether the collection exists. Note -that it is currently not possible to run ``listCollections`` from within a -transaction. - -assertCollectionNotExists -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The "assertCollectionNotExists" operation instructs the test runner to assert -that the given collection does not exist in the database:: - - - name: assertCollectionNotExists - object: testRunner - arguments: - database: db - collection: test - -Use a ``listCollections`` command to check whether the collection exists. Note -that it is currently not possible to run ``listCollections`` from within a -transaction. - -assertIndexExists -~~~~~~~~~~~~~~~~~ - -The "assertIndexExists" operation instructs the test runner to assert that the -index with the given name exists on the collection:: - - - name: assertIndexExists - object: testRunner - arguments: - database: db - collection: test - index: t_1 - -Use a ``listIndexes`` command to check whether the index exists. Note that it is -currently not possible to run ``listIndexes`` from within a transaction. - -assertIndexNotExists -~~~~~~~~~~~~~~~~~~~~ - -The "assertIndexNotExists" operation instructs the test runner to assert that -the index with the given name does not exist on the collection:: - - - name: assertIndexNotExists - object: testRunner - arguments: - database: db - collection: test - index: t_1 - -Use a ``listIndexes`` command to check whether the index exists. Note that it is -currently not possible to run ``listIndexes`` from within a transaction. - -Command-Started Events -`````````````````````` - -The event listener used for these tests MUST ignore the security commands -listed in the Command Monitoring Spec. - -Logical Session Id -~~~~~~~~~~~~~~~~~~ - -Each command-started event in ``expectations`` includes an ``lsid`` with the -value "session0" or "session1". Tests MUST assert that the command's actual -``lsid`` matches the id of the correct ClientSession named ``session0`` or -``session1``. - -Null Values -~~~~~~~~~~~ - -Some command-started events in ``expectations`` include ``null`` values for -fields such as ``txnNumber``, ``autocommit``, and ``writeConcern``. -Tests MUST assert that the actual command **omits** any field that has a -``null`` value in the expected command. - -Cursor Id -^^^^^^^^^ - -A ``getMore`` value of ``"42"`` in a command-started event is a fake cursorId -that MUST be ignored. (In the Command Monitoring Spec tests, fake cursorIds are -correlated with real ones, but that is not necessary for Transactions Spec -tests.) - -afterClusterTime -^^^^^^^^^^^^^^^^ - -A ``readConcern.afterClusterTime`` value of ``42`` in a command-started event -is a fake cluster time. Drivers MUST assert that the actual command includes an -afterClusterTime. - -recoveryToken -^^^^^^^^^^^^^ - -A ``recoveryToken`` value of ``42`` in a command-started event is a -placeholder for an arbitrary recovery token. Drivers MUST assert that the -actual command includes a "recoveryToken" field and SHOULD assert that field -is a BSON document. - -Mongos Pinning Prose Tests -========================== - -The following tests ensure that a ClientSession is properly unpinned after -a sharded transaction. Initialize these tests with a MongoClient connected -to multiple mongoses. - -These tests use a cursor's address field to track which server an operation -was run on. If this is not possible in your driver, use command monitoring -instead. - -#. Test that starting a new transaction on a pinned ClientSession unpins the - session and normal server selection is performed for the next operation. - - .. code:: python - - @require_server_version(4, 1, 6) - @require_mongos_count_at_least(2) - def test_unpin_for_next_transaction(self): - # Increase localThresholdMS and wait until both nodes are discovered - # to avoid false positives. - client = MongoClient(mongos_hosts, localThresholdMS=1000) - wait_until(lambda: len(client.nodes) > 1) - # Create the collection. - client.test.test.insert_one({}) - with client.start_session() as s: - # Session is pinned to Mongos. - with s.start_transaction(): - client.test.test.insert_one({}, session=s) - - addresses = set() - for _ in range(50): - with s.start_transaction(): - cursor = client.test.test.find({}, session=s) - assert next(cursor) - addresses.add(cursor.address) - - assert len(addresses) > 1 - -#. Test non-transaction operations using a pinned ClientSession unpins the - session and normal server selection is performed. - - .. code:: python - - @require_server_version(4, 1, 6) - @require_mongos_count_at_least(2) - def test_unpin_for_non_transaction_operation(self): - # Increase localThresholdMS and wait until both nodes are discovered - # to avoid false positives. - client = MongoClient(mongos_hosts, localThresholdMS=1000) - wait_until(lambda: len(client.nodes) > 1) - # Create the collection. - client.test.test.insert_one({}) - with client.start_session() as s: - # Session is pinned to Mongos. - with s.start_transaction(): - client.test.test.insert_one({}, session=s) - - addresses = set() - for _ in range(50): - cursor = client.test.test.find({}, session=s) - assert next(cursor) - addresses.add(cursor.address) - - assert len(addresses) > 1 - -Q & A -===== - -Why do tests that run distinct sometimes fail with StaleDbVersion? -`````````````````````````````````````````````````````````````````` - -When a shard receives its first command that contains a dbVersion, the shard -returns a StaleDbVersion error and the Mongos retries the operation. In a -sharded transaction, Mongos does not retry these operations and instead returns -the error to the client. For example:: - - Command distinct failed: Transaction aa09e296-472a-494f-8334-48d57ab530b6:1 was aborted on statement 0 due to: an error from cluster data placement change :: caused by :: got stale databaseVersion response from shard sh01 at host localhost:27217 :: caused by :: don't know dbVersion. - -To workaround this limitation, a driver test runner MUST run a -non-transactional ``distinct`` command on each Mongos before running any test -that uses ``distinct``. To ease the implementation drivers can simply run -``distinct`` before *every* test. - -Note that drivers can remove this workaround once `SERVER-39704`_ is resolved -so that mongos retries this operation transparently. The ``distinct`` command -is the only command allowed in a sharded transaction that uses the -``dbVersion`` concept so it is the only command affected. - -.. _SERVER-39704: https://jira.mongodb.org/browse/SERVER-39704 - -Changelog -========= - -:2022-04-22: Clarifications to ``serverless`` and ``useMultipleMongoses``. -:2019-05-15: Add operation level ``error`` field to assert any error. -:2019-03-25: Add workaround for StaleDbVersion on distinct. -:2019-03-01: Add top-level ``runOn`` field to denote server version and/or - topology requirements requirements for the test file. Removes the - ``topology`` top-level field, which is now expressed within - ``runOn`` elements. -:2019-02-28: ``useMultipleMongoses: true`` and non-targeted fail points are - mutually exclusive. -:2019-02-13: Modify test format for 4.2 sharded transactions, including - "useMultipleMongoses", ``object: testRunner``, the - ``targetedFailPoint`` operation, and recoveryToken assertions. diff --git a/src/test/spec/json/transactions/legacy-test-format.md b/src/test/spec/json/transactions/legacy-test-format.md new file mode 100644 index 000000000..59f1e6a06 --- /dev/null +++ b/src/test/spec/json/transactions/legacy-test-format.md @@ -0,0 +1,484 @@ +# Legacy Transactions Tests Format + +______________________________________________________________________ + +## Introduction + +This test format is no longer used by the transactions spec but may still be referenced by other specs (e.g. CSFLE) and +is preserved for historical record. + +## Server Fail Point + +### failCommand + +Some tests depend on a server fail point, expressed in the `failPoint` field. For example the `failCommand` fail point +allows the client to force the server to return an error. Keep in mind that the fail point only triggers for commands +listed in the "failCommands" field. See [SERVER-35004](https://jira.mongodb.org/browse/SERVER-35004) and +[SERVER-35083](https://jira.mongodb.org/browse/SERVER-35083) for more information. + +The `failCommand` fail point may be configured like so: + +```javascript + db.adminCommand({ + configureFailPoint: "failCommand", + mode: , + data: { + failCommands: ["commandName", "commandName2"], + closeConnection: , + errorCode: , + writeConcernError: , + appName: , + blockConnection: , + blockTimeMS: , + } + }); +``` + +`mode` is a generic fail point option and may be assigned a string or document value. The string values `"alwaysOn"` and +`"off"` may be used to enable or disable the fail point, respectively. A document may be used to specify either `times` +or `skip`, which are mutually exclusive: + +- `{ times: }` may be used to limit the number of times the fail point may trigger before transitioning to + `"off"`. +- `{ skip: }` may be used to defer the first trigger of a fail point, after which it will transition to + `"alwaysOn"`. + +The `data` option is a document that may be used to specify options that control the fail point's behavior. +`failCommand` supports the following `data` options, which may be combined if desired: + +- `failCommands`: Required, the list of command names to fail. +- `closeConnection`: Boolean option, which defaults to `false`. If `true`, the command will not be executed, the + connection will be closed, and the client will see a network error. +- `errorCode`: Integer option, which is unset by default. If set, the command will not be executed and the specified + command error code will be returned as a command error. +- `appName`: A string to filter which MongoClient should be affected by the failpoint. + [New in mongod 4.4.0-rc2](https://jira.mongodb.org/browse/SERVER-47195). +- `blockConnection`: Whether the server should block the affected commands. Default false. +- `blockTimeMS`: The number of milliseconds the affect commands should be blocked for. Required when blockConnection is + true. [New in mongod 4.3.4](https://jira.mongodb.org/browse/SERVER-41070). + +## Test Format + +Each YAML file has the following keys: + +- `runOn` (optional): An array of server version and/or topology requirements for which the tests can be run. If the + test environment satisfies one or more of these requirements, the tests may be executed; otherwise, this file should + be skipped. If this field is omitted, the tests can be assumed to have no particular requirements and should be + executed. Each element will have some or all of the following fields: + - `minServerVersion` (optional): The minimum server version (inclusive) required to successfully run the tests. If + this field is omitted, it should be assumed that there is no lower bound on the required server version. + + - `maxServerVersion` (optional): The maximum server version (inclusive) against which the tests can be run + successfully. If this field is omitted, it should be assumed that there is no upper bound on the required server + version. + + - `topology` (optional): An array of server topologies against which the tests can be run successfully. Valid + topologies are "single", "replicaset", "sharded", and "load-balanced". If this field is omitted, the default is + all topologies (i.e. `["single", "replicaset", "sharded", "load-balanced"]`). + + - `serverless`: (optional): Whether or not the test should be run on Atlas Serverless instances. Valid values are + "require", "forbid", and "allow". If "require", the test MUST only be run on Atlas Serverless instances. If + "forbid", the test MUST NOT be run on Atlas Serverless instances. If omitted or "allow", this option has no + effect. + + The test runner MUST be informed whether or not Atlas Serverless is being used in order to determine if this + requirement is met (e.g. through an environment variable or configuration option). + + Note: the Atlas Serverless proxy imitates mongos, so the test runner is not capable of determining if Atlas + Serverless is in use by issuing commands such as `buildInfo` or `hello`. Furthermore, connections to Atlas + Serverless use a load balancer, so the topology will appear as "load-balanced". +- `database_name` and `collection_name`: The database and collection to use for testing. +- `data`: The data that should exist in the collection under test before each test run. +- `tests`: An array of tests that are to be run independently of each other. Each test will have some or all of the + following fields: + - `description`: The name of the test. + + - `skipReason`: Optional, string describing why this test should be skipped. + + - `useMultipleMongoses` (optional): If `true`, and the topology type is `Sharded`, the MongoClient for this test + should be initialized with multiple mongos seed addresses. If `false` or omitted, only a single mongos address + should be specified. + + If `true`, the topology type is `LoadBalanced`, and Atlas Serverless is not being used, the MongoClient for this + test should be initialized with the URI of the load balancer fronting multiple servers. If `false` or omitted, the + MongoClient for this test should be initialized with the URI of the load balancer fronting a single server. + + `useMultipleMongoses` only affects `Sharded` and `LoadBalanced` topologies (excluding Atlas Serverless). + + - `clientOptions`: Optional, parameters to pass to MongoClient(). + + - `failPoint`: Optional, a server failpoint to enable expressed as the configureFailPoint command to run on the admin + database. This option and `useMultipleMongoses: true` are mutually exclusive. + + - `sessionOptions`: Optional, map of session names (e.g. "session0") to parameters to pass to + MongoClient.startSession() when creating that session. + + - `operations`: Array of documents, each describing an operation to be executed. Each document has the following + fields: + + - `name`: The name of the operation on `object`. + - `object`: The name of the object to perform the operation on. Can be "database", "collection", "session0", + "session1", or "testRunner". See the "targetedFailPoint" operation in + [Special Test Operations](#special-test-operations). + - `collectionOptions`: Optional, parameters to pass to the Collection() used for this operation. + - `databaseOptions`: Optional, parameters to pass to the Database() used for this operation. + - `command_name`: Present only when `name` is "runCommand". The name of the command to run. Required for languages + that are unable preserve the order keys in the "command" argument when parsing JSON/YAML. + - `arguments`: Optional, the names and values of arguments. + - `error`: Optional. If true, the test should expect an error or exception. This could be a server-generated or a + driver-generated error. + - `result`: The return value from the operation, if any. This field may be a single document or an array of + documents in the case of a multi-document read. If the operation is expected to return an error, the `result` is + a single document that has one or more of the following fields: + - `errorContains`: A substring of the expected error message. + - `errorCodeName`: The expected "codeName" field in the server error response. + - `errorLabelsContain`: A list of error label strings that the error is expected to have. + - `errorLabelsOmit`: A list of error label strings that the error is expected not to have. + + - `expectations`: Optional list of command-started events. + + - `outcome`: Document describing the return value and/or expected state of the collection after the operation is + executed. Contains the following fields: + + - `collection`: + - `data`: The data that should exist in the collection after the operations have run, sorted by "\_id". + +## Use as Integration Tests + +Run a MongoDB replica set with a primary, a secondary, and an arbiter, **server version 4.0.0 or later**. (Including a +secondary ensures that server selection in a transaction works properly. Including an arbiter helps ensure that no new +bugs have been introduced related to arbiters.) + +A driver that implements support for sharded transactions MUST also run these tests against a MongoDB sharded cluster +with multiple mongoses and **server version 4.2 or later**. Some tests require initializing the MongoClient with +multiple mongos seeds to ensures that mongos transaction pinning and the recoveryToken works properly. + +Load each YAML (or JSON) file using a Canonical Extended JSON parser. + +Then for each element in `tests`: + +1. If the `skipReason` field is present, skip this test completely. + +2. Create a MongoClient and call `client.admin.runCommand({killAllSessions: []})` to clean up any open transactions + from previous test failures. Ignore a command failure with error code 11601 ("Interrupted") to work around + [SERVER-38335](https://jira.mongodb.org/browse/SERVER-38335). + + - Running `killAllSessions` cleans up any open transactions from a previously failed test to prevent the current + test from blocking. It is sufficient to run this command once before starting the test suite and once after each + failed test. + - When testing against a sharded cluster run this command on ALL mongoses. + +3. Create a collection object from the MongoClient, using the `database_name` and `collection_name` fields of the YAML + file. + +4. Drop the test collection, using writeConcern "majority". + +5. Execute the "create" command to recreate the collection, using writeConcern "majority". (Creating the collection + inside a transaction is prohibited, so create it explicitly.) + +6. If the YAML file contains a `data` array, insert the documents in `data` into the test collection, using + writeConcern "majority". + +7. When testing against a sharded cluster run a `distinct` command on the newly created collection on all mongoses. For + an explanation see, + [Why do tests that run distinct sometimes fail with StaleDbVersion?](#why-do-tests-that-run-distinct-sometimes-fail-with-staledbversion) + + +8. If `failPoint` is specified, its value is a configureFailPoint command. Run the command on the admin database to + enable the fail point. + +9. Create a **new** MongoClient `client`, with Command Monitoring listeners enabled. (Using a new MongoClient for each + test ensures a fresh session pool that hasn't executed any transactions previously, so the tests can assert actual + txnNumbers, starting from 1.) Pass this test's `clientOptions` if present. + + - When testing against a sharded cluster and `useMultipleMongoses` is `true` the client MUST be created with + multiple (valid) mongos seed addresses. + +10. Call `client.startSession` twice to create ClientSession objects `session0` and `session1`, using the test's + "sessionOptions" if they are present. Save their lsids so they are available after calling `endSession`, see + [Logical Session Id](#logical-session-id). + +11. For each element in `operations`: + + - If the operation `name` is a special test operation type, execute it and go to the next operation, otherwise + proceed to the next step. + + - Enter a "try" block or your programming language's closest equivalent. + + - Create a Database object from the MongoClient, using the `database_name` field at the top level of the test file. + + - Create a Collection object from the Database, using the `collection_name` field at the top level of the test file. + If `collectionOptions` or `databaseOptions` is present, create the Collection or Database object with the + provided options, respectively. Otherwise create the object with the default options. + + - Execute the named method on the provided `object`, passing the arguments listed. Pass `session0` or `session1` to + the method, depending on which session's name is in the arguments list. If `arguments` contains no "session", + pass no explicit session to the method. + + - If the driver throws an exception / returns an error while executing this series of operations, store the error + message and server error code. + + - If the operation's `error` field is `true`, verify that the method threw an exception or returned an error. + + - If the result document has an "errorContains" field, verify that the method threw an exception or returned an + error, and that the value of the "errorContains" field matches the error string. "errorContains" is a substring + (case-insensitive) of the actual error message. + + If the result document has an "errorCodeName" field, verify that the method threw a command failed exception or + returned an error, and that the value of the "errorCodeName" field matches the "codeName" in the server error + response. + + If the result document has an "errorLabelsContain" field, verify that the method threw an exception or returned an + error. Verify that all of the error labels in "errorLabelsContain" are present in the error or exception using + the `hasErrorLabel` method. + + If the result document has an "errorLabelsOmit" field, verify that the method threw an exception or returned an + error. Verify that none of the error labels in "errorLabelsOmit" are present in the error or exception using the + `hasErrorLabel` method. + + - If the operation returns a raw command response, eg from `runCommand`, then compare only the fields present in the + expected result document. Otherwise, compare the method's return value to `result` using the same logic as the + CRUD Spec Tests runner. + +12. Call `session0.endSession()` and `session1.endSession`. + +13. If the test includes a list of command-started events in `expectations`, compare them to the actual command-started + events using the same logic as the + [legacy Command Monitoring Spec Tests runner](../../command-logging-and-monitoring/tests/README.md), plus the + rules in the Command-Started Events instructions below. + +14. If `failPoint` is specified, disable the fail point to avoid spurious failures in subsequent tests. The fail point + may be disabled like so: + + ```javascript + db.adminCommand({ + configureFailPoint: "", + mode: "off" + }); + ``` + +15. For each element in `outcome`: + + - If `name` is "collection", verify that the test collection contains exactly the documents in the `data` array. + Ensure this find reads the latest data by using **primary read preference** with **local read concern** even + when the MongoClient is configured with another read preference or read concern. Note the server does not + guarantee that documents returned by a find command will be in inserted order. This find MUST sort by `{_id:1}`. + +### Special Test Operations + +Certain operations that appear in the "operations" array do not correspond to API methods but instead represent special +test operations. Such operations are defined on the "testRunner" object and documented here: + +#### targetedFailPoint + +The "targetedFailPoint" operation instructs the test runner to configure a fail point on a specific mongos. The mongos +to run the `configureFailPoint` is determined by the "session" argument (either "session0" or "session1"). The session +must already be pinned to a mongos server. The "failPoint" argument is the `configureFailPoint` command to run. + +If a test uses `targetedFailPoint`, disable the fail point after running all `operations` to avoid spurious failures in +subsequent tests. The fail point may be disabled like so: + +```javascript + db.adminCommand({ + configureFailPoint: "", + mode: "off" + }); +``` + +Here is an example which instructs the test runner to enable the failCommand fail point on the mongos server which +"session0" is pinned to: + +```yaml +# Enable the fail point only on the Mongos that session0 is pinned to. +- name: targetedFailPoint + object: testRunner + arguments: + session: session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: ["commitTransaction"] + closeConnection: true +``` + +Tests that use the "targetedFailPoint" operation do not include `configureFailPoint` commands in their command +expectations. Drivers MUST ensure that `configureFailPoint` commands do not appear in the list of logged commands, +either by manually filtering it from the list of observed commands or by using a different MongoClient to execute +`configureFailPoint`. + +#### assertSessionTransactionState + +The "assertSessionTransactionState" operation instructs the test runner to assert that the transaction state of the +given session is equal to the specified value. The possible values are as follows: `none`, `starting`, `in_progress`, +`committed`, `aborted`: + +```yaml +- name: assertSessionTransactionState + object: testRunner + arguments: + session: session0 + state: in_progress +``` + +#### assertSessionPinned + +The "assertSessionPinned" operation instructs the test runner to assert that the given session is pinned to a mongos: + +```yaml +- name: assertSessionPinned + object: testRunner + arguments: + session: session0 +``` + +#### assertSessionUnpinned + +The "assertSessionUnpinned" operation instructs the test runner to assert that the given session is not pinned to a +mongos: + +```yaml +- name: assertSessionPinned + object: testRunner + arguments: + session: session0 +``` + +#### assertCollectionExists + +The "assertCollectionExists" operation instructs the test runner to assert that the given collection exists in the +database: + +```yaml +- name: assertCollectionExists + object: testRunner + arguments: + database: db + collection: test +``` + +Use a `listCollections` command to check whether the collection exists. Note that it is currently not possible to run +`listCollections` from within a transaction. + +#### assertCollectionNotExists + +The "assertCollectionNotExists" operation instructs the test runner to assert that the given collection does not exist +in the database: + +```yaml +- name: assertCollectionNotExists + object: testRunner + arguments: + database: db + collection: test +``` + +Use a `listCollections` command to check whether the collection exists. Note that it is currently not possible to run +`listCollections` from within a transaction. + +#### assertIndexExists + +The "assertIndexExists" operation instructs the test runner to assert that the index with the given name exists on the +collection: + +```yaml +- name: assertIndexExists + object: testRunner + arguments: + database: db + collection: test + index: t_1 +``` + +Use a `listIndexes` command to check whether the index exists. Note that it is currently not possible to run +`listIndexes` from within a transaction. + +#### assertIndexNotExists + +The "assertIndexNotExists" operation instructs the test runner to assert that the index with the given name does not +exist on the collection: + +```yaml +- name: assertIndexNotExists + object: testRunner + arguments: + database: db + collection: test + index: t_1 +``` + +Use a `listIndexes` command to check whether the index exists. Note that it is currently not possible to run +`listIndexes` from within a transaction. + +### Command-Started Events + +The event listener used for these tests MUST ignore the security commands listed in the Command Monitoring Spec. + +#### Logical Session Id + +Each command-started event in `expectations` includes an `lsid` with the value "session0" or "session1". Tests MUST +assert that the command's actual `lsid` matches the id of the correct ClientSession named `session0` or `session1`. + +#### Null Values + +Some command-started events in `expectations` include `null` values for top level `command` fields such as `txnNumber`, +`autocommit`, and `writeConcern`. Tests MUST assert that the actual command **omits** any field that has a `null` value +in the expected command. + +#### Cursor Id + +A `getMore` value of `"42"` in a command-started event is a fake cursorId that MUST be ignored. (In the Command +Monitoring Spec tests, fake cursorIds are correlated with real ones, but that is not necessary for Transactions Spec +tests.) + +#### afterClusterTime + +A `readConcern.afterClusterTime` value of `42` in a command-started event is a fake cluster time. Drivers MUST assert +that the actual command includes an afterClusterTime. + +#### recoveryToken + +A `recoveryToken` value of `42` in a command-started event is a placeholder for an arbitrary recovery token. Drivers +MUST assert that the actual command includes a "recoveryToken" field and SHOULD assert that field is a BSON document. + +## Q & A + +### Why do tests that run distinct sometimes fail with StaleDbVersion? + +When a shard receives its first command that contains a dbVersion, the shard returns a StaleDbVersion error and the +Mongos retries the operation. In a sharded transaction, Mongos does not retry these operations and instead returns the +error to the client. For example: + +```text +Command distinct failed: Transaction aa09e296-472a-494f-8334-48d57ab530b6:1 was aborted on statement 0 due to: an error from cluster data placement change :: caused by :: got stale databaseVersion response from shard sh01 at host localhost:27217 :: caused by :: don't know dbVersion. +``` + +To workaround this limitation, a driver test runner MUST run a non-transactional `distinct` command on each Mongos +before running any test that uses `distinct`. To ease the implementation drivers can simply run `distinct` before +*every* test. + +Note that drivers can remove this workaround once [SERVER-39704](https://jira.mongodb.org/browse/SERVER-39704) is +resolved so that mongos retries this operation transparently. The `distinct` command is the only command allowed in a +sharded transaction that uses the `dbVersion` concept so it is the only command affected. + +## Changelog + +- 2024-02-15: Migrated from reStructuredText to Markdown. + +- 2024-02-07: Moved legacy test format docs to this file from README.md. + +- 2023-09-28: Add `load-balanced` to test topology requirements. + +- 2022-04-22: Clarifications to `serverless` and `useMultipleMongoses`. + +- 2019-05-15: Add operation level `error` field to assert any error. + +- 2019-03-25: Add workaround for StaleDbVersion on distinct. + +- 2019-03-01: Add top-level `runOn` field to denote server version and/or topology requirements requirements for the + test file. Removes the `topology` top-level field, which is now expressed within `runOn` elements. + +- 2019-02-28: `useMultipleMongoses: true` and non-targeted fail points are mutually exclusive. + +- 2019-02-13: Modify test format for 4.2 sharded transactions, including "useMultipleMongoses", `object: testRunner`, + the `targetedFailPoint` operation, and recoveryToken assertions. diff --git a/src/test/spec/json/transactions/legacy/abort.json b/src/test/spec/json/transactions/legacy/abort.json deleted file mode 100644 index 3729a9829..000000000 --- a/src/test/spec/json/transactions/legacy/abort.json +++ /dev/null @@ -1,621 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "implicit abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "two aborts", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "abortTransaction", - "object": "session0", - "result": { - "errorContains": "cannot call abortTransaction twice" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abort without start", - "operations": [ - { - "name": "abortTransaction", - "object": "session0", - "result": { - "errorContains": "no transaction started" - } - } - ], - "expectations": [], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abort directly after no-op commit", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "abortTransaction", - "object": "session0", - "result": { - "errorContains": "Cannot call abortTransaction after calling commitTransaction" - } - } - ], - "expectations": [], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abort directly after commit", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "abortTransaction", - "object": "session0", - "result": { - "errorContains": "Cannot call abortTransaction after calling commitTransaction" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "abort ignores TransactionAborted", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ], - "errorContains": "E11000" - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorCodeName": "NoSuchTransaction", - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abort does not apply writeConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": 10 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/abort.yml b/src/test/spec/json/transactions/legacy/abort.yml deleted file mode 100644 index aa7904323..000000000 --- a/src/test/spec/json/transactions/legacy/abort.yml +++ /dev/null @@ -1,413 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: abort - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - "$numberLong": "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: implicit abort - - operations: - # Start a transaction but don't commit - the driver calls abortTransaction - # from ClientSession.endSession(). - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: two aborts - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - name: abortTransaction - object: session0 - result: - errorContains: cannot call abortTransaction twice - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abort without start - - operations: - - name: abortTransaction - object: session0 - result: - errorContains: no transaction started - - expectations: [] - - outcome: - collection: - data: [] - - - description: abort directly after no-op commit - - operations: - - name: startTransaction - object: session0 - - name: commitTransaction - object: session0 - - name: abortTransaction # Error calling abort after no-op commit. - object: session0 - result: - errorContains: Cannot call abortTransaction after calling commitTransaction - - expectations: [] - - outcome: - collection: - data: [] - - - description: abort directly after commit - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - name: abortTransaction # Error calling abort after commit. - object: session0 - result: - errorContains: Cannot call abortTransaction after calling commitTransaction - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: abort ignores TransactionAborted - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # Abort the server transaction with a duplicate key error. - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - # DuplicateKey error code included in the bulk write error message - # returned by the server - errorContains: E11000 - # Make sure the server aborted the transaction. - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - errorCodeName: NoSuchTransaction - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # abortTransaction must ignore the TransactionAborted and succeed. - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - - outcome: - collection: - data: [] - - - description: abort does not apply writeConcern - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: 10 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - # No write concern error. - - outcome: - collection: - data: [] diff --git a/src/test/spec/json/transactions/legacy/bulk.json b/src/test/spec/json/transactions/legacy/bulk.json deleted file mode 100644 index 8a9793b8b..000000000 --- a/src/test/spec/json/transactions/legacy/bulk.json +++ /dev/null @@ -1,531 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "bulk", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$set": { - "x": 2 - } - }, - "upsert": true - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 5 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 6 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 7 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 2 - }, - "replacement": { - "y": 2 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 3 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 4 - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "_id": { - "$gte": 2 - } - }, - "update": { - "$set": { - "z": 1 - } - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "_id": { - "$gte": 6 - } - } - } - } - ] - }, - "result": { - "deletedCount": 4, - "insertedCount": 6, - "insertedIds": { - "0": 1, - "3": 3, - "4": 4, - "5": 5, - "6": 6, - "7": 7 - }, - "matchedCount": 7, - "modifiedCount": 7, - "upsertedCount": 1, - "upsertedIds": { - "2": 2 - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 1 - }, - "limit": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$set": { - "x": 1 - } - } - }, - { - "q": { - "_id": 2 - }, - "u": { - "$set": { - "x": 2 - } - }, - "upsert": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - }, - { - "_id": 5 - }, - { - "_id": 6 - }, - { - "_id": 7 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "y": 1 - } - }, - { - "q": { - "_id": 2 - }, - "u": { - "y": 2 - } - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 3 - }, - "limit": 1 - }, - { - "q": { - "_id": 4 - }, - "limit": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": { - "$gte": 2 - } - }, - "u": { - "$set": { - "z": 1 - } - }, - "multi": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": { - "$gte": 6 - } - }, - "limit": 0 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "y": 1 - }, - { - "_id": 2, - "y": 2, - "z": 1 - }, - { - "_id": 5, - "z": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/bulk.yml b/src/test/spec/json/transactions/legacy/bulk.yml deleted file mode 100644 index 513bcb09e..000000000 --- a/src/test/spec/json/transactions/legacy/bulk.yml +++ /dev/null @@ -1,268 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: bulk - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: deleteOne - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - deletedCount: 1 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: insertOne - arguments: - document: {_id: 1} - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - - name: updateOne - arguments: - filter: {_id: 2} - update: {$set: {x: 2}} - upsert: true # Produces upsertedIds: {2: 2} in the result. - - name: insertOne - arguments: - document: {_id: 3} - - name: insertOne - arguments: - document: {_id: 4} - - name: insertOne - arguments: - document: {_id: 5} - - name: insertOne - arguments: - document: {_id: 6} - - name: insertOne - arguments: - document: {_id: 7} - # Keep replaces segregated from updates, so that drivers that aren't able to coalesce - # adjacent updates and replaces into a single update command will still pass this test - - name: replaceOne - arguments: - filter: {_id: 1} - replacement: {y: 1} - - name: replaceOne - arguments: - filter: {_id: 2} - replacement: {y: 2} - - name: deleteOne - arguments: - filter: {_id: 3} - - name: deleteOne - arguments: - filter: {_id: 4} - - name: updateMany - arguments: - filter: {_id: {$gte: 2}} - update: {$set: {z: 1}} - # Keep deleteMany segregated from deleteOne, so that drivers that aren't able to coalesce - # adjacent mixed deletes into a single delete command will still pass this test - - name: deleteMany - arguments: - filter: {_id: {$gte: 6}} - result: - deletedCount: 4 - insertedCount: 6 - insertedIds: {0: 1, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7} - matchedCount: 7 - modifiedCount: 7 - upsertedCount: 1 - upsertedIds: {2: 2} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 1} - limit: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - # Commands in the bulkWrite. - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 1} - u: {$set: {x: 1}} - - q: {_id: 2} - u: {$set: {x: 2}} - upsert: true - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - - _id: 4 - - _id: 5 - - _id: 6 - - _id: 7 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 1} - u: {y: 1} - - q: {_id: 2} - u: {y: 2} - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 3} - limit: 1 - - q: {_id: 4} - limit: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: {$gte: 2}} - u: {$set: {z: 1}} - multi: true - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: {$gte: 6}} - limit: 0 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - {_id: 1, y: 1} - - {_id: 2, y: 2, z: 1} - - {_id: 5, z: 1} diff --git a/src/test/spec/json/transactions/legacy/causal-consistency.json b/src/test/spec/json/transactions/legacy/causal-consistency.json deleted file mode 100644 index 0e81bf2ff..000000000 --- a/src/test/spec/json/transactions/legacy/causal-consistency.json +++ /dev/null @@ -1,305 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1, - "count": 0 - } - ], - "tests": [ - { - "description": "causal consistency", - "clientOptions": { - "retryWrites": false - }, - "operations": [ - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "count": 1 - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "count": 1 - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$inc": { - "count": 1 - } - } - } - ], - "ordered": true, - "lsid": "session0", - "readConcern": null, - "txnNumber": null, - "startTransaction": null, - "autocommit": null, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$inc": { - "count": 1 - } - } - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "count": 2 - } - ] - } - } - }, - { - "description": "causal consistency disabled", - "clientOptions": { - "retryWrites": false - }, - "sessionOptions": { - "session0": { - "causalConsistency": false - } - }, - "operations": [ - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "count": 1 - } - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": null, - "autocommit": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 1 - }, - "u": { - "$inc": { - "count": 1 - } - } - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "count": 1 - }, - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/causal-consistency.yml b/src/test/spec/json/transactions/legacy/causal-consistency.yml deleted file mode 100644 index aaac8ac60..000000000 --- a/src/test/spec/json/transactions/legacy/causal-consistency.yml +++ /dev/null @@ -1,175 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - count: 0 - -tests: - - description: causal consistency - clientOptions: - retryWrites: false - operations: - # Update a document without a transaction. - - &updateOne - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: - $inc: {count: 1} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - # Updating the same document inside a transaction. - # Casual consistency ensures that the transaction snapshot is causally - # after the first updateOne. - - name: startTransaction - object: session0 - - *updateOne - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 1} - u: {$inc: {count: 1}} - ordered: true - lsid: session0 - readConcern: - txnNumber: - startTransaction: - autocommit: - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 1} - u: {$inc: {count: 1}} - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - count: 2 - - - description: causal consistency disabled - clientOptions: - retryWrites: false - sessionOptions: - session0: - causalConsistency: false - - operations: - # Insert a document without a transaction. - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - - name: startTransaction - object: session0 - - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: - $inc: {count: 1} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - readConcern: - lsid: session0 - txnNumber: - autocommit: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 1} - u: {$inc: {count: 1}} - ordered: true - # No afterClusterTime - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - count: 1 - - _id: 2 diff --git a/src/test/spec/json/transactions/legacy/commit.json b/src/test/spec/json/transactions/legacy/commit.json deleted file mode 100644 index faa39a65f..000000000 --- a/src/test/spec/json/transactions/legacy/commit.json +++ /dev/null @@ -1,925 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commit", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "rerun commit after empty transaction", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "multiple commits in a row", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "write concern error on commit", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": 10 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commit without start", - "operations": [ - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorContains": "no transaction started" - } - } - ], - "expectations": [], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "commit after no-op abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorContains": "Cannot call commitTransaction after calling abortTransaction" - } - } - ], - "expectations": [], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "commit after abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorContains": "Cannot call commitTransaction after calling abortTransaction" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "multiple commits after empty transaction", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "reset session state commit", - "clientOptions": { - "retryWrites": false - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorContains": "no transaction started" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": null, - "startTransaction": null, - "autocommit": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "reset session state abort", - "clientOptions": { - "retryWrites": false - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0", - "result": { - "errorContains": "no transaction started" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": null, - "startTransaction": null, - "autocommit": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/commit.yml b/src/test/spec/json/transactions/legacy/commit.yml deleted file mode 100644 index 3758bf57d..000000000 --- a/src/test/spec/json/transactions/legacy/commit.yml +++ /dev/null @@ -1,603 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] -tests: - - description: commit - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - # Again, to verify that txnNumber is incremented. - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - - description: rerun commit after empty transaction - - operations: - - name: startTransaction - object: session0 - - name: commitTransaction - object: session0 - # Rerun the commit (which does not increment the txnNumber). - - name: commitTransaction - object: session0 - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: multiple commits in a row - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: write concern error on commit - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: 10 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - # { - # 'ok': 1.0, - # 'writeConcernError': { - # 'code': 100, - # 'codeName': 'UnsatisfiableWriteConcern', - # 'errmsg': 'Not enough data-bearing nodes' - # } - # } - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - - outcome: - collection: - data: - - _id: 1 - - - description: commit without start - - operations: - - name: commitTransaction - object: session0 - result: - errorContains: no transaction started - - expectations: [] - - outcome: - collection: - data: [] - - - description: commit after no-op abort - - operations: - - name: startTransaction - object: session0 - - name: abortTransaction - object: session0 - - name: commitTransaction - object: session0 - result: - errorContains: Cannot call commitTransaction after calling abortTransaction - - expectations: [] - - outcome: - collection: - data: [] - - - description: commit after abort - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - name: commitTransaction - object: session0 - result: - errorContains: Cannot call commitTransaction after calling abortTransaction - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - - description: multiple commits after empty transaction - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - # Increments txnNumber. - - name: startTransaction - object: session0 - # These commits aren't sent to server, transaction is empty. - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session0 - # Verify that previous, empty transaction incremented txnNumber. - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - # txnNumber 2 was skipped. - txnNumber: - $numberLong: "3" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "3" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: reset session state commit - clientOptions: - retryWrites: false - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - # Running any operation after an ended transaction resets the session - # state to "no transaction". - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - # Calling commit again should error instead of re-running the commit. - - name: commitTransaction - object: session0 - result: - errorContains: no transaction started - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - readConcern: - lsid: session0 - txnNumber: - startTransaction: - autocommit: - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - - description: reset session state abort - clientOptions: - retryWrites: false - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - # Running any operation after an ended transaction resets the session - # state to "no transaction". - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - # Calling abort should error with "no transaction started" instead of - # "cannot call abortTransaction twice". - - name: abortTransaction - object: session0 - result: - errorContains: no transaction started - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - readConcern: - lsid: session0 - txnNumber: - startTransaction: - autocommit: - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - _id: 2 diff --git a/src/test/spec/json/transactions/legacy/count.json b/src/test/spec/json/transactions/legacy/count.json deleted file mode 100644 index 169296416..000000000 --- a/src/test/spec/json/transactions/legacy/count.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0.2", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "tests": [ - { - "description": "count", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "count", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorCodeName": "OperationNotSupportedInTransaction", - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "count": "test", - "query": { - "_id": 1 - }, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "count", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/count.yml b/src/test/spec/json/transactions/legacy/count.yml deleted file mode 100644 index e3a1da2fc..000000000 --- a/src/test/spec/json/transactions/legacy/count.yml +++ /dev/null @@ -1,67 +0,0 @@ -runOn: - # SERVER-35388 introduced OperationNotSupportedInTransaction in 4.0.2 - - - minServerVersion: "4.0.2" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - -tests: - - description: count - - operations: - - name: startTransaction - object: session0 - - name: count - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - errorCodeName: OperationNotSupportedInTransaction - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - count: *collection_name - query: - _id: 1 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: count - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: - *data diff --git a/src/test/spec/json/transactions/legacy/create-collection.json b/src/test/spec/json/transactions/legacy/create-collection.json deleted file mode 100644 index 9071c59c4..000000000 --- a/src/test/spec/json/transactions/legacy/create-collection.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.4", - "topology": [ - "replicaset", - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "explicitly create collection using create command", - "operations": [ - { - "name": "dropCollection", - "object": "database", - "arguments": { - "collection": "test" - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "createCollection", - "object": "database", - "arguments": { - "session": "session0", - "collection": "test" - } - }, - { - "name": "assertCollectionNotExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertCollectionExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "drop": "test", - "writeConcern": null - }, - "command_name": "drop", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "create": "test", - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "create", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "implicitly create collection using insert", - "operations": [ - { - "name": "dropCollection", - "object": "database", - "arguments": { - "collection": "test" - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "assertCollectionNotExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertCollectionExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "drop": "test", - "writeConcern": null - }, - "command_name": "drop", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/create-collection.yml b/src/test/spec/json/transactions/legacy/create-collection.yml deleted file mode 100644 index b94326a16..000000000 --- a/src/test/spec/json/transactions/legacy/create-collection.yml +++ /dev/null @@ -1,131 +0,0 @@ -runOn: - - - minServerVersion: "4.3.4" - topology: ["replicaset", "sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: explicitly create collection using create command - - operations: - - name: dropCollection - object: database - arguments: - collection: *collection_name - - name: startTransaction - object: session0 - - name: createCollection - object: database - arguments: - session: session0 - collection: *collection_name - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - - name: commitTransaction - object: session0 - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - - expectations: - - command_started_event: - command: - drop: *collection_name - writeConcern: - command_name: drop - database_name: *database_name - - command_started_event: - command: - create: *collection_name - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: create - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - - description: implicitly create collection using insert - - operations: - - name: dropCollection - object: database - arguments: - collection: *collection_name - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: assertCollectionNotExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - - name: commitTransaction - object: session0 - - name: assertCollectionExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - - expectations: - - command_started_event: - command: - drop: *collection_name - writeConcern: - command_name: drop - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin diff --git a/src/test/spec/json/transactions/legacy/create-index.json b/src/test/spec/json/transactions/legacy/create-index.json deleted file mode 100644 index 2ff09c928..000000000 --- a/src/test/spec/json/transactions/legacy/create-index.json +++ /dev/null @@ -1,237 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.4", - "topology": [ - "replicaset", - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "create index on a non-existing collection", - "operations": [ - { - "name": "dropCollection", - "object": "database", - "arguments": { - "collection": "test" - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "createIndex", - "object": "collection", - "arguments": { - "session": "session0", - "name": "t_1", - "keys": { - "x": 1 - } - } - }, - { - "name": "assertIndexNotExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test", - "index": "t_1" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertIndexExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test", - "index": "t_1" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "drop": "test", - "writeConcern": null - }, - "command_name": "drop", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "createIndexes": "test", - "indexes": [ - { - "name": "t_1", - "key": { - "x": 1 - } - } - ], - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "createIndexes", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "create index on a collection created within the same transaction", - "operations": [ - { - "name": "dropCollection", - "object": "database", - "arguments": { - "collection": "test" - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "createCollection", - "object": "database", - "arguments": { - "session": "session0", - "collection": "test" - } - }, - { - "name": "createIndex", - "object": "collection", - "arguments": { - "session": "session0", - "name": "t_1", - "keys": { - "x": 1 - } - } - }, - { - "name": "assertIndexNotExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test", - "index": "t_1" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertIndexExists", - "object": "testRunner", - "arguments": { - "database": "transaction-tests", - "collection": "test", - "index": "t_1" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "drop": "test", - "writeConcern": null - }, - "command_name": "drop", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "create": "test", - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "create", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "createIndexes": "test", - "indexes": [ - { - "name": "t_1", - "key": { - "x": 1 - } - } - ], - "lsid": "session0", - "writeConcern": null - }, - "command_name": "createIndexes", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/create-index.yml b/src/test/spec/json/transactions/legacy/create-index.yml deleted file mode 100644 index d2380f474..000000000 --- a/src/test/spec/json/transactions/legacy/create-index.yml +++ /dev/null @@ -1,152 +0,0 @@ -runOn: - - - minServerVersion: "4.3.4" - topology: ["replicaset", "sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: create index on a non-existing collection - - operations: - - name: dropCollection - object: database - arguments: - collection: *collection_name - - name: startTransaction - object: session0 - - name: createIndex - object: collection - arguments: - session: session0 - name: &index_name "t_1" - keys: - x: 1 - - name: assertIndexNotExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - index: *index_name - - name: commitTransaction - object: session0 - - name: assertIndexExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - index: *index_name - - expectations: - - command_started_event: - command: - drop: *collection_name - writeConcern: - command_name: drop - database_name: *database_name - - command_started_event: - command: - createIndexes: *collection_name - indexes: - - name: *index_name - key: - x: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: createIndexes - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - - description: create index on a collection created within the same transaction - - operations: - - name: dropCollection - object: database - arguments: - collection: *collection_name - - name: startTransaction - object: session0 - - name: createCollection - object: database - arguments: - session: session0 - collection: *collection_name - - name: createIndex - object: collection - arguments: - session: session0 - name: *index_name - keys: - x: 1 - - name: assertIndexNotExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - index: *index_name - - name: commitTransaction - object: session0 - - name: assertIndexExists - object: testRunner - arguments: - database: *database_name - collection: *collection_name - index: *index_name - - expectations: - - command_started_event: - command: - drop: *collection_name - writeConcern: - command_name: drop - database_name: *database_name - - command_started_event: - command: - create: *collection_name - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: create - database_name: *database_name - - command_started_event: - command: - createIndexes: *collection_name - indexes: - - name: *index_name - key: - x: 1 - lsid: session0 - writeConcern: - command_name: createIndexes - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin diff --git a/src/test/spec/json/transactions/legacy/delete.json b/src/test/spec/json/transactions/legacy/delete.json deleted file mode 100644 index 65b832703..000000000 --- a/src/test/spec/json/transactions/legacy/delete.json +++ /dev/null @@ -1,327 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - }, - { - "_id": 5 - } - ], - "tests": [ - { - "description": "delete", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "deleteMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$lte": 3 - } - } - }, - "result": { - "deletedCount": 2 - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 1 - }, - "limit": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": { - "$lte": 3 - } - }, - "limit": 0 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 4 - }, - "limit": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 5 - } - ] - } - } - }, - { - "description": "collection writeConcern ignored for delete", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "deleteOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "deleteMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$lte": 3 - } - } - }, - "result": { - "deletedCount": 2 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 1 - }, - "limit": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": { - "$lte": 3 - } - }, - "limit": 0 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/delete.yml b/src/test/spec/json/transactions/legacy/delete.yml deleted file mode 100644 index a1c8fd608..000000000 --- a/src/test/spec/json/transactions/legacy/delete.yml +++ /dev/null @@ -1,192 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - - _id: 5 - -tests: - - description: delete - - operations: - - name: startTransaction - object: session0 - - name: deleteOne - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - deletedCount: 1 - - name: deleteMany - object: collection - arguments: - session: session0 - filter: - _id: {$lte: 3} - result: - deletedCount: 2 - - name: deleteOne - object: collection - arguments: - session: session0 - filter: - _id: 4 - result: - deletedCount: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 1} - limit: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: {$lte: 3}} - limit: 0 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 4} - limit: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 5 - - - description: collection writeConcern ignored for delete - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: deleteOne - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: - _id: 1 - result: - deletedCount: 1 - - name: deleteMany - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: - _id: {$lte: 3} - result: - deletedCount: 2 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 1} - limit: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: {$lte: 3}} - limit: 0 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: delete - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin diff --git a/src/test/spec/json/transactions/legacy/error-labels-blockConnection.json b/src/test/spec/json/transactions/legacy/error-labels-blockConnection.json deleted file mode 100644 index 56b646f7a..000000000 --- a/src/test/spec/json/transactions/legacy/error-labels-blockConnection.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.2", - "topology": [ - "replicaset", - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors", - "clientOptions": { - "socketTimeoutMS": 100 - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "blockConnection": true, - "blockTimeMS": 150 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/error-labels-blockConnection.yml b/src/test/spec/json/transactions/legacy/error-labels-blockConnection.yml deleted file mode 100644 index 36d4aeff8..000000000 --- a/src/test/spec/json/transactions/legacy/error-labels-blockConnection.yml +++ /dev/null @@ -1,113 +0,0 @@ -# This file contains a single test that should be in error-labels.yml. The test -# was moved from error-labels.yml during the spec work for client-side -# operations timeout because it uses the blockConnection parameter in -# failCommand, which is only available in server versions 4.2+. It should be -# merged back into error-labels.yml when that test file is ported to the -# unified test format as the format allows for per-test runOn requirements. - -runOn: - - - minServerVersion: "4.2" - topology: ["replicaset", "sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - # This test previously used failCommand with closeConnection=true to force a - # network error, but this does not work after CSOT is implemented because - # network errors are retried indefinitely. It has been changed to use - # socketTimeoutMS with blockConnection to force a network error because - # drivers only retry socketTimeoutMS-related errors once rather than - # indefinitely. - - description: add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors - - clientOptions: - socketTimeoutMS: 100 - - failPoint: - # Drivers stop retrying after two socket timeouts that occur due to the use of socketTimeoutMS. - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - blockConnection: true - blockTimeMS: 150 - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/error-labels.json b/src/test/spec/json/transactions/legacy/error-labels.json deleted file mode 100644 index 0be19c731..000000000 --- a/src/test/spec/json/transactions/legacy/error-labels.json +++ /dev/null @@ -1,2086 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ], - "serverless": "forbid" - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "DuplicateKey errors do not contain transient label", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 1 - }, - { - "_id": 1 - } - ] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ], - "errorContains": "E11000" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - }, - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "NotWritablePrimary errors contain transient label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 10107 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "WriteConflict errors contain transient label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 112 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "NoSuchTransaction errors contain transient label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 251 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "NoSuchTransaction errors on commit contain transient label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 251 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "add TransientTransactionError label to connection errors, but do not add RetryableWriteError label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 4 - }, - "data": { - "failCommands": [ - "insert", - "find", - "aggregate", - "distinct" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "session": "session0" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": {}, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to retryable commit errors", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 11602, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to writeConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "do not add RetryableWriteError label to writeConcernError ShutdownInProgress that occurs within transaction", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [], - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 64, - "errmsg": "multiple errors reported" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed with wtimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 64, - "codeName": "WriteConcernFailed", - "errmsg": "waiting for replication timed out", - "errInfo": { - "wtimeout": true - } - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "omit UnknownTransactionCommitResult label from writeConcernError UnsatisfiableWriteConcern", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 100, - "errmsg": "Not enough data-bearing nodes" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "omit UnknownTransactionCommitResult label from writeConcernError UnknownReplWriteConcern", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 79, - "errmsg": "No write concern mode named 'blah' found in replica set configuration" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "RetryableWriteConcern", - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "do not add UnknownTransactionCommitResult label to MaxTimeMSExpired inside transactions", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 50 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "maxTimeMS": 60000, - "session": "session0" - }, - "result": { - "errorLabelsOmit": [ - "RetryableWriteError", - "UnknownTransactionCommitResult", - "TransientTransactionError" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": {}, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "maxTimeMS": 60000 - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "add UnknownTransactionCommitResult label to MaxTimeMSExpired", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 50 - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - }, - "maxCommitTimeMS": 60000 - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "add UnknownTransactionCommitResult label to writeConcernError MaxTimeMSExpired", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 50, - "errmsg": "operation exceeded time limit" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - }, - "maxCommitTimeMS": 60000 - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/error-labels.yml b/src/test/spec/json/transactions/legacy/error-labels.yml deleted file mode 100644 index 1f8dbe4db..000000000 --- a/src/test/spec/json/transactions/legacy/error-labels.yml +++ /dev/null @@ -1,1273 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: DuplicateKey errors do not contain transient label - - operations: - - name: startTransaction - object: session0 - - name: insertMany - object: collection - arguments: - session: session0 - documents: - - _id: 1 - - _id: 1 - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - # DuplicateKey error code included in the bulk write error message - # returned by the server - errorContains: E11000 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: NotWritablePrimary errors contain transient label - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 10107 # NotWritablePrimary - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - # Note, the server will return the errorLabel in this case. - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: WriteConflict errors contain transient label - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 112 # WriteConflict - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - # Note, the server will return the errorLabel in this case. - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: NoSuchTransaction errors contain transient label - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - errorCode: 251 # NoSuchTransaction - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - # Note, the server will return the errorLabel in this case. - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: NoSuchTransaction errors on commit contain transient label - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 251 # NoSuchTransaction - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - # Note, the server will return the errorLabel in this case. - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult"] - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: add TransientTransactionError label to connection errors, but do not add RetryableWriteError label - - failPoint: - configureFailPoint: failCommand - mode: { times: 4 } - data: - failCommands: ["insert", "find", "aggregate", "distinct"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: &transient_label_only - errorLabelsContain: ["TransientTransactionError"] - # While a connection error would normally be retryable, these are not because - # they occur within a transaction; ensure the driver does not add the - # RetryableWriteError label to these errors. - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult"] - - name: find - object: collection - arguments: - session: session0 - result: *transient_label_only - - name: aggregate - object: collection - arguments: - pipeline: - - $project: - _id: 1 - session: session0 - result: *transient_label_only - - name: distinct - object: collection - arguments: - fieldName: _id - session: session0 - result: *transient_label_only - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - find: *collection_name - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: {} - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: distinct - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: add RetryableWriteError and UnknownTransactionCommitResult labels to retryable commit errors - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorCode: 11602 # InterruptedDueToReplStateChange - errorLabels: ["RetryableWriteError"] - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: add RetryableWriteError and UnknownTransactionCommitResult labels to writeConcernError ShutdownInProgress - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: do not add RetryableWriteError label to writeConcernError ShutdownInProgress that occurs within transaction - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - errorLabelsContain: [] - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError", "UnknownTransactionCommitResult"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 64 # WriteConcernFailed without wtimeout - errmsg: multiple errors reported - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: add UnknownTransactionCommitResult label to writeConcernError WriteConcernFailed with wtimeout - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 64 - codeName: WriteConcernFailed - errmsg: waiting for replication timed out - errInfo: {wtimeout: True} - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: omit UnknownTransactionCommitResult label from writeConcernError UnsatisfiableWriteConcern - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 100 # UnsatisfiableWriteConcern - errmsg: Not enough data-bearing nodes - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError", "UnknownTransactionCommitResult"] - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: omit UnknownTransactionCommitResult label from writeConcernError UnknownReplWriteConcern - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 79 # UnknownReplWriteConcern - errmsg: No write concern mode named 'blah' found in replica set configuration - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsOmit: ["RetryableWriteConcern", "TransientTransactionError", "UnknownTransactionCommitResult"] - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: do not add UnknownTransactionCommitResult label to MaxTimeMSExpired inside transactions - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["aggregate"] - errorCode: 50 # MaxTimeMSExpired - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: aggregate - object: collection - arguments: - pipeline: - - $project: - _id: 1 - maxTimeMS: 60000 - session: session0 - result: - errorLabelsOmit: ["RetryableWriteError", "UnknownTransactionCommitResult", "TransientTransactionError"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: {} - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - autocommit: false - maxTimeMS: 60000 - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: add UnknownTransactionCommitResult label to MaxTimeMSExpired - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 50 # MaxTimeMSExpired - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - maxCommitTimeMS: 60000 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: add UnknownTransactionCommitResult label to writeConcernError MaxTimeMSExpired - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 50 # MaxTimeMSExpired - errmsg: operation exceeded time limit - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - maxCommitTimeMS: 60000 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["UnknownTransactionCommitResult"] - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError"] - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/errors-client.json b/src/test/spec/json/transactions/legacy/errors-client.json deleted file mode 100644 index 15fae96fe..000000000 --- a/src/test/spec/json/transactions/legacy/errors-client.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "Client side error in command starting transaction", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "x": 1 - } - }, - "error": true - }, - { - "name": "assertSessionTransactionState", - "object": "testRunner", - "arguments": { - "session": "session0", - "state": "starting" - } - } - ] - }, - { - "description": "Client side error when transaction is in progress", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "x": 1 - } - }, - "error": true - }, - { - "name": "assertSessionTransactionState", - "object": "testRunner", - "arguments": { - "session": "session0", - "state": "in_progress" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/errors-client.yml b/src/test/spec/json/transactions/legacy/errors-client.yml deleted file mode 100644 index 38b110424..000000000 --- a/src/test/spec/json/transactions/legacy/errors-client.yml +++ /dev/null @@ -1,55 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] -tests: - - description: Client side error in command starting transaction - - operations: - - name: startTransaction - object: session0 - - name: updateOne - object: collection - arguments: - session: session0 - filter: { _id: 1 } - update: { x: 1 } - error: true - - name: assertSessionTransactionState - object: testRunner - arguments: - session: session0 - state: starting - - - description: Client side error when transaction is in progress - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: { _id: 1 } - result: - insertedId: 1 - - name: updateOne - object: collection - arguments: - session: session0 - filter: { _id: 1 } - update: { x: 1 } - error: true - - name: assertSessionTransactionState - object: testRunner - arguments: - session: session0 - state: in_progress diff --git a/src/test/spec/json/transactions/legacy/errors.json b/src/test/spec/json/transactions/legacy/errors.json deleted file mode 100644 index 5fc4905e8..000000000 --- a/src/test/spec/json/transactions/legacy/errors.json +++ /dev/null @@ -1,222 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "start insert start", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "startTransaction", - "object": "session0", - "result": { - "errorContains": "transaction already in progress" - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ] - }, - { - "description": "start twice", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0", - "result": { - "errorContains": "transaction already in progress" - } - } - ] - }, - { - "description": "commit and start twice", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0", - "result": { - "errorContains": "transaction already in progress" - } - } - ] - }, - { - "description": "write conflict commit", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "startTransaction", - "object": "session1" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session1", - "document": { - "_id": 1 - } - }, - "result": { - "errorCodeName": "WriteConflict", - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session1", - "result": { - "errorCodeName": "NoSuchTransaction", - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - } - ] - }, - { - "description": "write conflict abort", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "startTransaction", - "object": "session1" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session1", - "document": { - "_id": 1 - } - }, - "result": { - "errorCodeName": "WriteConflict", - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "abortTransaction", - "object": "session1" - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/errors.yml b/src/test/spec/json/transactions/legacy/errors.yml deleted file mode 100644 index 4a7ee67c3..000000000 --- a/src/test/spec/json/transactions/legacy/errors.yml +++ /dev/null @@ -1,133 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] -tests: - - description: start insert start - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: startTransaction - object: session0 - result: - # Client-side error. - errorContains: transaction already in progress - # Just to clean up. - - name: commitTransaction - object: session0 - - - description: start twice - - operations: - - name: startTransaction - object: session0 - - name: startTransaction - object: session0 - result: - # Client-side error. - errorContains: transaction already in progress - - - description: commit and start twice - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - name: startTransaction - object: session0 - - name: startTransaction - object: session0 - result: - # Client-side error. - errorContains: transaction already in progress - - - description: write conflict commit - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: startTransaction - object: session1 - - name: insertOne - object: collection - arguments: - session: session1 - document: - _id: 1 - result: - errorCodeName: WriteConflict - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session1 - result: - errorCodeName: NoSuchTransaction - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - - - description: write conflict abort - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: startTransaction - object: session1 - - name: insertOne - object: collection - arguments: - session: session1 - document: - _id: 1 - result: - errorCodeName: WriteConflict - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - - name: commitTransaction - object: session0 - # Driver ignores "NoSuchTransaction" error. - - name: abortTransaction - object: session1 diff --git a/src/test/spec/json/transactions/legacy/findOneAndDelete.json b/src/test/spec/json/transactions/legacy/findOneAndDelete.json deleted file mode 100644 index d82657a9f..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndDelete.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "tests": [ - { - "description": "findOneAndDelete", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - } - }, - "result": { - "_id": 3 - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - } - }, - "result": null - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "remove": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 4 - }, - "remove": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "collection writeConcern ignored for findOneAndDelete", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - } - }, - "result": { - "_id": 3 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "remove": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/findOneAndDelete.yml b/src/test/spec/json/transactions/legacy/findOneAndDelete.yml deleted file mode 100644 index fe2e63974..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndDelete.yml +++ /dev/null @@ -1,134 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - - _id: 2 - - _id: 3 - -tests: - - description: findOneAndDelete - - operations: - - name: startTransaction - object: session0 - - name: findOneAndDelete - object: collection - arguments: - session: session0 - filter: {_id: 3} - result: {_id: 3} - - name: findOneAndDelete - object: collection - arguments: - session: session0 - filter: {_id: 4} - result: - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - remove: True - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 4} - remove: True - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - - description: collection writeConcern ignored for findOneAndDelete - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: findOneAndDelete - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: {_id: 3} - result: {_id: 3} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - remove: True - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin diff --git a/src/test/spec/json/transactions/legacy/findOneAndReplace.json b/src/test/spec/json/transactions/legacy/findOneAndReplace.json deleted file mode 100644 index 7a54ca343..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndReplace.json +++ /dev/null @@ -1,255 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "tests": [ - { - "description": "findOneAndReplace", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "replacement": { - "x": 1 - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3 - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - }, - "replacement": { - "x": 1 - }, - "upsert": true, - "returnDocument": "After" - }, - "result": { - "_id": 4, - "x": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "x": 1 - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 4 - }, - "update": { - "x": 1 - }, - "new": true, - "upsert": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3, - "x": 1 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "collection writeConcern ignored for findOneAndReplace", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "replacement": { - "x": 1 - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "x": 1 - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/findOneAndReplace.yml b/src/test/spec/json/transactions/legacy/findOneAndReplace.yml deleted file mode 100644 index 0d6c79aa0..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndReplace.yml +++ /dev/null @@ -1,148 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - - _id: 2 - - _id: 3 - -tests: - - description: findOneAndReplace - - operations: - - name: startTransaction - object: session0 - - name: findOneAndReplace - object: collection - arguments: - session: session0 - filter: {_id: 3} - replacement: {x: 1} - returnDocument: Before - result: {_id: 3} - - name: findOneAndReplace - object: collection - arguments: - session: session0 - filter: {_id: 4} - replacement: {x: 1} - upsert: true - returnDocument: After - result: {_id: 4, x: 1} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: {x: 1} - new: false - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 4} - update: {x: 1} - new: true - upsert: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3, x: 1} - - {_id: 4, x: 1} - - - description: collection writeConcern ignored for findOneAndReplace - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: findOneAndReplace - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: {_id: 3} - replacement: {x: 1} - returnDocument: Before - result: {_id: 3} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: {x: 1} - new: false - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - diff --git a/src/test/spec/json/transactions/legacy/findOneAndUpdate.json b/src/test/spec/json/transactions/legacy/findOneAndUpdate.json deleted file mode 100644 index 7af54ba80..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndUpdate.json +++ /dev/null @@ -1,413 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "tests": [ - { - "description": "findOneAndUpdate", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3 - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true, - "returnDocument": "After" - }, - "result": { - "_id": 4, - "x": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3, - "x": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3, - "x": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": true, - "upsert": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "afterClusterTime": 42 - }, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "afterClusterTime": 42 - }, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3, - "x": 2 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "collection writeConcern ignored for findOneAndUpdate", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 3 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 3 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/findOneAndUpdate.yml b/src/test/spec/json/transactions/legacy/findOneAndUpdate.yml deleted file mode 100644 index afb9ad63b..000000000 --- a/src/test/spec/json/transactions/legacy/findOneAndUpdate.yml +++ /dev/null @@ -1,236 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - - _id: 2 - - _id: 3 - -tests: - - description: findOneAndUpdate - - operations: - - name: startTransaction - object: session0 - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 3} - update: - $inc: {x: 1} - returnDocument: Before - result: {_id: 3} - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 4} - update: - $inc: {x: 1} - upsert: true - returnDocument: After - result: {_id: 4, x: 1} - - name: commitTransaction - object: session0 - - name: startTransaction - object: session0 - # Test a second time to ensure txnNumber is incremented. - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 3} - update: - $inc: {x: 1} - returnDocument: Before - result: {_id: 3, x: 1} - - name: commitTransaction - object: session0 - # Test a third time to ensure abort works. - - name: startTransaction - object: session0 - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 3} - update: - $inc: {x: 1} - returnDocument: Before - result: {_id: 3, x: 2} - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: {$inc: {x: 1}} - new: false - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 4} - update: {$inc: {x: 1}} - new: true - upsert: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: {$inc: {x: 1}} - new: false - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - afterClusterTime: 42 - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: {$inc: {x: 1}} - new: false - lsid: session0 - txnNumber: - $numberLong: "3" - startTransaction: true - autocommit: false - readConcern: - afterClusterTime: 42 - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "3" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3, x: 2} - - {_id: 4, x: 1} - - - description: collection writeConcern ignored for findOneAndUpdate - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: findOneAndUpdate - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: {_id: 3} - update: - $inc: {x: 1} - returnDocument: Before - result: {_id: 3} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 3} - update: - $inc: {x: 1} - new: false - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: findAndModify - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - diff --git a/src/test/spec/json/transactions/legacy/insert.json b/src/test/spec/json/transactions/legacy/insert.json deleted file mode 100644 index f26e7c2a7..000000000 --- a/src/test/spec/json/transactions/legacy/insert.json +++ /dev/null @@ -1,648 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "insert", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "insertedId": 4 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 5 - } - }, - "result": { - "insertedId": 5 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 5 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - }, - { - "_id": 5 - } - ] - } - } - }, - { - "description": "insert with session1", - "operations": [ - { - "name": "startTransaction", - "object": "session1" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session1", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "session": "session1" - }, - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - } - }, - { - "name": "commitTransaction", - "object": "session1" - }, - { - "name": "startTransaction", - "object": "session1" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session1", - "document": { - "_id": 4 - } - }, - "result": { - "insertedId": 4 - } - }, - { - "name": "abortTransaction", - "object": "session1" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session1", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "ordered": true, - "lsid": "session1", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session1", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session1", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session1", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - } - }, - { - "description": "collection writeConcern without transaction", - "clientOptions": { - "retryWrites": false - }, - "operations": [ - { - "name": "insertOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": null, - "startTransaction": null, - "autocommit": null, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "collection writeConcern ignored for insert", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "insertMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/insert.yml b/src/test/spec/json/transactions/legacy/insert.yml deleted file mode 100644 index d2062fcfd..000000000 --- a/src/test/spec/json/transactions/legacy/insert.yml +++ /dev/null @@ -1,390 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: insert - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: insertMany - object: collection - arguments: - documents: - - _id: 2 - - _id: 3 - session: session0 - result: - insertedIds: {0: 2, 1: 3} - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - insertedId: 4 - - name: commitTransaction - object: session0 - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 5 - result: - insertedId: 5 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - - _id: 3 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 5 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - - _id: 5 - - # This test proves that the driver uses "session1" correctly in operations - # and APM expectations. - - description: insert with session1 - - operations: - - name: startTransaction - object: session1 - - name: insertOne - object: collection - arguments: - session: session1 - document: - _id: 1 - result: - insertedId: 1 - - name: insertMany - object: collection - arguments: - documents: - - _id: 2 - - _id: 3 - session: session1 - result: - insertedIds: {0: 2, 1: 3} - - name: commitTransaction - object: session1 - - name: startTransaction - object: session1 - - name: insertOne - object: collection - arguments: - session: session1 - document: - _id: 4 - result: - insertedId: 4 - - name: abortTransaction - object: session1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session1 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - - _id: 3 - ordered: true - lsid: session1 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session1 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session1 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session1 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - _id: 3 - - # This test proves that the driver parses the collectionOptions writeConcern. - - description: collection writeConcern without transaction - clientOptions: - retryWrites: false - operations: - - name: insertOne - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - startTransaction: - autocommit: - writeConcern: - w: majority - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - _id: 1 - - - description: collection writeConcern ignored for insert - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: insertMany - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - documents: - - _id: 2 - - _id: 3 - session: session0 - result: - insertedIds: {0: 2, 1: 3} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - - _id: 3 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - _id: 3 diff --git a/src/test/spec/json/transactions/legacy/isolation.json b/src/test/spec/json/transactions/legacy/isolation.json deleted file mode 100644 index f16b28a5e..000000000 --- a/src/test/spec/json/transactions/legacy/isolation.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "one transaction", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session1", - "filter": { - "_id": 1 - } - }, - "result": [] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [] - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session1", - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1 - } - ] - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "two transactions", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session1" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session1", - "filter": { - "_id": 1 - } - }, - "result": [] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [] - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session1", - "filter": { - "_id": 1 - } - }, - "result": [] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1 - } - ] - }, - { - "name": "commitTransaction", - "object": "session1" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/isolation.yml b/src/test/spec/json/transactions/legacy/isolation.yml deleted file mode 100644 index d48a07f29..000000000 --- a/src/test/spec/json/transactions/legacy/isolation.yml +++ /dev/null @@ -1,133 +0,0 @@ -# Test snapshot isolation. -# This test doesn't check contents of command-started events. -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: one transaction - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: find - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - - {_id: 1} - - name: find - object: collection - arguments: - session: session1 - filter: - _id: 1 - result: [] - - name: find - object: collection - arguments: - filter: - _id: 1 - result: [] - - name: commitTransaction - object: session0 - - name: find - object: collection - arguments: - session: session1 - filter: - _id: 1 - result: - - {_id: 1} - - name: find - object: collection - arguments: - filter: - _id: 1 - result: - - {_id: 1} - - outcome: - collection: - data: - - _id: 1 - - - description: two transactions - - operations: - - name: startTransaction - object: session0 - - name: startTransaction - object: session1 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: find - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - - {_id: 1} - - name: find - object: collection - arguments: - session: session1 - filter: - _id: 1 - result: [] - - name: find - object: collection - arguments: - filter: - _id: 1 - result: [] - - name: commitTransaction - object: session0 - # Snapshot isolation in session1, not read-committed. - - name: find - object: collection - arguments: - session: session1 - filter: - _id: 1 - result: [] - - name: find - object: collection - arguments: - filter: - _id: 1 - result: - - {_id: 1} - - name: commitTransaction - object: session1 - - outcome: - collection: - data: - - {_id: 1} diff --git a/src/test/spec/json/transactions/legacy/mongos-pin-auto-tests.py b/src/test/spec/json/transactions/legacy/mongos-pin-auto-tests.py deleted file mode 100644 index 1072ec290..000000000 --- a/src/test/spec/json/transactions/legacy/mongos-pin-auto-tests.py +++ /dev/null @@ -1,340 +0,0 @@ -import itertools -import sys - -# Require Python 3.7+ for ordered dictionaries so that the order of the -# generated tests remain the same. -# Usage: -# python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml -if sys.version_info[:2] < (3, 7): - print('ERROR: This script requires Python >= 3.7, not:') - print(sys.version) - print('Usage: python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml') - exit(1) - -HEADER = '''# Autogenerated tests that transient errors in a transaction unpin the session. -# See mongos-pin-auto-tests.py -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: remain pinned after non-transient Interrupted error on insertOne - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 3} - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - errorCodeName: Interrupted - - &assertSessionPinned - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: &outcome - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - - description: unpin after transient error within a transaction - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # abortTransaction succeeds immediately on any mongos. - - &assertSessionUnpinned - name: assertSessionUnpinned - object: testRunner - arguments: - session: session0 - - &abortTransaction - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - outcome: &outcome - collection: - data: *data - - # The rest of the tests in this file test every operation type against - # multiple types of transient errors (connection and error code).''' - -TEMPLATE = ''' - - description: {test_name} {error_name} error on {op_name} {command_name} - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {{times: 1}} - data: - failCommands: ["{command_name}"] - {error_data} - - name: {op_name} - object: {object_name} - arguments: - session: session0 - {op_args} - result: - {error_labels}: ["TransientTransactionError"] - - *{assertion} - - *abortTransaction - outcome: *outcome -''' - - -# Maps from op_name to (command_name, object_name, op_args) -OPS = { - # Write ops: - 'insertOne': ('insert', 'collection', r'document: {_id: 4}'), - 'insertMany': ('insert', 'collection', r'documents: [{_id: 4}, {_id: 5}]'), - 'updateOne': ('update', 'collection', r'''filter: {_id: 1} - update: {$inc: {x: 1}}'''), - 'replaceOne': ('update', 'collection', r'''filter: {_id: 1} - replacement: {y: 1}'''), - 'updateMany': ('update', 'collection', r'''filter: {_id: {$gte: 1}} - update: {$set: {z: 1}}'''), - 'deleteOne': ('delete', 'collection', r'filter: {_id: 1}'), - 'deleteMany': ('delete', 'collection', r'filter: {_id: {$gte: 1}}'), - 'findOneAndDelete': ('findAndModify', 'collection', r'filter: {_id: 1}'), - 'findOneAndUpdate': ('findAndModify', 'collection', r'''filter: {_id: 1} - update: {$inc: {x: 1}} - returnDocument: Before'''), - 'findOneAndReplace': ('findAndModify', 'collection', r'''filter: {_id: 1} - replacement: {y: 1} - returnDocument: Before'''), - # Bulk write insert/update/delete: - 'bulkWrite insert': ('insert', 'collection', r'''requests: - - name: insertOne - arguments: - document: {_id: 1}'''), - 'bulkWrite update': ('update', 'collection', r'''requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}}'''), - 'bulkWrite delete': ('delete', 'collection', r'''requests: - - name: deleteOne - arguments: - filter: {_id: 1}'''), - # Read ops: - 'find': ('find', 'collection', r'filter: {_id: 1}'), - 'countDocuments': ('aggregate', 'collection', r'filter: {}'), - 'aggregate': ('aggregate', 'collection', r'pipeline: []'), - 'distinct': ('distinct', 'collection', r'fieldName: _id'), - # runCommand: - 'runCommand': ( - 'insert', - r'''database - command_name: insert''', # runCommand requires command_name. - r'''command: - insert: *collection_name - documents: - - _id : 1'''), -} - -# Maps from error_name to error_data. -NON_TRANSIENT_ERRORS = { - 'Interrupted': 'errorCode: 11601', -} - -# Maps from error_name to error_data. -TRANSIENT_ERRORS = { - 'connection': 'closeConnection: true', - 'ShutdownInProgress': 'errorCode: 91', -} - - -def create_pin_test(op_name, error_name): - test_name = 'remain pinned after non-transient' - assertion = 'assertSessionPinned' - error_labels = 'errorLabelsOmit' - command_name, object_name, op_args = OPS[op_name] - error_data = NON_TRANSIENT_ERRORS[error_name] - if op_name.startswith('bulkWrite'): - op_name = 'bulkWrite' - return TEMPLATE.format(**locals()) - - -def create_unpin_test(op_name, error_name): - test_name = 'unpin after transient' - assertion = 'assertSessionUnpinned' - error_labels = 'errorLabelsContain' - command_name, object_name, op_args = OPS[op_name] - error_data = TRANSIENT_ERRORS[error_name] - if op_name.startswith('bulkWrite'): - op_name = 'bulkWrite' - return TEMPLATE.format(**locals()) - -tests = [] -for op_name, error_name in itertools.product(OPS, NON_TRANSIENT_ERRORS): - tests.append(create_pin_test(op_name, error_name)) -for op_name, error_name in itertools.product(OPS, TRANSIENT_ERRORS): - tests.append(create_unpin_test(op_name, error_name)) - -print(HEADER) -print(''.join(tests)) diff --git a/src/test/spec/json/transactions/legacy/mongos-pin-auto.json b/src/test/spec/json/transactions/legacy/mongos-pin-auto.json deleted file mode 100644 index 037f212f4..000000000 --- a/src/test/spec/json/transactions/legacy/mongos-pin-auto.json +++ /dev/null @@ -1,4815 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ], - "serverless": "forbid" - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ], - "tests": [ - { - "description": "remain pinned after non-transient Interrupted error on insertOne", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError", - "UnknownTransactionCommitResult" - ], - "errorCodeName": "Interrupted" - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ] - } - } - }, - { - "description": "unpin after transient error within a transaction", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on insertOne insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on insertMany insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on updateOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on replaceOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on updateMany update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "updateMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on deleteOne delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on deleteMany delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "deleteMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on bulkWrite delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on find find", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on countDocuments aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "session": "session0", - "filter": {} - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on aggregate aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on distinct distinct", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient Interrupted error on runCommand insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 11601 - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on insertOne insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on insertOne insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on insertMany insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on insertMany insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on updateOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on updateOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on replaceOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on replaceOne update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on updateMany update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "updateMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on updateMany update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "updateMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on deleteOne delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on deleteOne delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "deleteOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on deleteMany delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "deleteMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on deleteMany delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "deleteMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 1 - } - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndDelete", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "closeConnection": true - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "findAndModify" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "findOneAndReplace", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - }, - "replacement": { - "y": 1 - }, - "returnDocument": "Before" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on bulkWrite insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on bulkWrite update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite update", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "update" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 1 - } - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on bulkWrite delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "closeConnection": true - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on bulkWrite delete", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "delete" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "session": "session0", - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - } - ] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on find find", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "closeConnection": true - } - } - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on find find", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "find" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on countDocuments aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - } - } - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "session": "session0", - "filter": {} - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on countDocuments aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "session": "session0", - "filter": {} - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on aggregate aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "closeConnection": true - } - } - } - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on aggregate aggregate", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "aggregate" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "session": "session0", - "pipeline": [] - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on distinct distinct", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "closeConnection": true - } - } - } - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on distinct distinct", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "distinct" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient connection error on runCommand insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient ShutdownInProgress error on runCommand insert", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "errorCode": 91 - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "assertSessionUnpinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/mongos-pin-auto.yml b/src/test/spec/json/transactions/legacy/mongos-pin-auto.yml deleted file mode 100644 index 7e2e3e445..000000000 --- a/src/test/spec/json/transactions/legacy/mongos-pin-auto.yml +++ /dev/null @@ -1,1674 +0,0 @@ -# Autogenerated tests that transient errors in a transaction unpin the session. -# See mongos-pin-auto-tests.py -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: remain pinned after non-transient Interrupted error on insertOne - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 3} - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] - errorCodeName: Interrupted - - &assertSessionPinned - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: &outcome - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - - description: unpin after transient error within a transaction - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &initialCommand - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # abortTransaction succeeds immediately on any mongos. - - &assertSessionUnpinned - name: assertSessionUnpinned - object: testRunner - arguments: - session: session0 - - &abortTransaction - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - outcome: &outcome - collection: - data: *data - - # The rest of the tests in this file test every operation type against - # multiple types of transient errors (connection and error code). - - - description: remain pinned after non-transient Interrupted error on insertOne insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 4} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on insertMany insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: insertMany - object: collection - arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on updateOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on replaceOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: replaceOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on updateMany update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: updateMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on deleteOne delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: deleteOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on deleteMany delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: deleteMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndDelete - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - returnDocument: Before - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 11601 - - name: findOneAndReplace - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - returnDocument: Before - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: insertOne - arguments: - document: {_id: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 11601 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on bulkWrite delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 11601 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on find find - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - errorCode: 11601 - - name: find - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on countDocuments aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 11601 - - name: countDocuments - object: collection - arguments: - session: session0 - filter: {} - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on aggregate aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 11601 - - name: aggregate - object: collection - arguments: - session: session0 - pipeline: [] - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on distinct distinct - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - errorCode: 11601 - - name: distinct - object: collection - arguments: - session: session0 - fieldName: _id - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: remain pinned after non-transient Interrupted error on runCommand insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 11601 - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - result: - errorLabelsOmit: ["TransientTransactionError"] - - *assertSessionPinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on insertOne insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 4} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on insertOne insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: insertOne - object: collection - arguments: - session: session0 - document: {_id: 4} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on insertMany insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: insertMany - object: collection - arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on insertMany insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: insertMany - object: collection - arguments: - session: session0 - documents: [{_id: 4}, {_id: 5}] - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on updateOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on updateOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on replaceOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: replaceOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on replaceOne update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: replaceOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on updateMany update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: updateMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on updateMany update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: updateMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - update: {$set: {z: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on deleteOne delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: deleteOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on deleteOne delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: deleteOne - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on deleteMany delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: deleteMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on deleteMany delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: deleteMany - object: collection - arguments: - session: session0 - filter: {_id: {$gte: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndDelete findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndDelete - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndDelete - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndUpdate findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - returnDocument: Before - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndUpdate - object: collection - arguments: - session: session0 - filter: {_id: 1} - update: {$inc: {x: 1}} - returnDocument: Before - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on findOneAndReplace findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - closeConnection: true - - name: findOneAndReplace - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - returnDocument: Before - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["findAndModify"] - errorCode: 91 - - name: findOneAndReplace - object: collection - arguments: - session: session0 - filter: {_id: 1} - replacement: {y: 1} - returnDocument: Before - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: insertOne - arguments: - document: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: insertOne - arguments: - document: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - closeConnection: true - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite update - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["update"] - errorCode: 91 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: updateOne - arguments: - filter: {_id: 1} - update: {$set: {x: 1}} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on bulkWrite delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - closeConnection: true - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on bulkWrite delete - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["delete"] - errorCode: 91 - - name: bulkWrite - object: collection - arguments: - session: session0 - requests: - - name: deleteOne - arguments: - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on find find - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - closeConnection: true - - name: find - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on find find - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["find"] - errorCode: 91 - - name: find - object: collection - arguments: - session: session0 - filter: {_id: 1} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on countDocuments aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - closeConnection: true - - name: countDocuments - object: collection - arguments: - session: session0 - filter: {} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on countDocuments aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 91 - - name: countDocuments - object: collection - arguments: - session: session0 - filter: {} - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on aggregate aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - closeConnection: true - - name: aggregate - object: collection - arguments: - session: session0 - pipeline: [] - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on aggregate aggregate - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["aggregate"] - errorCode: 91 - - name: aggregate - object: collection - arguments: - session: session0 - pipeline: [] - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on distinct distinct - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - closeConnection: true - - name: distinct - object: collection - arguments: - session: session0 - fieldName: _id - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on distinct distinct - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["distinct"] - errorCode: 91 - - name: distinct - object: collection - arguments: - session: session0 - fieldName: _id - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient connection error on runCommand insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - closeConnection: true - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - - - description: unpin after transient ShutdownInProgress error on runCommand insert - useMultipleMongoses: true - operations: - - *startTransaction - - *initialCommand - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: {times: 1} - data: - failCommands: ["insert"] - errorCode: 91 - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - result: - errorLabelsContain: ["TransientTransactionError"] - - *assertSessionUnpinned - - *abortTransaction - outcome: *outcome - diff --git a/src/test/spec/json/transactions/legacy/mongos-recovery-token.json b/src/test/spec/json/transactions/legacy/mongos-recovery-token.json deleted file mode 100644 index da4e9861d..000000000 --- a/src/test/spec/json/transactions/legacy/mongos-recovery-token.json +++ /dev/null @@ -1,511 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ], - "serverless": "forbid" - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commitTransaction explicit retries include recoveryToken", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction retry succeeds on new mongos", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - }, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction retry fails on new mongos", - "useMultipleMongoses": true, - "clientOptions": { - "heartbeatFrequencyMS": 30000 - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 7 - }, - "data": { - "failCommands": [ - "commitTransaction", - "isMaster", - "hello" - ], - "closeConnection": true - } - } - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ], - "errorCodeName": "NoSuchTransaction" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - }, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction sends recoveryToken", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "closeConnection": true - } - } - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/mongos-recovery-token.yml b/src/test/spec/json/transactions/legacy/mongos-recovery-token.yml deleted file mode 100644 index e8763db88..000000000 --- a/src/test/spec/json/transactions/legacy/mongos-recovery-token.yml +++ /dev/null @@ -1,350 +0,0 @@ -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't use recovery tokens - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: commitTransaction explicit retries include recoveryToken - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session0 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction retry succeeds on new mongos - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # Enable the fail point only on the Mongos that session0 is pinned to. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - # The client sees a retryable writeConcernError on the first - # commitTransaction due to the fail point but it actually succeeds on the - # server (SERVER-39346). The retry will succeed both on a new mongos and - # on the original. - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction retry fails on new mongos - useMultipleMongoses: true - clientOptions: - # Increase heartbeatFrequencyMS to avoid the race condition where an in - # flight heartbeat refreshes the first mongoes' SDAM state in between - # the initial commitTransaction and the retry attempt. - heartbeatFrequencyMS: 30000 - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # Enable the fail point only on the Mongos that session0 is pinned to. - # Fail hello/legacy hello to prevent the heartbeat requested directly after the - # retryable commit error from racing with server selection for the retry. - # Note: times: 7 is slightly artbitrary but it accounts for one failed - # commit and some SDAM heartbeats. A test runner will have multiple - # clients connected to this server so this fail point configuration - # is also racy. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 7 } - data: - failCommands: ["commitTransaction", "isMaster", "hello"] - closeConnection: true - # The first commitTransaction sees a retryable connection error due to - # the fail point and also fails on the server. The retry attempt on a - # new mongos will wait for the transaction to timeout and will fail - # because the transaction was aborted. Note that the retry attempt should - # not select the original mongos because that server's SDAM state is - # reset by the connection error, heartbeatFrequencyMS is high, and - # subsequent heartbeats should fail. - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - errorCodeName: NoSuchTransaction - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction sends recoveryToken - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # Enable the fail point only on the Mongos that session0 is pinned to. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - closeConnection: true - # The first abortTransaction sees a retryable connection error due to - # the fail point. The retry attempt on a new mongos will send the - # recoveryToken. Note that the retry attempt will also fail because the - # server does not yet support aborting from a new mongos, however this - # operation should "succeed" since abortTransaction ignores errors. - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] diff --git a/src/test/spec/json/transactions/legacy/pin-mongos.json b/src/test/spec/json/transactions/legacy/pin-mongos.json deleted file mode 100644 index 485a3d932..000000000 --- a/src/test/spec/json/transactions/legacy/pin-mongos.json +++ /dev/null @@ -1,1229 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ], - "serverless": "forbid" - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ], - "tests": [ - { - "description": "countDocuments", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "distinct", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "fieldName": "_id", - "session": "session0" - }, - "result": [ - 1, - 2 - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "find", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "filter": { - "_id": 2 - }, - "session": "session0" - }, - "result": [ - { - "_id": 2 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "insertOne", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 3 - }, - "session": "session0" - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 4 - }, - "session": "session0" - }, - "result": { - "insertedId": 4 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 5 - }, - "session": "session0" - }, - "result": { - "insertedId": 5 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 6 - }, - "session": "session0" - }, - "result": { - "insertedId": 6 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 7 - }, - "session": "session0" - }, - "result": { - "insertedId": 7 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 8 - }, - "session": "session0" - }, - "result": { - "insertedId": 8 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 9 - }, - "session": "session0" - }, - "result": { - "insertedId": 9 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 10 - }, - "session": "session0" - }, - "result": { - "insertedId": 10 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - }, - { - "_id": 5 - }, - { - "_id": 6 - }, - { - "_id": 7 - }, - { - "_id": 8 - }, - { - "_id": 9 - }, - { - "_id": 10 - } - ] - } - } - }, - { - "description": "mixed read write operations", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 3 - }, - "session": "session0" - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 3 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 3 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 3 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 3 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "countDocuments", - "object": "collection", - "arguments": { - "filter": { - "_id": 3 - }, - "session": "session0" - }, - "result": 1 - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 4 - }, - "session": "session0" - }, - "result": { - "insertedId": 4 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 5 - }, - "session": "session0" - }, - "result": { - "insertedId": 5 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 6 - }, - "session": "session0" - }, - "result": { - "insertedId": 6 - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "document": { - "_id": 7 - }, - "session": "session0" - }, - "result": { - "insertedId": 7 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - }, - { - "_id": 5 - }, - { - "_id": 6 - }, - { - "_id": 7 - } - ] - } - } - }, - { - "description": "multiple commits", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 3, - "1": 4 - } - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "remain pinned after non-transient error on commit", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 3, - "1": 4 - } - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 51 - } - } - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "TransientTransactionError" - ], - "errorCode": 51 - } - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "assertSessionPinned", - "object": "testRunner", - "arguments": { - "session": "session0" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "unpin after transient error within a transaction", - "useMultipleMongoses": true, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unpin after transient error within a transaction and commit", - "useMultipleMongoses": true, - "clientOptions": { - "heartbeatFrequencyMS": 30000 - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "targetedFailPoint", - "object": "testRunner", - "arguments": { - "session": "session0", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 7 - }, - "data": { - "failCommands": [ - "insert", - "isMaster", - "hello" - ], - "closeConnection": true - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 4 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ], - "errorLabelsOmit": [ - "UnknownTransactionCommitResult" - ], - "errorCodeName": "NoSuchTransaction" - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null, - "recoveryToken": 42 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/pin-mongos.yml b/src/test/spec/json/transactions/legacy/pin-mongos.yml deleted file mode 100644 index 067506dba..000000000 --- a/src/test/spec/json/transactions/legacy/pin-mongos.yml +++ /dev/null @@ -1,559 +0,0 @@ -# Test that all the operations go to the same mongos. -# -# In tests that don't include command-started events the assertion is implicit: -# that all the read operations succeed. If the driver does not properly pin to -# a single mongos then one of the operations in a transaction will eventually -# be sent to a different mongos, which is unaware of the transaction, and the -# mongos will return a command error. An example of such an error is: -# { -# 'ok': 0.0, -# 'errmsg': 'cannot continue txnId -1 for session 28938f50-9d29-4ca5-8de5-ddaf261267c4 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= with txnId 1', -# 'code': 251, -# 'codeName': 'NoSuchTransaction', -# 'errorLabels': ['TransientTransactionError'] -# } -runOn: - - - minServerVersion: "4.1.8" - topology: ["sharded"] - # serverless proxy doesn't append error labels to errors in transactions - # caused by failpoints (CLOUDP-88216) - serverless: "forbid" - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - -tests: - - description: countDocuments - useMultipleMongoses: true - operations: - - &startTransaction - name: startTransaction - object: session0 - - &countDocuments - name: countDocuments - object: collection - arguments: - filter: - _id: 2 - session: session0 - result: 1 - - *countDocuments - - *countDocuments - - *countDocuments - - *countDocuments - - *countDocuments - - *countDocuments - - *countDocuments - - &commitTransaction - name: commitTransaction - object: session0 - - outcome: - collection: - data: *data - - - description: distinct - useMultipleMongoses: true - operations: - - *startTransaction - - &distinct - name: distinct - object: collection - arguments: - fieldName: _id - session: session0 - result: [1, 2] - - *distinct - - *distinct - - *distinct - - *distinct - - *distinct - - *distinct - - *distinct - - *commitTransaction - - outcome: - collection: - data: *data - - - description: find - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - &find - name: find - object: collection - arguments: - filter: - _id: 2 - session: session0 - result: - - {_id: 2} - - *find - - *find - - *find - - *find - - *find - - *find - - *find - - *commitTransaction - - outcome: - collection: - data: *data - - - description: insertOne - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - document: - _id: 3 - session: session0 - result: - insertedId: 3 - - name: insertOne - object: collection - arguments: - document: - _id: 4 - session: session0 - result: - insertedId: 4 - - name: insertOne - object: collection - arguments: - document: - _id: 5 - session: session0 - result: - insertedId: 5 - - name: insertOne - object: collection - arguments: - document: - _id: 6 - session: session0 - result: - insertedId: 6 - - name: insertOne - object: collection - arguments: - document: - _id: 7 - session: session0 - result: - insertedId: 7 - - name: insertOne - object: collection - arguments: - document: - _id: 8 - session: session0 - result: - insertedId: 8 - - name: insertOne - object: collection - arguments: - document: - _id: 9 - session: session0 - result: - insertedId: 9 - - name: insertOne - object: collection - arguments: - document: - _id: 10 - session: session0 - result: - insertedId: 10 - - *commitTransaction - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - {_id: 5} - - {_id: 6} - - {_id: 7} - - {_id: 8} - - {_id: 9} - - {_id: 10} - - - description: mixed read write operations - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - document: - _id: 3 - session: session0 - result: - insertedId: 3 - - &countDocuments - name: countDocuments - object: collection - arguments: - filter: - _id: 3 - session: session0 - result: 1 - - *countDocuments - - *countDocuments - - *countDocuments - - *countDocuments - - name: insertOne - object: collection - arguments: - document: - _id: 4 - session: session0 - result: - insertedId: 4 - - name: insertOne - object: collection - arguments: - document: - _id: 5 - session: session0 - result: - insertedId: 5 - - name: insertOne - object: collection - arguments: - document: - _id: 6 - session: session0 - result: - insertedId: 6 - - name: insertOne - object: collection - arguments: - document: - _id: 7 - session: session0 - result: - insertedId: 7 - - *commitTransaction - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - {_id: 5} - - {_id: 6} - - {_id: 7} - - - description: multiple commits - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 3, 1: 4} - # Session is pinned and remains pinned after successful commits. - - &assertSessionPinned - name: assertSessionPinned - object: testRunner - arguments: - session: session0 - - *commitTransaction - - *assertSessionPinned - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *commitTransaction - - *assertSessionPinned - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - - description: remain pinned after non-transient error on commit - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertMany - object: collection - arguments: - documents: - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 3, 1: 4} - # Session is pinned. - - *assertSessionPinned - # Fail the commit with a non-transient error. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 51 # ManualInterventionRequired - - name: commitTransaction - object: session0 - result: - errorLabelsOmit: ["TransientTransactionError"] - errorCode: 51 - - *assertSessionPinned - # The next commit should succeed. - - name: commitTransaction - object: session0 - - *assertSessionPinned - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - - - description: unpin after transient error within a transaction - useMultipleMongoses: true - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - # Enable the fail point only on the Mongos that session0 is pinned to. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # abortTransaction succeeds immediately on any mongos. - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - # Applications should not run commitTransaction after transient errors but - # the transactions API allows it and this test confirms unpinning behavior. - # In a sharded cluster, a transient error within a transaction unpins the - # session. This way a subsequent abort can "succeed" immediately instead of - # blocking for serverSelectionTimeoutMS in the case the mongos went down. - # However since the abortTransaction helper ignores errors, this test uses - # commitTransaction to prove the session was unpinned. - - description: unpin after transient error within a transaction and commit - useMultipleMongoses: true - clientOptions: - # Increase heartbeatFrequencyMS to avoid the race condition where an in - # flight heartbeat refreshes the first mongoes' SDAM state in between - # the insert connection error and the single commit attempt. - heartbeatFrequencyMS: 30000 - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - # Enable the fail point only on the Mongos that session0 is pinned to. - # Fail hello/legacy hello to prevent the heartbeat requested directly after the - # insert error from racing with server selection for the commit. - # Note: times: 7 is slightly artbitrary but it accounts for one failed - # insert and some SDAM heartbeats. A test runner will have multiple - # clients connected to this server so this fail point configuration - # is also racy. - - name: targetedFailPoint - object: testRunner - arguments: - session: session0 - failPoint: - configureFailPoint: failCommand - mode: { times: 7 } - data: - failCommands: ["insert", "isMaster", "hello"] - closeConnection: true - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 4 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - # Session unpins from the first mongos after the insert error and - # commitTransaction selects the second mongos which is unaware of the - # transaction and therefore fails with NoSuchTransaction error. If this - # commit succeeds it indicates a bug, either: - # - the driver mistakenly remained pinned even after the insert error, or - # - the test client was initialized with a single mongos seed - # - # Note that the commit attempt should not select the original mongos - # because that server's SDAM state is reset by the connection error, - # heartbeatFrequencyMS is high, and subsequent heartbeats - # should fail. - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["TransientTransactionError"] - errorLabelsOmit: ["UnknownTransactionCommitResult"] - errorCodeName: NoSuchTransaction - - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - recoveryToken: 42 - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - _id: 2 diff --git a/src/test/spec/json/transactions/legacy/read-concern.json b/src/test/spec/json/transactions/legacy/read-concern.json deleted file mode 100644 index dd9243e2f..000000000 --- a/src/test/spec/json/transactions/legacy/read-concern.json +++ /dev/null @@ -1,1628 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "tests": [ - { - "description": "only first countDocuments includes readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - } - } - } - }, - { - "name": "countDocuments", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 2 - } - } - }, - "result": 3 - }, - { - "name": "countDocuments", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 2 - } - } - }, - "result": 3 - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$match": { - "_id": { - "$gte": 2 - } - } - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ], - "cursor": {}, - "lsid": "session0", - "readConcern": { - "level": "majority" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$match": { - "_id": { - "$gte": 2 - } - } - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ], - "cursor": {}, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "only first find includes readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - } - } - } - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": { - "level": "majority" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "only first aggregate includes readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - } - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "lsid": "session0", - "readConcern": { - "level": "majority" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "only first distinct includes readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - } - } - } - }, - { - "name": "distinct", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": [ - 1, - 2, - 3, - 4 - ] - }, - { - "name": "distinct", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": [ - 1, - 2, - 3, - 4 - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": { - "level": "majority" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "only first runCommand includes readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "lsid": "session0", - "readConcern": { - "level": "majority" - }, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "countDocuments ignores collection readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "countDocuments", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 2 - } - } - }, - "result": 3 - }, - { - "name": "countDocuments", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 2 - } - } - }, - "result": 3 - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$match": { - "_id": { - "$gte": 2 - } - } - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ], - "cursor": {}, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$match": { - "_id": { - "$gte": 2 - } - } - }, - { - "$group": { - "_id": 1, - "n": { - "$sum": 1 - } - } - } - ], - "cursor": {}, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "find ignores collection readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "aggregate ignores collection readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "distinct ignores collection readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "distinct", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": [ - 1, - 2, - 3, - 4 - ] - }, - { - "name": "distinct", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": [ - 1, - 2, - 3, - 4 - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "runCommand ignores database readConcern", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "runCommand", - "object": "database", - "databaseOptions": { - "readConcern": { - "level": "majority" - } - }, - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/read-concern.yml b/src/test/spec/json/transactions/legacy/read-concern.yml deleted file mode 100644 index 769da3436..000000000 --- a/src/test/spec/json/transactions/legacy/read-concern.yml +++ /dev/null @@ -1,623 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - -tests: - - description: only first countDocuments includes readConcern - - operations: - - &startTransaction - name: startTransaction - object: session0 - arguments: - options: - readConcern: - level: majority - - &countDocuments - name: countDocuments - object: collection - collectionOptions: - readConcern: - level: majority - arguments: - session: session0 - filter: {_id: {$gte: 2}} - result: 3 - - *countDocuments - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $match: {_id: {$gte: 2}} - - $group: {_id: 1, n: {$sum: 1}} - cursor: {} - lsid: session0 - readConcern: - level: majority - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $match: {_id: {$gte: 2}} - - $group: {_id: 1, n: {$sum: 1}} - cursor: {} - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - &commitTransactionEvent - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - readConcern: - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: &outcome - collection: - data: - *data - - - description: only first find includes readConcern - - operations: - - *startTransaction - - &find - name: find - object: collection - collectionOptions: - readConcern: - level: majority - arguments: - session: session0 - batchSize: 3 - result: *data - - *find - - *commitTransaction - - expectations: - - command_started_event: - command: - find: *collection_name - batchSize: 3 - lsid: session0 - readConcern: - level: majority - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - find: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - *commitTransactionEvent - - outcome: &outcome - collection: - data: - *data - - - description: only first aggregate includes readConcern - - operations: - - *startTransaction - - &aggregate - name: aggregate - object: collection - collectionOptions: - readConcern: - level: majority - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: *data - - *aggregate - - *commitTransaction - - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - lsid: session0 - readConcern: - level: majority - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: only first distinct includes readConcern - - operations: - - *startTransaction - - &distinct - name: distinct - object: collection - collectionOptions: - readConcern: - level: majority - arguments: - session: session0 - fieldName: _id - result: [1, 2, 3, 4] - - *distinct - - *commitTransaction - - expectations: - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: - level: majority - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: distinct - database_name: *database_name - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: distinct - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: only first runCommand includes readConcern - - operations: - - *startTransaction - - &runCommand - name: runCommand - object: database - command_name: find - arguments: - session: session0 - command: - find: *collection_name - - *runCommand - - *commitTransaction - - expectations: - - command_started_event: - command: - find: *collection_name - lsid: session0 - readConcern: - level: majority - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: find - database_name: *database_name - - command_started_event: - command: - find: *collection_name - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: find - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: countDocuments ignores collection readConcern - - operations: - - &startTransactionNoReadConcern - name: startTransaction - object: session0 - - *countDocuments - - *countDocuments - - *commitTransaction - - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $match: {_id: {$gte: 2}} - - $group: {_id: 1, n: {$sum: 1}} - cursor: {} - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $match: {_id: {$gte: 2}} - - $group: {_id: 1, n: {$sum: 1}} - cursor: {} - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: find ignores collection readConcern - - operations: - - *startTransactionNoReadConcern - - *find - - *find - - *commitTransaction - - expectations: - - command_started_event: - command: - find: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - find: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: aggregate ignores collection readConcern - - operations: - - *startTransactionNoReadConcern - - *aggregate - - *aggregate - - *commitTransaction - - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: distinct ignores collection readConcern - - operations: - - *startTransactionNoReadConcern - - *distinct - - *distinct - - *commitTransaction - - expectations: - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: distinct - database_name: *database_name - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: distinct - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome - - - description: runCommand ignores database readConcern - - operations: - - *startTransactionNoReadConcern - - name: runCommand - object: database - databaseOptions: - readConcern: - level: majority - command_name: find - arguments: - session: session0 - command: - find: *collection_name - - *runCommand - - *commitTransaction - - expectations: - - command_started_event: - command: - find: *collection_name - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: find - database_name: *database_name - - command_started_event: - command: - find: *collection_name - lsid: session0 - readConcern: # No readConcern - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: find - database_name: *database_name - - *commitTransactionEvent - - outcome: *outcome diff --git a/src/test/spec/json/transactions/legacy/read-pref.json b/src/test/spec/json/transactions/legacy/read-pref.json deleted file mode 100644 index bf1f1970e..000000000 --- a/src/test/spec/json/transactions/legacy/read-pref.json +++ /dev/null @@ -1,720 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "default readPreference", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2, - "2": 3, - "3": 4 - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "session": "session0", - "pipeline": [ - { - "$match": { - "_id": 1 - } - }, - { - "$count": "count" - } - ] - }, - "result": [ - { - "count": 1 - } - ] - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "primary readPreference", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Primary" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2, - "2": 3, - "3": 4 - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "session": "session0", - "pipeline": [ - { - "$match": { - "_id": 1 - } - }, - { - "$count": "count" - } - ] - }, - "result": [ - { - "count": 1 - } - ] - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Secondary" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "secondary readPreference", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Secondary" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2, - "2": 3, - "3": 4 - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "pipeline": [ - { - "$match": { - "_id": 1 - } - }, - { - "$count": "count" - } - ] - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "primaryPreferred readPreference", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "PrimaryPreferred" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2, - "2": 3, - "3": 4 - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "pipeline": [ - { - "$match": { - "_id": 1 - } - }, - { - "$count": "count" - } - ] - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "nearest readPreference", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Nearest" - } - } - } - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2, - "2": 3, - "3": 4 - } - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "pipeline": [ - { - "$match": { - "_id": 1 - } - }, - { - "$count": "count" - } - ] - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "aggregate", - "object": "collection", - "collectionOptions": { - "readPreference": { - "mode": "Primary" - } - }, - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "secondary write only", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Secondary" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/read-pref.yml b/src/test/spec/json/transactions/legacy/read-pref.yml deleted file mode 100644 index 6d94e3a19..000000000 --- a/src/test/spec/json/transactions/legacy/read-pref.yml +++ /dev/null @@ -1,348 +0,0 @@ -# This test doesn't check contents of command-started events. -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: default readPreference - - operations: - - name: startTransaction - object: session0 - - name: insertMany - object: collection - arguments: - documents: &insertedDocs - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 1, 1: 2, 2: 3, 3: 4} - - name: aggregate - object: collection - collectionOptions: - # The driver overrides the collection's read pref with the - # transaction's so count runs with Primary and succeeds. - readPreference: - mode: Secondary - arguments: - session: session0 - pipeline: - - $match: - _id: 1 - - $count: count - result: - - count: 1 - - name: find - object: collection - collectionOptions: - readPreference: - mode: Secondary - arguments: - session: session0 - batchSize: 3 - result: *insertedDocs - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Secondary - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: *insertedDocs - - name: commitTransaction - object: session0 - - outcome: - collection: - data: *insertedDocs - - - description: primary readPreference - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Primary - - name: insertMany - object: collection - arguments: - documents: &insertedDocs - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 1, 1: 2, 2: 3, 3: 4} - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Secondary - arguments: - session: session0 - pipeline: - - $match: - _id: 1 - - $count: count - result: - - count: 1 - - name: find - object: collection - collectionOptions: - readPreference: - mode: Secondary - arguments: - session: session0 - batchSize: 3 - result: *insertedDocs - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Secondary - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: *insertedDocs - - name: commitTransaction - object: session0 - - outcome: - collection: - data: *insertedDocs - - - description: secondary readPreference - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Secondary - - name: insertMany - object: collection - arguments: - documents: &insertedDocs - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 1, 1: 2, 2: 3, 3: 4} - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - pipeline: - - $match: - _id: 1 - - $count: count - result: - errorContains: read preference in a transaction must be primary - - name: find - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - batchSize: 3 - result: - errorContains: read preference in a transaction must be primary - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: - errorContains: read preference in a transaction must be primary - - name: abortTransaction - object: session0 - - outcome: - collection: - data: [] - - - description: primaryPreferred readPreference - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: PrimaryPreferred - - name: insertMany - object: collection - arguments: - documents: &insertedDocs - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 1, 1: 2, 2: 3, 3: 4} - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - pipeline: - - $match: - _id: 1 - - $count: count - result: - errorContains: read preference in a transaction must be primary - - name: find - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - batchSize: 3 - result: - errorContains: read preference in a transaction must be primary - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: - errorContains: read preference in a transaction must be primary - - name: abortTransaction - object: session0 - - outcome: - collection: - data: [] - - - description: nearest readPreference - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Nearest - - name: insertMany - object: collection - arguments: - documents: &insertedDocs - - _id: 1 - - _id: 2 - - _id: 3 - - _id: 4 - session: session0 - result: - insertedIds: {0: 1, 1: 2, 2: 3, 3: 4} - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - pipeline: - - $match: - _id: 1 - - $count: count - result: - errorContains: read preference in a transaction must be primary - - name: find - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - session: session0 - batchSize: 3 - result: - errorContains: read preference in a transaction must be primary - - name: aggregate - object: collection - collectionOptions: - readPreference: - mode: Primary - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: - errorContains: read preference in a transaction must be primary - - name: abortTransaction - object: session0 - - outcome: - collection: - data: [] - - - description: secondary write only - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Secondary - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/reads.json b/src/test/spec/json/transactions/legacy/reads.json deleted file mode 100644 index 9fc587f48..000000000 --- a/src/test/spec/json/transactions/legacy/reads.json +++ /dev/null @@ -1,543 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ], - "tests": [ - { - "description": "collection readConcern without transaction", - "operations": [ - { - "name": "find", - "object": "collection", - "collectionOptions": { - "readConcern": { - "level": "majority" - } - }, - "arguments": { - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "readConcern": { - "level": "majority" - }, - "lsid": "session0", - "txnNumber": null, - "startTransaction": null, - "autocommit": null - }, - "command_name": "find", - "database_name": "transaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "find", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "batchSize": 3 - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "find": "test", - "batchSize": 3, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "find", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "aggregate", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "aggregate", - "object": "collection", - "arguments": { - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "batchSize": 3, - "session": "session0" - }, - "result": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "aggregate": "test", - "pipeline": [ - { - "$project": { - "_id": 1 - } - } - ], - "cursor": { - "batchSize": 3 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "aggregate", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "getMore": { - "$numberLong": "42" - }, - "collection": "test", - "batchSize": 3, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false - }, - "command_name": "getMore", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - }, - { - "description": "distinct", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "distinct", - "object": "collection", - "arguments": { - "session": "session0", - "fieldName": "_id" - }, - "result": [ - 1, - 2, - 3, - 4 - ] - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "distinct": "test", - "key": "_id", - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "distinct", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "readConcern": null, - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - }, - { - "_id": 4 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/reads.yml b/src/test/spec/json/transactions/legacy/reads.yml deleted file mode 100644 index cee4184a7..000000000 --- a/src/test/spec/json/transactions/legacy/reads.yml +++ /dev/null @@ -1,261 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - {_id: 1} - - {_id: 2} - - {_id: 3} - - {_id: 4} - -tests: - - description: collection readConcern without transaction - - operations: - - name: find - object: collection - collectionOptions: - readConcern: - level: majority - arguments: - session: session0 - result: *data - - expectations: - - command_started_event: - command: - find: *collection_name - readConcern: - level: majority - lsid: session0 - txnNumber: - startTransaction: - autocommit: - command_name: find - database_name: *database_name - - outcome: &outcome - collection: - data: - *data - - - description: find - - operations: - - &startTransaction - name: startTransaction - object: session0 - - &find - name: find - object: collection - arguments: - session: session0 - batchSize: 3 - result: *data - - *find - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - find: *collection_name - batchSize: 3 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - find: *collection_name - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: find - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: *outcome - - - description: aggregate - - operations: - - *startTransaction - - &aggregate - name: aggregate - object: collection - arguments: - pipeline: - - $project: - _id: 1 - batchSize: 3 - session: session0 - result: *data - - *aggregate - - *commitTransaction - - expectations: - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - # 42 is a fake placeholder value for the cursorId. - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - aggregate: *collection_name - pipeline: - - $project: - _id: 1 - cursor: - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: aggregate - database_name: *database_name - - command_started_event: - command: - getMore: - $numberLong: '42' - collection: *collection_name - batchSize: 3 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - command_name: getMore - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: *outcome - - - description: distinct - - operations: - - *startTransaction - - name: distinct - object: collection - arguments: - session: session0 - fieldName: _id - result: [1, 2, 3, 4] - - *commitTransaction - - expectations: - - command_started_event: - command: - distinct: *collection_name - key: _id - lsid: session0 - readConcern: - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: distinct - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - readConcern: - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: *outcome diff --git a/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.json b/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.json deleted file mode 100644 index 1110ce2c3..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "abortTransaction only retries once with RetryableWriteError from server", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction does not retry without RetryableWriteError label", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.yml b/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.yml deleted file mode 100644 index edd0d18c0..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-abort-errorLabels.yml +++ /dev/null @@ -1,124 +0,0 @@ -runOn: - - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] -tests: - - description: abortTransaction only retries once with RetryableWriteError from server - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["abortTransaction"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: # Driver retries abort once - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - outcome: - collection: - data: [] - - - description: abortTransaction does not retry without RetryableWriteError label - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - outcome: # Driver does not retry abort - collection: - data: [] diff --git a/src/test/spec/json/transactions/legacy/retryable-abort.json b/src/test/spec/json/transactions/legacy/retryable-abort.json deleted file mode 100644 index 13cc7c88f..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-abort.json +++ /dev/null @@ -1,2017 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "abortTransaction only performs a single retry", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction does not retry after Interrupted", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 11601, - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction does not retry after WriteConcernError Interrupted", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "writeConcernError": { - "code": 11601, - "errmsg": "operation was interrupted" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after connection error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 10107, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 13436, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 13435, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 11602, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 11600, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 91, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 7, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 6, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 9001, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorCode": 89, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after WriteConcernError InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11600, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11602, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after WriteConcernError PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 189, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "abortTransaction succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "abortTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/retryable-abort.yml b/src/test/spec/json/transactions/legacy/retryable-abort.yml deleted file mode 100644 index b129189d7..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-abort.yml +++ /dev/null @@ -1,1315 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: abortTransaction only performs a single retry - - clientOptions: - retryWrites: false - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["abortTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # Call to abort returns no error even when the retry attempt fails. - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction does not retry after Interrupted - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 11601 - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction does not retry after WriteConcernError Interrupted - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - writeConcernError: - code: 11601 - errmsg: operation was interrupted - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after connection error - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after NotWritablePrimary - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 10107 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after NotPrimaryOrSecondary - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 13436 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after NotPrimaryNoSecondaryOk - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 13435 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after InterruptedDueToReplStateChange - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 11602 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after InterruptedAtShutdown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 11600 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after PrimarySteppedDown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after ShutdownInProgress - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 91 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after HostNotFound - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 7 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after HostUnreachable - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 6 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after SocketException - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 9001 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after NetworkTimeout - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorCode: 89 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after WriteConcernError InterruptedAtShutdown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11600 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11602 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after WriteConcernError PrimarySteppedDown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 189 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: abortTransaction succeeds after WriteConcernError ShutdownInProgress - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["abortTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] diff --git a/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.json b/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.json deleted file mode 100644 index e0818f237..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.3.1", - "topology": [ - "replicaset", - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commitTransaction does not retry error without RetryableWriteError label", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 11600, - "errorLabels": [] - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "commitTransaction retries once with RetryableWriteError from server", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 112, - "errorLabels": [ - "RetryableWriteError" - ] - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.yml b/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.yml deleted file mode 100644 index d0425d594..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-commit-errorLabels.yml +++ /dev/null @@ -1,132 +0,0 @@ -runOn: - - - minServerVersion: "4.3.1" - topology: ["replicaset", "sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: commitTransaction does not retry error without RetryableWriteError label - clientOptions: - retryWrites: false - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code - errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError"] - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - outcome: # Driver does not retry commit because there was no RetryableWriteError label on response - collection: - data: [] - - - description: commitTransaction retries once with RetryableWriteError from server - clientOptions: - retryWrites: false - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 112 # WriteConflict, not a retryable error code - errorLabels: ["RetryableWriteError"] # Override server behavior: send RetryableWriteError label with non-retryable error code - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - outcome: # Driver retries commit and it succeeds - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/retryable-commit.json b/src/test/spec/json/transactions/legacy/retryable-commit.json deleted file mode 100644 index 49148c62d..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-commit.json +++ /dev/null @@ -1,2336 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "commitTransaction fails after two errors", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction applies majority write concern on retries", - "clientOptions": { - "retryWrites": false - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 2 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": 2, - "j": true, - "wtimeout": 5000 - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsContain": [ - "RetryableWriteError", - "UnknownTransactionCommitResult" - ], - "errorLabelsOmit": [ - "TransientTransactionError" - ] - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": 2, - "j": true, - "wtimeout": 5000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "j": true, - "wtimeout": 5000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "j": true, - "wtimeout": 5000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction fails after Interrupted", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 11601, - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorCodeName": "Interrupted", - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "commitTransaction is not retried after UnsatisfiableWriteConcern error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "writeConcernError": { - "code": 100, - "errmsg": "Not enough data-bearing nodes" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0", - "result": { - "errorLabelsOmit": [ - "RetryableWriteError", - "TransientTransactionError", - "UnknownTransactionCommitResult" - ] - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after connection error", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after NotWritablePrimary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 10107, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after NotPrimaryOrSecondary", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 13436, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after NotPrimaryNoSecondaryOk", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 13435, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 11602, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 11600, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 189, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 91, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after HostNotFound", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 7, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after HostUnreachable", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 6, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after SocketException", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 9001, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after NetworkTimeout", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorCode": 89, - "errorLabels": [ - "RetryableWriteError" - ], - "closeConnection": false - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after WriteConcernError InterruptedAtShutdown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11600, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 11602, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after WriteConcernError PrimarySteppedDown", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 189, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commitTransaction succeeds after WriteConcernError ShutdownInProgress", - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "commitTransaction" - ], - "errorLabels": [ - "RetryableWriteError" - ], - "writeConcernError": { - "code": 91, - "errmsg": "Replication is being shut down" - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority", - "wtimeout": 10000 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/retryable-commit.yml b/src/test/spec/json/transactions/legacy/retryable-commit.yml deleted file mode 100644 index f38b7343a..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-commit.yml +++ /dev/null @@ -1,1460 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: commitTransaction fails after two errors - - clientOptions: - retryWrites: false - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # First call to commit fails after a single retry attempt. - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - # Second call to commit succeeds because the failpoint was disabled. - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction applies majority write concern on retries - - clientOptions: - retryWrites: false - - failPoint: - configureFailPoint: failCommand - mode: { times: 2 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: { w: 2, j: true, wtimeout: 5000 } - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - # First call to commit fails after a single retry attempt. - - name: commitTransaction - object: session0 - result: - errorLabelsContain: ["RetryableWriteError", "UnknownTransactionCommitResult"] - errorLabelsOmit: ["TransientTransactionError"] - # Second call to commit succeeds because the failpoint was disabled. - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: 2, j: true, wtimeout: 5000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, j: true, wtimeout: 5000 } - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: { w: majority, j: true, wtimeout: 5000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction fails after Interrupted - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 11601 - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorCodeName: Interrupted - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError", "UnknownTransactionCommitResult"] - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: [] - - - description: commitTransaction is not retried after UnsatisfiableWriteConcern error - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - writeConcernError: - code: 100 - errmsg: Not enough data-bearing nodes - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - result: - errorLabelsOmit: ["RetryableWriteError", "TransientTransactionError", "UnknownTransactionCommitResult"] - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after connection error - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after NotWritablePrimary - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 10107 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after NotPrimaryOrSecondary - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 13436 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after NotPrimaryNoSecondaryOk - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 13435 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after InterruptedDueToReplStateChange - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 11602 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after InterruptedAtShutdown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 11600 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after PrimarySteppedDown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 189 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after ShutdownInProgress - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 91 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after HostNotFound - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 7 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after HostUnreachable - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 6 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after SocketException - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 9001 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after NetworkTimeout - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorCode: 89 - errorLabels: ["RetryableWriteError"] - closeConnection: false - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after WriteConcernError InterruptedAtShutdown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11600 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 11602 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after WriteConcernError PrimarySteppedDown - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 189 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: commitTransaction succeeds after WriteConcernError ShutdownInProgress - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["commitTransaction"] - errorLabels: ["RetryableWriteError"] - writeConcernError: - code: 91 - errmsg: Replication is being shut down - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - # commitTransaction applies w:majority on retries - writeConcern: { w: majority, wtimeout: 10000 } - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/retryable-writes.json b/src/test/spec/json/transactions/legacy/retryable-writes.json deleted file mode 100644 index c932893b5..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-writes.json +++ /dev/null @@ -1,343 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "increment txnNumber", - "clientOptions": { - "retryWrites": true - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 3 - } - }, - "result": { - "insertedId": 3 - } - }, - { - "name": "abortTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "arguments": { - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ], - "session": "session0" - }, - "result": { - "insertedIds": { - "0": 4, - "1": 5 - } - } - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 3 - } - ], - "ordered": true, - "readConcern": { - "afterClusterTime": 42 - }, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "3" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 4 - }, - { - "_id": 5 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "4" - }, - "startTransaction": null, - "autocommit": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 4 - }, - { - "_id": 5 - } - ] - } - } - }, - { - "description": "writes are not retried", - "clientOptions": { - "retryWrites": true - }, - "failPoint": { - "configureFailPoint": "failCommand", - "mode": { - "times": 1 - }, - "data": { - "failCommands": [ - "insert" - ], - "closeConnection": true - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "errorLabelsContain": [ - "TransientTransactionError" - ] - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/retryable-writes.yml b/src/test/spec/json/transactions/legacy/retryable-writes.yml deleted file mode 100644 index 37eaaa962..000000000 --- a/src/test/spec/json/transactions/legacy/retryable-writes.yml +++ /dev/null @@ -1,216 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: increment txnNumber - - clientOptions: - retryWrites: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - # Retryable write should include the next txnNumber - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - # Next transaction should include the next txnNumber - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 3 - result: - insertedId: 3 - - name: abortTransaction - object: session0 - # Retryable write should include the next txnNumber - - name: insertMany - object: collection - arguments: - documents: - - _id: 4 - - _id: 5 - session: session0 - result: - insertedIds: {0: 4, 1: 5} - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 3 - ordered: true - readConcern: - afterClusterTime: 42 - lsid: session0 - txnNumber: - $numberLong: "3" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "3" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 4 - - _id: 5 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "4" - startTransaction: - autocommit: - writeConcern: - command_name: insert - database_name: *database_name - - outcome: - collection: - data: - - _id: 1 - - _id: 2 - - _id: 4 - - _id: 5 - - - description: writes are not retried - - clientOptions: - retryWrites: true - - failPoint: - configureFailPoint: failCommand - mode: { times: 1 } - data: - failCommands: ["insert"] - closeConnection: true - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - errorLabelsContain: ["TransientTransactionError"] - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: [] diff --git a/src/test/spec/json/transactions/legacy/run-command.json b/src/test/spec/json/transactions/legacy/run-command.json deleted file mode 100644 index 2f2a3a881..000000000 --- a/src/test/spec/json/transactions/legacy/run-command.json +++ /dev/null @@ -1,306 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "run command with default read preference", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "result": { - "n": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "run command with secondary read preference in client option and primary read preference in transaction options", - "clientOptions": { - "readPreference": "secondary" - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Primary" - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - } - }, - "result": { - "n": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "run command with explicit primary read preference", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "runCommand", - "object": "database", - "command_name": "insert", - "arguments": { - "session": "session0", - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ] - }, - "readPreference": { - "mode": "Primary" - } - }, - "result": { - "n": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - }, - { - "description": "run command fails with explicit secondary read preference", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "runCommand", - "object": "database", - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - }, - "readPreference": { - "mode": "Secondary" - } - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - } - ] - }, - { - "description": "run command fails with secondary read preference from transaction options", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Secondary" - } - } - } - }, - { - "name": "runCommand", - "object": "database", - "command_name": "find", - "arguments": { - "session": "session0", - "command": { - "find": "test" - } - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/run-command.yml b/src/test/spec/json/transactions/legacy/run-command.yml deleted file mode 100644 index a62562e97..000000000 --- a/src/test/spec/json/transactions/legacy/run-command.yml +++ /dev/null @@ -1,197 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: run command with default read preference - - operations: - - name: startTransaction - object: session0 - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - result: - n: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id : 1 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - - description: run command with secondary read preference in client option and primary read preference in transaction options - - clientOptions: - readPreference: secondary - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Primary - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - result: - n: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id : 1 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - - description: run command with explicit primary read preference - - operations: - - name: startTransaction - object: session0 - - name: runCommand - object: database - command_name: insert - arguments: - session: session0 - command: - insert: *collection_name - documents: - - _id : 1 - readPreference: - mode: Primary - result: - n: 1 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id : 1 - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - - description: run command fails with explicit secondary read preference - - operations: - - name: startTransaction - object: session0 - - name: runCommand - object: database - command_name: find - arguments: - session: session0 - command: - find: *collection_name - readPreference: - mode: Secondary - result: - errorContains: read preference in a transaction must be primary - - - description: run command fails with secondary read preference from transaction options - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Secondary - - name: runCommand - object: database - command_name: find - arguments: - session: session0 - command: - find: *collection_name - result: - errorContains: read preference in a transaction must be primary - diff --git a/src/test/spec/json/transactions/legacy/transaction-options-repl.json b/src/test/spec/json/transactions/legacy/transaction-options-repl.json deleted file mode 100644 index 33324debb..000000000 --- a/src/test/spec/json/transactions/legacy/transaction-options-repl.json +++ /dev/null @@ -1,181 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "readConcern snapshot in startTransaction options", - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "majority" - } - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "snapshot" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "snapshot" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "snapshot" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "snapshot", - "afterClusterTime": 42 - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/transaction-options-repl.yml b/src/test/spec/json/transactions/legacy/transaction-options-repl.yml deleted file mode 100644 index c1191c32b..000000000 --- a/src/test/spec/json/transactions/legacy/transaction-options-repl.yml +++ /dev/null @@ -1,117 +0,0 @@ -runOn: - - minServerVersion: "4.0" - topology: ["replicaset"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - - description: readConcern snapshot in startTransaction options - - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: - level: majority # Overridden. - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readConcern: - level: snapshot - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - # Now test abort. - - name: startTransaction - object: session0 - arguments: - options: - readConcern: - level: snapshot - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: snapshot - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: snapshot - afterClusterTime: 42 - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/transaction-options.json b/src/test/spec/json/transactions/legacy/transaction-options.json deleted file mode 100644 index 25d245dca..000000000 --- a/src/test/spec/json/transactions/legacy/transaction-options.json +++ /dev/null @@ -1,1404 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [], - "tests": [ - { - "description": "no transaction options set", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "afterClusterTime": 42 - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "transaction options inherited from client", - "clientOptions": { - "w": 1, - "readConcernLevel": "local" - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "local" - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - }, - "maxTimeMS": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "local", - "afterClusterTime": 42 - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - }, - "maxTimeMS": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "transaction options inherited from defaultTransactionOptions", - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": 1 - }, - "maxCommitTimeMS": 60000 - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority", - "afterClusterTime": 42 - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "startTransaction options override defaults", - "clientOptions": { - "readConcernLevel": "local", - "w": 1 - }, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "snapshot" - }, - "writeConcern": { - "w": 1 - }, - "maxCommitTimeMS": 30000 - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": "majority" - }, - "maxCommitTimeMS": 60000 - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority", - "afterClusterTime": 42 - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "defaultTransactionOptions override client options", - "clientOptions": { - "readConcernLevel": "local", - "w": 1 - }, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "majority" - }, - "writeConcern": { - "w": "majority" - }, - "maxCommitTimeMS": 60000 - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority" - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": 60000 - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "majority", - "afterClusterTime": 42 - }, - "writeConcern": null, - "maxTimeMS": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": "majority" - }, - "maxTimeMS": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "readConcern local in defaultTransactionOptions", - "clientOptions": { - "w": 1 - }, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readConcern": { - "level": "local" - } - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - }, - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 2 - } - }, - "result": { - "insertedId": 2 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "local" - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - }, - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": { - "level": "local", - "afterClusterTime": 42 - }, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "2" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": { - "w": 1 - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "client writeConcern ignored for bulk", - "clientOptions": { - "w": "majority" - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": 1 - } - } - } - }, - { - "name": "bulkWrite", - "object": "collection", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - } - ], - "session": "session0" - }, - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "0": 1 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": 1 - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "readPreference inherited from client", - "clientOptions": { - "readPreference": "secondary" - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "readPreference inherited from defaultTransactionOptions", - "clientOptions": { - "readPreference": "primary" - }, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readPreference": { - "mode": "Secondary" - } - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - }, - { - "description": "startTransaction overrides readPreference", - "clientOptions": { - "readPreference": "primary" - }, - "sessionOptions": { - "session0": { - "defaultTransactionOptions": { - "readPreference": { - "mode": "Primary" - } - } - } - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "readPreference": { - "mode": "Secondary" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "find", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 1 - } - }, - "result": { - "errorContains": "read preference in a transaction must be primary" - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/transaction-options.yml b/src/test/spec/json/transactions/legacy/transaction-options.yml deleted file mode 100644 index 461e87d55..000000000 --- a/src/test/spec/json/transactions/legacy/transaction-options.yml +++ /dev/null @@ -1,803 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: [] - -tests: - - description: no transaction options set - - operations: &commitAbortOperations - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - # Now test abort. - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - maxTimeMS: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - afterClusterTime: 42 - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - maxTimeMS: - command_name: abortTransaction - database_name: admin - - outcome: &outcome - collection: - data: - - _id: 1 - - - description: transaction options inherited from client - - clientOptions: - w: 1 - readConcernLevel: local - - operations: *commitAbortOperations - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: local - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - maxTimeMS: - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: local - afterClusterTime: 42 - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - maxTimeMS: - command_name: abortTransaction - database_name: admin - - outcome: *outcome - - - description: transaction options inherited from defaultTransactionOptions - - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: - level: majority - writeConcern: - w: 1 - maxCommitTimeMS: 60000 - - operations: *commitAbortOperations - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: majority - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: majority - afterClusterTime: 42 - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - command_name: abortTransaction - database_name: admin - - outcome: *outcome - - - description: startTransaction options override defaults - - clientOptions: - readConcernLevel: local - w: 1 - - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: - level: snapshot - writeConcern: - w: 1 - maxCommitTimeMS: 30000 - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readConcern: - level: majority - writeConcern: - w: majority - maxCommitTimeMS: 60000 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: commitTransaction - object: session0 - - name: startTransaction - object: session0 - arguments: - options: - readConcern: - level: majority - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 2 - result: - insertedId: 2 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: majority - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: majority - afterClusterTime: 42 - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - maxTimeMS: - command_name: abortTransaction - database_name: admin - - outcome: *outcome - - - description: defaultTransactionOptions override client options - - clientOptions: - readConcernLevel: local - w: 1 - - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: - level: majority - writeConcern: - w: majority - maxCommitTimeMS: 60000 - - operations: *commitAbortOperations - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: majority - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - maxTimeMS: 60000 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: majority - afterClusterTime: 42 - writeConcern: - maxTimeMS: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: majority - maxTimeMS: - command_name: abortTransaction - database_name: admin - - outcome: *outcome - - - description: readConcern local in defaultTransactionOptions - - clientOptions: - w: 1 - - sessionOptions: - session0: - defaultTransactionOptions: - readConcern: - level: local - - operations: *commitAbortOperations - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - level: local - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - command_name: commitTransaction - database_name: admin - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 2 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: true - autocommit: false - readConcern: - level: local - afterClusterTime: 42 - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "2" - startTransaction: - autocommit: false - readConcern: - writeConcern: - w: 1 - command_name: abortTransaction - database_name: admin - - outcome: *outcome - - - description: client writeConcern ignored for bulk - - clientOptions: - w: majority - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: 1 - - name: bulkWrite - object: collection - arguments: - requests: - - name: insertOne - arguments: - document: {_id: 1} - session: session0 - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: {0: 1} - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - # No writeConcern. - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: 1 - command_name: commitTransaction - database_name: admin - - outcome: *outcome - - - description: readPreference inherited from client - - clientOptions: - readPreference: secondary - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: find - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - errorContains: read preference in a transaction must be primary - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: readPreference inherited from defaultTransactionOptions - - clientOptions: - readPreference: primary - - sessionOptions: - session0: - defaultTransactionOptions: - readPreference: - mode: Secondary - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: find - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - errorContains: read preference in a transaction must be primary - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 - - - description: startTransaction overrides readPreference - - clientOptions: - readPreference: primary - - sessionOptions: - session0: - defaultTransactionOptions: - readPreference: - mode: Primary - - operations: - - name: startTransaction - object: session0 - arguments: - options: - readPreference: - mode: Secondary - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: find - object: collection - arguments: - session: session0 - filter: - _id: 1 - result: - errorContains: read preference in a transaction must be primary - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - readConcern: - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 1 diff --git a/src/test/spec/json/transactions/legacy/update.json b/src/test/spec/json/transactions/legacy/update.json deleted file mode 100644 index e33bf5b81..000000000 --- a/src/test/spec/json/transactions/legacy/update.json +++ /dev/null @@ -1,442 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3 - } - ], - "tests": [ - { - "description": "update", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - }, - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - } - }, - { - "name": "replaceOne", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "x": 1 - }, - "replacement": { - "y": 1 - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "updateMany", - "object": "collection", - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 3 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 4 - }, - "u": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "x": 1 - }, - "u": { - "y": 1 - } - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": { - "$gte": 3 - } - }, - "u": { - "$set": { - "z": 1 - } - }, - "multi": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1 - }, - { - "_id": 2 - }, - { - "_id": 3, - "z": 1 - }, - { - "_id": 4, - "y": 1, - "z": 1 - } - ] - } - } - }, - { - "description": "collections writeConcern ignored for update", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "updateOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - }, - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - } - }, - { - "name": "replaceOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "x": 1 - }, - "replacement": { - "y": 1 - } - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "updateMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": "majority" - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": { - "$gte": 3 - } - }, - "update": { - "$set": { - "z": 1 - } - } - }, - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 4 - }, - "u": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - ], - "ordered": true, - "readConcern": null, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "x": 1 - }, - "u": { - "y": 1 - } - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": { - "$gte": 3 - } - }, - "u": { - "$set": { - "z": 1 - } - }, - "multi": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ] - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/update.yml b/src/test/spec/json/transactions/legacy/update.yml deleted file mode 100644 index e4f07a6b9..000000000 --- a/src/test/spec/json/transactions/legacy/update.yml +++ /dev/null @@ -1,246 +0,0 @@ -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: - - _id: 1 - - _id: 2 - - _id: 3 - -tests: - - description: update - - operations: - - name: startTransaction - object: session0 - - name: updateOne - object: collection - arguments: - session: session0 - filter: {_id: 4} - update: - $inc: {x: 1} - upsert: true - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - - name: replaceOne - object: collection - arguments: - session: session0 - filter: {x: 1} - replacement: {y: 1} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - - name: updateMany - object: collection - arguments: - session: session0 - filter: - _id: {$gte: 3} - update: - $set: {z: 1} - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 4} - u: {$inc: {x: 1}} - upsert: true - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {x: 1} - u: {y: 1} - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: {$gte: 3}} - u: {$set: {z: 1}} - multi: true - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - {_id: 1} - - {_id: 2} - - {_id: 3, z: 1} - - {_id: 4, y: 1, z: 1} - - - description: collections writeConcern ignored for update - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: updateOne - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: {_id: 4} - update: - $inc: {x: 1} - upsert: true - result: - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 1 - upsertedId: 4 - - name: replaceOne - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: {x: 1} - replacement: {y: 1} - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - - name: updateMany - object: collection - collectionOptions: - writeConcern: - w: majority - arguments: - session: session0 - filter: - _id: {$gte: 3} - update: - $set: {z: 1} - result: - matchedCount: 2 - modifiedCount: 2 - upsertedCount: 0 - - name: commitTransaction - object: session0 - - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 4} - u: {$inc: {x: 1}} - upsert: true - ordered: true - readConcern: - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {x: 1} - u: {y: 1} - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: {$gte: 3}} - u: {$set: {z: 1}} - multi: true - ordered: true - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: update - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin diff --git a/src/test/spec/json/transactions/legacy/write-concern.json b/src/test/spec/json/transactions/legacy/write-concern.json deleted file mode 100644 index 84b1ea365..000000000 --- a/src/test/spec/json/transactions/legacy/write-concern.json +++ /dev/null @@ -1,1278 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.0", - "topology": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.8", - "topology": [ - "sharded" - ] - } - ], - "database_name": "transaction-tests", - "collection_name": "test", - "data": [ - { - "_id": 0 - } - ], - "tests": [ - { - "description": "commit with majority", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - }, - { - "_id": 1 - } - ] - } - } - }, - { - "description": "commit with default", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - }, - { - "_id": 1 - } - ] - } - } - }, - { - "description": "abort with majority", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": "majority" - } - } - } - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": { - "w": "majority" - } - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - } - ] - } - } - }, - { - "description": "abort with default", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "abortTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "abortTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "abortTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - } - ] - } - } - }, - { - "description": "start with unacknowledged write concern", - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "arguments": { - "options": { - "writeConcern": { - "w": 0 - } - } - }, - "result": { - "errorContains": "transactions do not support unacknowledged write concern" - } - } - ] - }, - { - "description": "start with implicit unacknowledged write concern", - "clientOptions": { - "w": 0 - }, - "operations": [ - { - "name": "startTransaction", - "object": "session0", - "result": { - "errorContains": "transactions do not support unacknowledged write concern" - } - } - ] - }, - { - "description": "unacknowledged write concern coll insertOne", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "document": { - "_id": 1 - } - }, - "result": { - "insertedId": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - }, - { - "_id": 1 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll insertMany", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "insertMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ] - }, - "result": { - "insertedIds": { - "0": 1, - "1": 2 - } - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - }, - { - "_id": 2 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - }, - { - "_id": 1 - }, - { - "_id": 2 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll bulkWrite", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "bulkWrite", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1 - } - } - } - ] - }, - "result": { - "deletedCount": 0, - "insertedCount": 1, - "insertedIds": { - "0": 1 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "test", - "documents": [ - { - "_id": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "insert", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0 - }, - { - "_id": 1 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll deleteOne", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "deleteOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 0 - }, - "limit": 1 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "unacknowledged write concern coll deleteMany", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "deleteMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - } - }, - "result": { - "deletedCount": 1 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "delete": "test", - "deletes": [ - { - "q": { - "_id": 0 - }, - "limit": 0 - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "delete", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "unacknowledged write concern coll updateOne", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateOne", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 0 - }, - "u": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "x": 1 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll updateMany", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "updateMany", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - }, - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "update": "test", - "updates": [ - { - "q": { - "_id": 0 - }, - "u": { - "$inc": { - "x": 1 - } - }, - "multi": true, - "upsert": true - } - ], - "ordered": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "update", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "x": 1 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll findOneAndDelete", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndDelete", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - } - }, - "result": { - "_id": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 0 - }, - "remove": true, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [] - } - } - }, - { - "description": "unacknowledged write concern coll findOneAndReplace", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndReplace", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - }, - "replacement": { - "x": 1 - }, - "returnDocument": "Before" - }, - "result": { - "_id": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 0 - }, - "update": { - "x": 1 - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "x": 1 - } - ] - } - } - }, - { - "description": "unacknowledged write concern coll findOneAndUpdate", - "operations": [ - { - "name": "startTransaction", - "object": "session0" - }, - { - "name": "findOneAndUpdate", - "object": "collection", - "collectionOptions": { - "writeConcern": { - "w": 0 - } - }, - "arguments": { - "session": "session0", - "filter": { - "_id": 0 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "returnDocument": "Before" - }, - "result": { - "_id": 0 - } - }, - { - "name": "commitTransaction", - "object": "session0" - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "findAndModify": "test", - "query": { - "_id": 0 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "new": false, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": true, - "autocommit": false, - "readConcern": null, - "writeConcern": null - }, - "command_name": "findAndModify", - "database_name": "transaction-tests" - } - }, - { - "command_started_event": { - "command": { - "commitTransaction": 1, - "lsid": "session0", - "txnNumber": { - "$numberLong": "1" - }, - "startTransaction": null, - "autocommit": false, - "writeConcern": null - }, - "command_name": "commitTransaction", - "database_name": "admin" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 0, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/src/test/spec/json/transactions/legacy/write-concern.yml b/src/test/spec/json/transactions/legacy/write-concern.yml deleted file mode 100644 index 16687e501..000000000 --- a/src/test/spec/json/transactions/legacy/write-concern.yml +++ /dev/null @@ -1,554 +0,0 @@ -# Assumes the default for transactions is the same as for all ops, tests -# setting the writeConcern to "majority". -runOn: - - - minServerVersion: "4.0" - topology: ["replicaset"] - - - minServerVersion: "4.1.8" - topology: ["sharded"] - -database_name: &database_name "transaction-tests" -collection_name: &collection_name "test" - -data: &data - - _id: 0 - -tests: - - description: commit with majority - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - &commitTransaction - name: commitTransaction - object: session0 - - expectations: - - &insertOneEvent - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - <<: &transactionCommandArgs - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: true - autocommit: false - readConcern: - writeConcern: - command_name: insert - database_name: *database_name - - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 0 - - _id: 1 - - - description: commit with default - - operations: - - &startTransaction - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - *commitTransaction - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - <<: *transactionCommandArgs - command_name: insert - database_name: *database_name - - &commitWithDefaultWCEvent - command_started_event: - command: - commitTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: commitTransaction - database_name: admin - - outcome: - collection: - data: - - _id: 0 - - _id: 1 - - - description: abort with majority - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: majority - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - <<: *transactionCommandArgs - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - w: majority - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: *data - - - description: abort with default - - operations: - - name: startTransaction - object: session0 - - name: insertOne - object: collection - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - name: abortTransaction - object: session0 - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - ordered: true - <<: *transactionCommandArgs - command_name: insert - database_name: *database_name - - command_started_event: - command: - abortTransaction: 1 - lsid: session0 - txnNumber: - $numberLong: "1" - startTransaction: - autocommit: false - writeConcern: - command_name: abortTransaction - database_name: admin - - outcome: - collection: - data: *data - - - description: start with unacknowledged write concern - - operations: - - name: startTransaction - object: session0 - arguments: - options: - writeConcern: - w: 0 - result: - # Client-side error. - errorContains: transactions do not support unacknowledged write concern - - - description: start with implicit unacknowledged write concern - - clientOptions: - w: 0 - - operations: - - name: startTransaction - object: session0 - result: - # Client-side error. - errorContains: transactions do not support unacknowledged write concern - - - description: unacknowledged write concern coll insertOne - - operations: - - *startTransaction - - name: insertOne - <<: &collection_w0 - object: collection - collectionOptions: - writeConcern: { w: 0 } - arguments: - session: session0 - document: - _id: 1 - result: - insertedId: 1 - - *commitTransaction - - expectations: - - *insertOneEvent - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - _id: 0 - - _id: 1 - - - description: unacknowledged write concern coll insertMany - - operations: - - *startTransaction - - name: insertMany - <<: *collection_w0 - arguments: - session: session0 - documents: - - _id: 1 - - _id: 2 - result: - insertedIds: {0: 1, 1: 2} - - *commitTransaction - - expectations: - - command_started_event: - command: - insert: *collection_name - documents: - - _id: 1 - - _id: 2 - ordered: true - <<: *transactionCommandArgs - command_name: insert - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - _id: 0 - - _id: 1 - - _id: 2 - - - description: unacknowledged write concern coll bulkWrite - - operations: - - *startTransaction - - name: bulkWrite - <<: *collection_w0 - arguments: - session: session0 - requests: - - name: insertOne - arguments: - document: {_id: 1} - result: - deletedCount: 0 - insertedCount: 1 - insertedIds: {0: 1} - matchedCount: 0 - modifiedCount: 0 - upsertedCount: 0 - upsertedIds: {} - - *commitTransaction - - expectations: - - *insertOneEvent - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - _id: 0 - - _id: 1 - - - - description: unacknowledged write concern coll deleteOne - - operations: - - *startTransaction - - name: deleteOne - <<: *collection_w0 - arguments: - session: session0 - filter: - _id: 0 - result: - deletedCount: 1 - - *commitTransaction - - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 0} - limit: 1 - ordered: true - <<: *transactionCommandArgs - command_name: delete - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: [] - - - description: unacknowledged write concern coll deleteMany - - operations: - - *startTransaction - - name: deleteMany - <<: *collection_w0 - arguments: - session: session0 - filter: - _id: 0 - result: - deletedCount: 1 - - *commitTransaction - - expectations: - - command_started_event: - command: - delete: *collection_name - deletes: - - q: {_id: 0} - limit: 0 - ordered: true - <<: *transactionCommandArgs - command_name: delete - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: [] - - - description: unacknowledged write concern coll updateOne - - operations: - - *startTransaction - - name: updateOne - <<: *collection_w0 - arguments: - session: session0 - filter: {_id: 0} - update: - $inc: {x: 1} - upsert: true - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - - *commitTransaction - - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 0} - u: {$inc: {x: 1}} - upsert: true - ordered: true - <<: *transactionCommandArgs - command_name: update - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - {_id: 0, x: 1} - - - description: unacknowledged write concern coll updateMany - - operations: - - *startTransaction - - name: updateMany - <<: *collection_w0 - arguments: - session: session0 - filter: {_id: 0} - update: - $inc: {x: 1} - upsert: true - result: - matchedCount: 1 - modifiedCount: 1 - upsertedCount: 0 - - *commitTransaction - - expectations: - - command_started_event: - command: - update: *collection_name - updates: - - q: {_id: 0} - u: {$inc: {x: 1}} - multi: true - upsert: true - ordered: true - <<: *transactionCommandArgs - command_name: update - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - {_id: 0, x: 1} - - - description: unacknowledged write concern coll findOneAndDelete - - operations: - - *startTransaction - - name: findOneAndDelete - <<: *collection_w0 - arguments: - session: session0 - filter: {_id: 0} - result: {_id: 0} - - *commitTransaction - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 0} - remove: True - <<: *transactionCommandArgs - command_name: findAndModify - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: [] - - - description: unacknowledged write concern coll findOneAndReplace - - operations: - - *startTransaction - - name: findOneAndReplace - <<: *collection_w0 - arguments: - session: session0 - filter: {_id: 0} - replacement: {x: 1} - returnDocument: Before - result: {_id: 0} - - *commitTransaction - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 0} - update: {x: 1} - new: false - <<: *transactionCommandArgs - command_name: findAndModify - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - {_id: 0, x: 1} - - - description: unacknowledged write concern coll findOneAndUpdate - - operations: - - *startTransaction - - name: findOneAndUpdate - <<: *collection_w0 - arguments: - session: session0 - filter: {_id: 0} - update: - $inc: {x: 1} - returnDocument: Before - result: {_id: 0} - - *commitTransaction - - expectations: - - command_started_event: - command: - findAndModify: *collection_name - query: {_id: 0} - update: {$inc: {x: 1}} - new: false - <<: *transactionCommandArgs - command_name: findAndModify - database_name: *database_name - - *commitWithDefaultWCEvent - - outcome: - collection: - data: - - {_id: 0, x: 1} diff --git a/src/test/spec/json/transactions/unified/abort.json b/src/test/spec/json/transactions/unified/abort.json new file mode 100644 index 000000000..c151a7d0c --- /dev/null +++ b/src/test/spec/json/transactions/unified/abort.json @@ -0,0 +1,828 @@ +{ + "description": "abort", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "abort", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "implicit abort", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "endSession" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "two aborts", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "session0", + "name": "abortTransaction", + "expectError": { + "errorContains": "cannot call abortTransaction twice" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abort without start", + "operations": [ + { + "object": "session0", + "name": "abortTransaction", + "expectError": { + "errorContains": "no transaction started" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abort directly after no-op commit", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "abortTransaction", + "expectError": { + "errorContains": "Cannot call abortTransaction after calling commitTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abort directly after commit", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "abortTransaction", + "expectError": { + "errorContains": "Cannot call abortTransaction after calling commitTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "abort ignores TransactionAborted", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ], + "errorContains": "E11000" + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorCodeName": "NoSuchTransaction", + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abort does not apply writeConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 10 + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/abort.yml b/src/test/spec/json/transactions/unified/abort.yml new file mode 100644 index 000000000..194846c92 --- /dev/null +++ b/src/test/spec/json/transactions/unified/abort.yml @@ -0,0 +1,483 @@ +description: abort + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: abort + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'implicit abort' + operations: + # Start a transaction but don't commit - the driver calls abortTransaction from ClientSession.endSession() + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # The original legacy test relied on the test runner to call endSession after all operations. Since the unified + # test runner has no such behavior, we manually call endSession as the last operation. + - + object: *session0 + name: endSession + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'two aborts' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + - + object: *session0 + name: abortTransaction + expectError: + errorContains: 'cannot call abortTransaction twice' + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abort without start' + operations: + - + object: *session0 + name: abortTransaction + expectError: + errorContains: 'no transaction started' + expectEvents: + - + client: *client0 + events: [] + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abort directly after no-op commit' + operations: + - + object: *session0 + name: startTransaction + - + object: *session0 + name: commitTransaction + # Error calling abort after no-op commit + - + object: *session0 + name: abortTransaction + expectError: + errorContains: 'Cannot call abortTransaction after calling commitTransaction' + expectEvents: + - + client: *client0 + events: [] + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abort directly after commit' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: abortTransaction + expectError: + errorContains: 'Cannot call abortTransaction after calling commitTransaction' + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'abort ignores TransactionAborted' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # Abort the server transaction with a duplicate key error + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + errorLabelsOmit: + - TransientTransactionError + - UnknownTransactionCommitResult + # DuplicateKey error code included in the bulk write error message returned by the server + errorContains: E11000 + # Make sure the server aborted the transaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + errorCodeName: NoSuchTransaction + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + # abortTransaction must ignore the TransactionAborted and succeed + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abort does not apply writeConcern' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: 10 + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + # No write concern error + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] diff --git a/src/test/spec/json/transactions/unified/bulk.json b/src/test/spec/json/transactions/unified/bulk.json new file mode 100644 index 000000000..ece162518 --- /dev/null +++ b/src/test/spec/json/transactions/unified/bulk.json @@ -0,0 +1,652 @@ +{ + "description": "bulk", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "bulk", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "collection0", + "name": "bulkWrite", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + }, + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + }, + { + "updateOne": { + "filter": { + "_id": 2 + }, + "update": { + "$set": { + "x": 2 + } + }, + "upsert": true + } + }, + { + "insertOne": { + "document": { + "_id": 3 + } + } + }, + { + "insertOne": { + "document": { + "_id": 4 + } + } + }, + { + "insertOne": { + "document": { + "_id": 5 + } + } + }, + { + "insertOne": { + "document": { + "_id": 6 + } + } + }, + { + "insertOne": { + "document": { + "_id": 7 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + } + }, + { + "replaceOne": { + "filter": { + "_id": 2 + }, + "replacement": { + "y": 2 + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 3 + } + } + }, + { + "deleteOne": { + "filter": { + "_id": 4 + } + } + }, + { + "updateMany": { + "filter": { + "_id": { + "$gte": 2 + } + }, + "update": { + "$set": { + "z": 1 + } + } + } + }, + { + "deleteMany": { + "filter": { + "_id": { + "$gte": 6 + } + } + } + } + ] + }, + "expectResult": { + "deletedCount": 4, + "insertedCount": 6, + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "3": 3, + "4": 4, + "5": 5, + "6": 6, + "7": 7 + } + }, + "matchedCount": 7, + "modifiedCount": 7, + "upsertedCount": 1, + "upsertedIds": { + "2": 2 + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 1 + }, + "limit": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$set": { + "x": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": 2 + }, + "u": { + "$set": { + "x": 2 + } + }, + "upsert": true, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + }, + { + "_id": 6 + }, + { + "_id": 7 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "y": 1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + }, + { + "q": { + "_id": 2 + }, + "u": { + "y": 2 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 3 + }, + "limit": 1 + }, + { + "q": { + "_id": 4 + }, + "limit": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gte": 2 + } + }, + "u": { + "$set": { + "z": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$gte": 6 + } + }, + "limit": 0 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "y": 1 + }, + { + "_id": 2, + "y": 2, + "z": 1 + }, + { + "_id": 5, + "z": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/bulk.yml b/src/test/spec/json/transactions/unified/bulk.yml new file mode 100644 index 000000000..5f686498b --- /dev/null +++ b/src/test/spec/json/transactions/unified/bulk.yml @@ -0,0 +1,329 @@ +description: bulk + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: bulk + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: deleteOne + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + deletedCount: 1 + - + object: *collection0 + name: bulkWrite + arguments: + session: *session0 + requests: + - + insertOne: + document: { _id: 1 } + - + updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + - + updateOne: + filter: { _id: 2 } + update: { $set: { x: 2 } } + # Produces upsertedIds: {2: 2} in the result + upsert: true + - + insertOne: + document: { _id: 3 } + - + insertOne: + document: { _id: 4 } + - + insertOne: + document: { _id: 5 } + - + insertOne: + document: { _id: 6 } + - + insertOne: + document: { _id: 7 } + # Keep replaces segregated from updates, so that drivers that aren't able to coalesce + # adjacent updates and replaces into a single update command will still pass this test + - + replaceOne: + filter: { _id: 1 } + replacement: { y: 1 } + - + replaceOne: + filter: { _id: 2 } + replacement: { y: 2 } + - + deleteOne: + filter: { _id: 3 } + - + deleteOne: + filter: { _id: 4 } + - + updateMany: + filter: { _id: { $gte: 2 } } + update: { $set: { z: 1 } } + # Keep deleteMany segregated from deleteOne, so that drivers that aren't able to coalesce + # adjacent mixed deletes into a single delete command will still pass this test + - + deleteMany: + filter: { _id: { $gte: 6 } } + expectResult: + deletedCount: 4 + insertedCount: 6 + insertedIds: + $$unsetOrMatches: + '0': 1 + '3': 3 + '4': 4 + '5': 5 + '6': 6 + '7': 7 + matchedCount: 7 + modifiedCount: 7 + upsertedCount: 1 + upsertedIds: { '2': 2 } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 1 } + limit: 1 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + # Commands in the bulkWrite + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $set: { x: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - + q: { _id: 2 } + u: { $set: { x: 2 } } + upsert: true + multi: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + - { _id: 6 } + - { _id: 7 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { y: 1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + - + q: { _id: 2 } + u: { y: 2 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 3 } + limit: 1 + - + q: { _id: 4 } + limit: 1 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: { $gte: 2 } } + u: { $set: { z: 1 } } + multi: true + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: { $gte: 6 } } + limit: 0 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, y: 1 } + - { _id: 2, y: 2, z: 1 } + - { _id: 5, z: 1 } diff --git a/src/test/spec/json/transactions/unified/causal-consistency.json b/src/test/spec/json/transactions/unified/causal-consistency.json new file mode 100644 index 000000000..52a6cb818 --- /dev/null +++ b/src/test/spec/json/transactions/unified/causal-consistency.json @@ -0,0 +1,426 @@ +{ + "description": "causal-consistency", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "session": { + "id": "session_no_cc", + "client": "client0", + "sessionOptions": { + "causalConsistency": false + } + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "count": 0 + } + ] + } + ], + "tests": [ + { + "description": "causal consistency", + "operations": [ + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "count": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "count": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "count": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "count": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "count": 2 + } + ] + } + ] + }, + { + "description": "causal consistency disabled", + "operations": [ + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session_no_cc", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session_no_cc", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session_no_cc", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "count": 1 + } + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "session_no_cc", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session_no_cc" + }, + "txnNumber": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "count": 1 + } + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session_no_cc" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session_no_cc" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "count": 1 + }, + { + "_id": 2 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/causal-consistency.yml b/src/test/spec/json/transactions/unified/causal-consistency.yml new file mode 100644 index 000000000..c3e75523d --- /dev/null +++ b/src/test/spec/json/transactions/unified/causal-consistency.yml @@ -0,0 +1,222 @@ +description: causal-consistency + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + - + session: + id: &session_no_cc session_no_cc + client: *client0 + sessionOptions: + causalConsistency: false + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, count: 0 } + +tests: + - + description: 'causal consistency' + operations: + # Update a document without a transaction + - &updateOne + object: *collection0 + name: updateOne + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { count: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + # Updating the same document inside a transaction. + # Casual consistency ensures that the transaction snapshot is causally after the first updateOne. + - + object: *session0 + name: startTransaction + - *updateOne + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $inc: { count: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $$exists: false } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $inc: { count: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, count: 2 } + - + description: 'causal consistency disabled' + operations: + # Insert a document without a transaction + - + object: *collection0 + name: insertOne + arguments: + session: *session_no_cc + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session_no_cc + name: startTransaction + - + object: *collection0 + name: updateOne + arguments: + session: *session_no_cc + filter: { _id: 1 } + update: { $inc: { count: 1 } } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - + object: *session_no_cc + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session_no_cc } + txnNumber: { $$exists: false } + autocommit: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 1 } + u: { $inc: { count: 1 } } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + # No afterClusterTime + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session_no_cc } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session_no_cc } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1, count: 1 } + - { _id: 2 } diff --git a/src/test/spec/json/transactions/unified/client-bulkWrite.json b/src/test/spec/json/transactions/unified/client-bulkWrite.json new file mode 100644 index 000000000..4a8d013f8 --- /dev/null +++ b/src/test/spec/json/transactions/unified/client-bulkWrite.json @@ -0,0 +1,593 @@ +{ + "description": "client bulkWrite transactions", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client_with_wmajority", + "uriOptions": { + "w": "majority" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "session": { + "id": "session_with_wmajority", + "client": "client_with_wmajority" + } + } + ], + "_yamlAnchors": { + "namespace": "transaction-tests.coll0" + }, + "initialData": [ + { + "databaseName": "transaction-tests", + "collectionName": "coll0", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + } + ] + } + ], + "tests": [ + { + "description": "client bulkWrite in a transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + }, + { + "updateOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "updateMany": { + "namespace": "transaction-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "update": { + "$inc": { + "x": 2 + } + } + } + }, + { + "replaceOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "upsert": true + } + }, + { + "deleteOne": { + "namespace": "transaction-tests.coll0", + "filter": { + "_id": 5 + } + } + }, + { + "deleteMany": { + "namespace": "transaction-tests.coll0", + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 1, + "matchedCount": 3, + "modifiedCount": 3, + "deletedCount": 3, + "insertResults": { + "0": { + "insertedId": 8 + } + }, + "updateResults": { + "1": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedId": { + "$$exists": false + } + }, + "2": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedId": { + "$$exists": false + } + }, + "3": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedId": 4 + } + }, + "deleteResults": { + "4": { + "deletedCount": 1 + }, + "5": { + "deletedCount": 2 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + }, + { + "update": 0, + "filter": { + "_id": 1 + }, + "updateMods": { + "$inc": { + "x": 1 + } + }, + "multi": false + }, + { + "update": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 1 + } + }, + { + "_id": { + "$lte": 3 + } + } + ] + }, + "updateMods": { + "$inc": { + "x": 2 + } + }, + "multi": true + }, + { + "update": 0, + "filter": { + "_id": 4 + }, + "updateMods": { + "x": 44 + }, + "upsert": true, + "multi": false + }, + { + "delete": 0, + "filter": { + "_id": 5 + }, + "multi": false + }, + { + "delete": 0, + "filter": { + "$and": [ + { + "_id": { + "$gt": 5 + } + }, + { + "_id": { + "$lte": 7 + } + } + ] + }, + "multi": true + } + ], + "nsInfo": [ + { + "ns": "transaction-tests.coll0" + } + ] + } + } + }, + { + "commandStartedEvent": { + "commandName": "commitTransaction", + "databaseName": "admin", + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": 1, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 35 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client writeConcern ignored for client bulkWrite in transaction", + "operations": [ + { + "object": "session_with_wmajority", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 1 + } + } + }, + { + "object": "client_with_wmajority", + "name": "clientBulkWrite", + "arguments": { + "session": "session_with_wmajority", + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "$$unsetOrMatches": {} + }, + "updateResults": { + "$$unsetOrMatches": {} + }, + "deleteResults": { + "$$unsetOrMatches": {} + } + } + }, + { + "object": "session_with_wmajority", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client_with_wmajority", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "lsid": { + "$$sessionLsid": "session_with_wmajority" + }, + "txnNumber": 1, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "bulkWrite": 1, + "errorsOnly": true, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 8, + "x": 88 + } + } + ], + "nsInfo": [ + { + "ns": "transaction-tests.coll0" + } + ] + } + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session_with_wmajority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": 1 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "coll0", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 5, + "x": 55 + }, + { + "_id": 6, + "x": 66 + }, + { + "_id": 7, + "x": 77 + }, + { + "_id": 8, + "x": 88 + } + ] + } + ] + }, + { + "description": "client bulkWrite with writeConcern in a transaction causes a transaction error", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "client0", + "name": "clientBulkWrite", + "arguments": { + "session": "session0", + "writeConcern": { + "w": 1 + }, + "models": [ + { + "insertOne": { + "namespace": "transaction-tests.coll0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "isClientError": true, + "errorContains": "Cannot set write concern after starting a transaction" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/client-bulkWrite.yml b/src/test/spec/json/transactions/unified/client-bulkWrite.yml new file mode 100644 index 000000000..d80e61872 --- /dev/null +++ b/src/test/spec/json/transactions/unified/client-bulkWrite.yml @@ -0,0 +1,263 @@ +description: "client bulkWrite transactions" +schemaVersion: "1.4" # To support `serverless: forbid` +runOnRequirements: + - minServerVersion: "8.0" + topologies: + - replicaset + - sharded + - load-balanced + serverless: forbid + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + - session: + id: &session0 session0 + client: *client0 + - client: + id: &client_with_wmajority client_with_wmajority + uriOptions: + w: majority + observeEvents: + - commandStartedEvent + - session: + id: &session_with_wmajority session_with_wmajority + client: *client_with_wmajority + +_yamlAnchors: + namespace: &namespace "transaction-tests.coll0" + +initialData: + - databaseName: *database0Name + collectionName: *collection0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + +tests: + - description: "client bulkWrite in a transaction" + operations: + - object: *session0 + name: startTransaction + - object: *client0 + name: clientBulkWrite + arguments: + session: *session0 + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + - updateOne: + namespace: *namespace + filter: { _id: 1 } + update: { $inc: { x: 1 } } + - updateMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + update: { $inc: { x: 2 } } + - replaceOne: + namespace: *namespace + filter: { _id: 4 } + replacement: { x: 44 } + upsert: true + - deleteOne: + namespace: *namespace + filter: { _id: 5 } + - deleteMany: + namespace: *namespace + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 1 + matchedCount: 3 + modifiedCount: 3 + deletedCount: 3 + insertResults: + 0: + insertedId: 8 + updateResults: + 1: + matchedCount: 1 + modifiedCount: 1 + upsertedId: { $$exists: false } + 2: + matchedCount: 2 + modifiedCount: 2 + upsertedId: { $$exists: false } + 3: + matchedCount: 1 + modifiedCount: 0 + upsertedId: 4 + deleteResults: + 4: + deletedCount: 1 + 5: + deletedCount: 2 + - object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + - update: 0 + filter: { _id: 1 } + updateMods: { $inc: { x: 1 } } + multi: false + - update: 0 + filter: + $and: [ { _id: { $gt: 1 } }, { _id: { $lte: 3 } } ] + updateMods: { $inc: { x: 2 } } + multi: true + - update: 0 + filter: { _id: 4 } + updateMods: { x: 44 } + upsert: true + multi: false + - delete: 0 + filter: { _id: 5 } + multi: false + - delete: 0 + filter: + $and: [ { _id: { $gt: 5 } }, { _id: { $lte: 7 } } ] + multi: true + nsInfo: + - ns: *namespace + - commandStartedEvent: + commandName: commitTransaction + databaseName: admin + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: 1 + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 12 } + - { _id: 2, x: 24 } + - { _id: 3, x: 35 } + - { _id: 4, x: 44 } + - { _id: 8, x: 88 } + - description: 'client writeConcern ignored for client bulkWrite in transaction' + operations: + - object: *session_with_wmajority + name: startTransaction + arguments: + writeConcern: + w: 1 + - object: *client_with_wmajority + name: clientBulkWrite + arguments: + session: *session_with_wmajority + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + $$unsetOrMatches: {} + updateResults: + $$unsetOrMatches: {} + deleteResults: + $$unsetOrMatches: {} + - object: *session_with_wmajority + name: commitTransaction + expectEvents: + - + client: *client_with_wmajority + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + lsid: { $$sessionLsid: *session_with_wmajority } + txnNumber: 1 + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + bulkWrite: 1 + errorsOnly: true + ordered: true + ops: + - insert: 0 + document: { _id: 8, x: 88 } + nsInfo: + - ns: *namespace + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session_with_wmajority } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: 1 + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 5, x: 55 } + - { _id: 6, x: 66 } + - { _id: 7, x: 77 } + - { _id: 8, x: 88 } + - description: "client bulkWrite with writeConcern in a transaction causes a transaction error" + operations: + - object: *session0 + name: startTransaction + - object: *client0 + name: clientBulkWrite + arguments: + session: *session0 + writeConcern: + w: 1 + models: + - insertOne: + namespace: *namespace + document: { _id: 8, x: 88 } + expectError: + isClientError: true + errorContains: "Cannot set write concern after starting a transaction" diff --git a/src/test/spec/json/transactions/unified/commit.json b/src/test/spec/json/transactions/unified/commit.json new file mode 100644 index 000000000..ab778d8df --- /dev/null +++ b/src/test/spec/json/transactions/unified/commit.json @@ -0,0 +1,1234 @@ +{ + "description": "commit", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commit", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "rerun commit after empty transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "multiple commits in a row", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "write concern error on commit", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 10 + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commit without start", + "operations": [ + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorContains": "no transaction started" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "commit after no-op abort", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorContains": "Cannot call commitTransaction after calling abortTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "commit after abort", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorContains": "Cannot call commitTransaction after calling abortTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "multiple commits after empty transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "reset session state commit", + "operations": [ + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorContains": "no transaction started" + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "reset session state abort", + "operations": [ + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction", + "expectError": { + "errorContains": "no transaction started" + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 2 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/commit.yml b/src/test/spec/json/transactions/unified/commit.yml new file mode 100644 index 000000000..d9af08489 --- /dev/null +++ b/src/test/spec/json/transactions/unified/commit.yml @@ -0,0 +1,713 @@ +description: commit + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + # Define a second set of entities for a retryWrites=false client + - + client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - + database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + session: + id: &session1 session1 + client: *client1 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: commit + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + # Again, to verify that txnNumber is incremented + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - + description: 'rerun commit after empty transaction' + operations: + - + object: *session0 + name: startTransaction + - + object: *session0 + name: commitTransaction + # Rerun the commit (which does not increment the txnNumber) + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'multiple commits in a row' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'write concern error on commit' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: 10 + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + # { + # 'ok': 1.0, + # 'writeConcernError': { + # 'code': 100, + # 'codeName': 'UnsatisfiableWriteConcern', + # 'errmsg': 'Not enough data-bearing nodes' + # } + # } + errorLabelsOmit: + - TransientTransactionError + - UnknownTransactionCommitResult + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commit without start' + operations: + - + object: *session0 + name: commitTransaction + expectError: + errorContains: 'no transaction started' + expectEvents: + - + client: *client0 + events: [] + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'commit after no-op abort' + operations: + - + object: *session0 + name: startTransaction + - + object: *session0 + name: abortTransaction + - + object: *session0 + name: commitTransaction + expectError: + errorContains: 'Cannot call commitTransaction after calling abortTransaction' + expectEvents: + - + client: *client0 + events: [] + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'commit after abort' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + - + object: *session0 + name: commitTransaction + expectError: + errorContains: 'Cannot call commitTransaction after calling abortTransaction' + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + description: 'multiple commits after empty transaction' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + # Increments txnNumber + - + object: *session0 + name: startTransaction + # These commits aren't sent to server, transaction is empty + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: commitTransaction + # Verify that previous, empty transaction incremented txnNumber + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + # txnNumber 2 was skipped + txnNumber: { $numberLong: '3' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '3' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'reset session state commit' + operations: + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + # Running any operation after an ended transaction resets the session state to "no transaction" + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + # Calling commit again should error instead of re-running the commit + - + object: *session1 + name: commitTransaction + expectError: + errorContains: 'no transaction started' + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $$exists: false } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + commandName: insert + databaseName: *database_name + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - + description: 'reset session state abort' + operations: + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: abortTransaction + # Running any operation after an ended transaction resets the session state to "no transaction" + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + # Calling abort should error with "no transaction started" instead of "cannot call abortTransaction twice" + - + object: *session1 + name: abortTransaction + expectError: + errorContains: 'no transaction started' + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $$exists: false } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + commandName: insert + databaseName: *database_name + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 2 } diff --git a/src/test/spec/json/transactions/unified/count.json b/src/test/spec/json/transactions/unified/count.json new file mode 100644 index 000000000..404b06beb --- /dev/null +++ b/src/test/spec/json/transactions/unified/count.json @@ -0,0 +1,177 @@ +{ + "description": "count", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0.2", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ], + "tests": [ + { + "description": "count", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "count", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorCodeName": "OperationNotSupportedInTransaction", + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "count": "test", + "query": { + "_id": 1 + }, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "count", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/count.yml b/src/test/spec/json/transactions/unified/count.yml new file mode 100644 index 000000000..c78d5b2ae --- /dev/null +++ b/src/test/spec/json/transactions/unified/count.yml @@ -0,0 +1,102 @@ +description: count + +schemaVersion: '1.3' + +runOnRequirements: + # SERVER-35388 introduced OperationNotSupportedInTransaction in 4.0.2 + - + minServerVersion: 4.0.2 + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + +tests: + - + description: count + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: count + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorCodeName: OperationNotSupportedInTransaction + errorLabelsOmit: + - TransientTransactionError + - UnknownTransactionCommitResult + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + count: *collection_name + query: { _id: 1 } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: count + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data \ No newline at end of file diff --git a/src/test/spec/json/transactions/unified/create-collection.json b/src/test/spec/json/transactions/unified/create-collection.json new file mode 100644 index 000000000..e190088b3 --- /dev/null +++ b/src/test/spec/json/transactions/unified/create-collection.json @@ -0,0 +1,282 @@ +{ + "description": "create-collection", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "explicitly create collection using create command", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database0", + "name": "createCollection", + "arguments": { + "session": "session0", + "collection": "test" + } + }, + { + "object": "testRunner", + "name": "assertCollectionNotExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertCollectionExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test", + "writeConcern": { + "$$exists": false + } + }, + "commandName": "drop", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "create", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "implicitly create collection using insert", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "assertCollectionNotExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertCollectionExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test", + "writeConcern": { + "$$exists": false + } + }, + "commandName": "drop", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/create-collection.yml b/src/test/spec/json/transactions/unified/create-collection.yml new file mode 100644 index 000000000..f79571686 --- /dev/null +++ b/src/test/spec/json/transactions/unified/create-collection.yml @@ -0,0 +1,177 @@ +description: create-collection + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.4 + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'explicitly create collection using create command' + operations: + - + object: *database0 + name: dropCollection + arguments: + collection: *collection_name + - + object: *session0 + name: startTransaction + - + object: *database0 + name: createCollection + arguments: + session: *session0 + collection: *collection_name + - + object: testRunner + name: assertCollectionNotExists + arguments: + databaseName: *database_name + collectionName: *collection_name + - + object: *session0 + name: commitTransaction + - + object: testRunner + name: assertCollectionExists + arguments: + databaseName: *database_name + collectionName: *collection_name + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + drop: *collection_name + writeConcern: { $$exists: false } + commandName: drop + databaseName: *database_name + - + commandStartedEvent: + command: + create: *collection_name + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: create + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + description: 'implicitly create collection using insert' + operations: + - + object: *database0 + name: dropCollection + arguments: + collection: *collection_name + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: testRunner + name: assertCollectionNotExists + arguments: + databaseName: *database_name + collectionName: *collection_name + - + object: *session0 + name: commitTransaction + - + object: testRunner + name: assertCollectionExists + arguments: + databaseName: *database_name + collectionName: *collection_name + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + drop: *collection_name + writeConcern: { $$exists: false } + commandName: drop + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/create-index.json b/src/test/spec/json/transactions/unified/create-index.json new file mode 100644 index 000000000..98d6e1154 --- /dev/null +++ b/src/test/spec/json/transactions/unified/create-index.json @@ -0,0 +1,313 @@ +{ + "description": "create-index", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.4", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "create index on a non-existing collection", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "createIndex", + "arguments": { + "session": "session0", + "name": "t_1", + "keys": { + "x": 1 + } + } + }, + { + "object": "testRunner", + "name": "assertIndexNotExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test", + "indexName": "t_1" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertIndexExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test", + "indexName": "t_1" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test", + "writeConcern": { + "$$exists": false + } + }, + "commandName": "drop", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "createIndexes": "test", + "indexes": [ + { + "name": "t_1", + "key": { + "x": 1 + } + } + ], + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "createIndexes", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "create index on a collection created within the same transaction", + "operations": [ + { + "object": "database0", + "name": "dropCollection", + "arguments": { + "collection": "test" + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database0", + "name": "createCollection", + "arguments": { + "session": "session0", + "collection": "test" + } + }, + { + "object": "collection0", + "name": "createIndex", + "arguments": { + "session": "session0", + "name": "t_1", + "keys": { + "x": 1 + } + } + }, + { + "object": "testRunner", + "name": "assertIndexNotExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test", + "indexName": "t_1" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertIndexExists", + "arguments": { + "databaseName": "transaction-tests", + "collectionName": "test", + "indexName": "t_1" + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "drop": "test", + "writeConcern": { + "$$exists": false + } + }, + "commandName": "drop", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "create": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "create", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "createIndexes": "test", + "indexes": [ + { + "name": "t_1", + "key": { + "x": 1 + } + } + ], + "lsid": { + "$$sessionLsid": "session0" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "createIndexes", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/create-index.yml b/src/test/spec/json/transactions/unified/create-index.yml new file mode 100644 index 000000000..4465d0922 --- /dev/null +++ b/src/test/spec/json/transactions/unified/create-index.yml @@ -0,0 +1,202 @@ +description: create-index + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.4 + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] +tests: + - + description: 'create index on a non-existing collection' + operations: + - + object: *database0 + name: dropCollection + arguments: + collection: *collection_name + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: createIndex + arguments: + session: *session0 + name: &index_name t_1 + keys: + x: 1 + - + object: testRunner + name: assertIndexNotExists + arguments: + databaseName: *database_name + collectionName: *collection_name + indexName: *index_name + - + object: *session0 + name: commitTransaction + - + object: testRunner + name: assertIndexExists + arguments: + databaseName: *database_name + collectionName: *collection_name + indexName: *index_name + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + drop: *collection_name + writeConcern: { $$exists: false } + commandName: drop + databaseName: *database_name + - + commandStartedEvent: + command: + createIndexes: *collection_name + indexes: + - + name: *index_name + key: + x: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: createIndexes + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + description: 'create index on a collection created within the same transaction' + operations: + - + object: *database0 + name: dropCollection + arguments: + collection: *collection_name + - + object: *session0 + name: startTransaction + - + object: *database0 + name: createCollection + arguments: + session: *session0 + collection: *collection_name + - + object: *collection0 + name: createIndex + arguments: + session: *session0 + name: *index_name + keys: + x: 1 + - + object: testRunner + name: assertIndexNotExists + arguments: + databaseName: *database_name + collectionName: *collection_name + indexName: *index_name + - + object: *session0 + name: commitTransaction + - + object: testRunner + name: assertIndexExists + arguments: + databaseName: *database_name + collectionName: *collection_name + indexName: *index_name + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + drop: *collection_name + writeConcern: { $$exists: false } + commandName: drop + databaseName: *database_name + - + commandStartedEvent: + command: + create: *collection_name + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: create + databaseName: *database_name + - + commandStartedEvent: + command: + createIndexes: *collection_name + indexes: + - + name: *index_name + key: + x: 1 + lsid: { $$sessionLsid: *session0 } + writeConcern: { $$exists: false } + commandName: createIndexes + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/delete.json b/src/test/spec/json/transactions/unified/delete.json new file mode 100644 index 000000000..4c1cae0a4 --- /dev/null +++ b/src/test/spec/json/transactions/unified/delete.json @@ -0,0 +1,425 @@ +{ + "description": "delete", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + ], + "tests": [ + { + "description": "delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "collection0", + "name": "deleteMany", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$lte": 3 + } + } + }, + "expectResult": { + "deletedCount": 2 + } + }, + { + "object": "collection0", + "name": "deleteOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 1 + }, + "limit": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$lte": 3 + } + }, + "limit": 0 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 4 + }, + "limit": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 5 + } + ] + } + ] + }, + { + "description": "collection writeConcern ignored for delete", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection_wc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection_wc_majority", + "name": "deleteOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "collection_wc_majority", + "name": "deleteMany", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$lte": 3 + } + } + }, + "expectResult": { + "deletedCount": 2 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 1 + }, + "limit": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": { + "$lte": 3 + } + }, + "limit": 0 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/delete.yml b/src/test/spec/json/transactions/unified/delete.yml new file mode 100644 index 000000000..49bbf1613 --- /dev/null +++ b/src/test/spec/json/transactions/unified/delete.yml @@ -0,0 +1,240 @@ +description: delete + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + +tests: + - + description: delete + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: deleteOne + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + deletedCount: 1 + - + object: *collection0 + name: deleteMany + arguments: + session: *session0 + filter: { _id: { $lte: 3 } } + expectResult: + deletedCount: 2 + - + object: *collection0 + name: deleteOne + arguments: + session: *session0 + filter: { _id: 4 } + expectResult: + deletedCount: 1 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 1 } + limit: 1 + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: { $lte: 3 } } + limit: 0 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 4 } + limit: 1 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 5 } + - + description: 'collection writeConcern ignored for delete' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection_wc_majority + name: deleteOne + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + deletedCount: 1 + - + object: *collection_wc_majority + name: deleteMany + arguments: + session: *session0 + filter: { _id: { $lte: 3 } } + expectResult: + deletedCount: 2 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 1 } + limit: 1 + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: { $lte: 3 } } + limit: 0 + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: delete + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/error-labels-blockConnection.json b/src/test/spec/json/transactions/unified/error-labels-blockConnection.json new file mode 100644 index 000000000..8da04d100 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels-blockConnection.json @@ -0,0 +1,235 @@ +{ + "description": "error-labels-blockConnection", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.2", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "uriOptions": { + "socketTimeoutMS": 100 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "blockConnection": true, + "blockTimeMS": 150 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/error-labels-blockConnection.yml b/src/test/spec/json/transactions/unified/error-labels-blockConnection.yml new file mode 100644 index 000000000..7fed7fc92 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels-blockConnection.yml @@ -0,0 +1,158 @@ +# This file contains a single test that should be in error-labels.yml. The test +# was moved from error-labels.yml during the spec work for client-side +# operations timeout because it uses the blockConnection parameter in +# failCommand, which is only available in server versions 4.2+. It should be +# merged back into error-labels.yml when that test file is ported to the +# unified test format as the format allows for per-test runOn requirements. +description: error-labels-blockConnection + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.2' + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + uriOptions: + socketTimeoutMS: 100 + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + # This test previously used failCommand with closeConnection=true to force a + # network error, but this does not work after CSOT is implemented because + # network errors are retried indefinitely. It has been changed to use + # socketTimeoutMS with blockConnection to force a network error because + # drivers only retry socketTimeoutMS-related errors once rather than + # indefinitely. + - + description: 'add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + # Drivers stop retrying after two socket timeouts that occur due to the use of socketTimeoutMS + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + blockConnection: true + blockTimeMS: 150 + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/error-labels-errorLabels.json b/src/test/spec/json/transactions/unified/error-labels-errorLabels.json new file mode 100644 index 000000000..1f95ad341 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels-errorLabels.json @@ -0,0 +1,423 @@ +{ + "description": "error-labels-errorLabels", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "serverless": "forbid", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to retryable commit errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 11602, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to writeConcernError ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/error-labels-errorLabels.yml b/src/test/spec/json/transactions/unified/error-labels-errorLabels.yml new file mode 100644 index 000000000..4af816549 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels-errorLabels.yml @@ -0,0 +1,257 @@ +description: error-labels-errorLabels + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: forbid + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'add RetryableWriteError and UnknownTransactionCommitResult labels to retryable commit errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + errorCode: 11602 # InterruptedDueToReplStateChange + errorLabels: + - RetryableWriteError + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'add RetryableWriteError and UnknownTransactionCommitResult labels to writeConcernError ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/error-labels.json b/src/test/spec/json/transactions/unified/error-labels.json new file mode 100644 index 000000000..74ed750b0 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels.json @@ -0,0 +1,2263 @@ +{ + "description": "error-labels", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "DuplicateKey errors do not contain transient label", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 1 + }, + { + "_id": 1 + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ], + "errorContains": "E11000" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + }, + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "NotWritablePrimary errors contain transient label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 10107 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "WriteConflict errors contain transient label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 112 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "NoSuchTransaction errors contain transient label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 251 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "NoSuchTransaction errors on commit contain transient label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 251 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "add TransientTransactionError label to connection errors, but do not add RetryableWriteError label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 4 + }, + "data": { + "failCommands": [ + "insert", + "find", + "aggregate", + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": {}, + "session": "session0" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "session": "session0" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": {}, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "do not add RetryableWriteError label to writeConcernError ShutdownInProgress that occurs within transaction", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 64, + "errmsg": "multiple errors reported" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout with wtimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 64, + "errmsg": "waiting for replication timed out", + "errInfo": { + "wtimeout": true + } + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "omit UnknownTransactionCommitResult label from writeConcernError UnsatisfiableWriteConcern", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 100, + "errmsg": "Not enough data-bearing nodes" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "omit UnknownTransactionCommitResult label from writeConcernError UnknownReplWriteConcern", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 79, + "errmsg": "No write concern mode named 'blah' found in replica set configuration" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteConcern", + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "do not add UnknownTransactionCommitResult label to MaxTimeMSExpired inside transactions", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 50 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "maxTimeMS": 60000, + "session": "session0" + }, + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError", + "UnknownTransactionCommitResult", + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": {}, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "maxTimeMS": 60000 + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "add UnknownTransactionCommitResult label to MaxTimeMSExpired", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 50 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + }, + "maxCommitTimeMS": 60000 + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "add UnknownTransactionCommitResult label to writeConcernError MaxTimeMSExpired", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 50, + "errmsg": "operation exceeded time limit" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + }, + "maxCommitTimeMS": 60000 + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/error-labels.yml b/src/test/spec/json/transactions/unified/error-labels.yml new file mode 100644 index 000000000..64e241d55 --- /dev/null +++ b/src/test/spec/json/transactions/unified/error-labels.yml @@ -0,0 +1,1314 @@ +description: error-labels + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'DuplicateKey errors do not contain transient label' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertMany + arguments: + session: *session0 + documents: + - { _id: 1 } + - { _id: 1 } + expectError: + errorLabelsOmit: + - TransientTransactionError + - UnknownTransactionCommitResult + # DuplicateKey error code included in the bulk write error message + # returned by the server + errorContains: E11000 + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'NotWritablePrimary errors contain transient label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + errorCode: 10107 # NotWritablePrimary + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + # Note, the server will return the errorLabel in this case + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'WriteConflict errors contain transient label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + errorCode: 112 # WriteConflict + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + # Note, the server will return the errorLabel in this case + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'NoSuchTransaction errors contain transient label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + errorCode: 251 # NoSuchTransaction + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + # Note, the server will return the errorLabel in this case + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'NoSuchTransaction errors on commit contain transient label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 251 # NoSuchTransaction + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + # Note, the server will return the errorLabel in this case + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'add TransientTransactionError label to connection errors, but do not add RetryableWriteError label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 4 } + data: + failCommands: + - insert + - find + - aggregate + - distinct + closeConnection: true + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: &transient_label_only + errorLabelsContain: + - TransientTransactionError + # While a connection error would normally be retryable, these are not because they occur within a transaction. + # Ensure the driver does not add the RetryableWriteError label to these errors. + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + - + object: *collection0 + name: find + arguments: + filter: {} + session: *session0 + expectError: *transient_label_only + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + session: *session0 + expectError: *transient_label_only + - + object: *collection0 + name: distinct + arguments: + fieldName: _id + filter: {} + session: *session0 + expectError: *transient_label_only + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: { } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: distinct + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + closeConnection: true + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'do not add RetryableWriteError label to writeConcernError ShutdownInProgress that occurs within transaction' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - UnknownTransactionCommitResult + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 64 # WriteConcernTimeout without wtimeout + errmsg: 'multiple errors reported' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - UnknownTransactionCommitResult + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'add UnknownTransactionCommitResult label to writeConcernError WriteConcernTimeout with wtimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 64 + errmsg: 'waiting for replication timed out' + errInfo: + wtimeout: true + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - UnknownTransactionCommitResult + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'omit UnknownTransactionCommitResult label from writeConcernError UnsatisfiableWriteConcern' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 100 # UnsatisfiableWriteConcern + errmsg: 'Not enough data-bearing nodes' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - UnknownTransactionCommitResult + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'omit UnknownTransactionCommitResult label from writeConcernError UnknownReplWriteConcern' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 79 # UnknownReplWriteConcern + errmsg: "No write concern mode named 'blah' found in replica set configuration" + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsOmit: + - RetryableWriteConcern + - TransientTransactionError + - UnknownTransactionCommitResult + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'do not add UnknownTransactionCommitResult label to MaxTimeMSExpired inside transactions' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - aggregate + errorCode: 50 # MaxTimeMSExpired + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + maxTimeMS: 60000 + session: *session0 + expectError: + errorLabelsOmit: + - RetryableWriteError + - UnknownTransactionCommitResult + - TransientTransactionError + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: { } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + autocommit: false + maxTimeMS: 60000 + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'add UnknownTransactionCommitResult label to MaxTimeMSExpired' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 50 # MaxTimeMSExpired + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + maxCommitTimeMS: 60000 + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - UnknownTransactionCommitResult + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'add UnknownTransactionCommitResult label to writeConcernError MaxTimeMSExpired' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 50 # MaxTimeMSExpired + errmsg: 'operation exceeded time limit' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + maxCommitTimeMS: 60000 + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsContain: + - UnknownTransactionCommitResult + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/errors-client.json b/src/test/spec/json/transactions/unified/errors-client.json new file mode 100644 index 000000000..00f1497c2 --- /dev/null +++ b/src/test/spec/json/transactions/unified/errors-client.json @@ -0,0 +1,142 @@ +{ + "description": "errors-client", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "Client side error in command starting transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "x": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "object": "testRunner", + "name": "assertSessionTransactionState", + "arguments": { + "session": "session0", + "state": "starting" + } + } + ] + }, + { + "description": "Client side error when transaction is in progress", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "x": 1 + } + }, + "expectError": { + "isError": true + } + }, + { + "object": "testRunner", + "name": "assertSessionTransactionState", + "arguments": { + "session": "session0", + "state": "in_progress" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/errors-client.yml b/src/test/spec/json/transactions/unified/errors-client.yml new file mode 100644 index 000000000..d3f451b98 --- /dev/null +++ b/src/test/spec/json/transactions/unified/errors-client.yml @@ -0,0 +1,96 @@ +description: errors-client + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'Client side error in command starting transaction' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: updateOne + arguments: + session: *session0 + filter: { _id: 1 } + update: + x: 1 + expectError: + isError: true + - + object: testRunner + name: assertSessionTransactionState + arguments: + session: *session0 + state: starting + - + description: 'Client side error when transaction is in progress' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: updateOne + arguments: + session: *session0 + filter: { _id: 1 } + update: + x: 1 + expectError: + isError: true + - + object: testRunner + name: assertSessionTransactionState + arguments: + session: *session0 + state: in_progress diff --git a/src/test/spec/json/transactions/unified/errors.json b/src/test/spec/json/transactions/unified/errors.json new file mode 100644 index 000000000..94a9cac20 --- /dev/null +++ b/src/test/spec/json/transactions/unified/errors.json @@ -0,0 +1,285 @@ +{ + "description": "errors", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "session": { + "id": "session1", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "start insert start", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "expectError": { + "isClientError": true, + "errorContains": "transaction already in progress" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ] + }, + { + "description": "start twice", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "startTransaction", + "expectError": { + "isClientError": true, + "errorContains": "transaction already in progress" + } + } + ] + }, + { + "description": "commit and start twice", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session0", + "name": "startTransaction", + "expectError": { + "isClientError": true, + "errorContains": "transaction already in progress" + } + } + ] + }, + { + "description": "write conflict commit", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorCodeName": "WriteConflict", + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorCodeName": "NoSuchTransaction", + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + } + ] + }, + { + "description": "write conflict abort", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorCodeName": "WriteConflict", + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "abortTransaction" + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/errors.yml b/src/test/spec/json/transactions/unified/errors.yml new file mode 100644 index 000000000..ce6ecfcff --- /dev/null +++ b/src/test/spec/json/transactions/unified/errors.yml @@ -0,0 +1,187 @@ +description: errors + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + - + session: + id: &session1 session1 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'start insert start' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: startTransaction + expectError: + isClientError: true + errorContains: 'transaction already in progress' + # Just to clean up + - + object: *session0 + name: commitTransaction + - + description: 'start twice' + operations: + - + object: *session0 + name: startTransaction + - + object: *session0 + name: startTransaction + expectError: + isClientError: true + errorContains: 'transaction already in progress' + - + description: 'commit and start twice' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: startTransaction + - + object: *session0 + name: startTransaction + expectError: + isClientError: true + errorContains: 'transaction already in progress' + - + description: 'write conflict commit' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectError: + errorCodeName: WriteConflict + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + - + object: *session0 + name: commitTransaction + - + object: *session1 + name: commitTransaction + expectError: + errorCodeName: NoSuchTransaction + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + - + description: 'write conflict abort' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectError: + errorCodeName: WriteConflict + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + - + object: *session0 + name: commitTransaction + # Driver ignores "NoSuchTransaction" error + - + object: *session1 + name: abortTransaction diff --git a/src/test/spec/json/transactions/unified/findOneAndDelete.json b/src/test/spec/json/transactions/unified/findOneAndDelete.json new file mode 100644 index 000000000..7db9c872a --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndDelete.json @@ -0,0 +1,317 @@ +{ + "description": "findOneAndDelete", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "findOneAndDelete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + } + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "collection0", + "name": "findOneAndDelete", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "remove": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 4 + }, + "remove": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "collection writeConcern ignored for findOneAndDelete", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection_wc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection_wc_majority", + "name": "findOneAndDelete", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + } + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "remove": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/findOneAndDelete.yml b/src/test/spec/json/transactions/unified/findOneAndDelete.yml new file mode 100644 index 000000000..446318970 --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndDelete.yml @@ -0,0 +1,183 @@ +description: findOneAndDelete + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + +tests: + - + description: findOneAndDelete + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: findOneAndDelete + arguments: + session: *session0 + filter: { _id: 3 } + expectResult: + _id: 3 + - + object: *collection0 + name: findOneAndDelete + arguments: + session: *session0 + filter: { _id: 4 } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + remove: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 4 } + remove: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - + description: 'collection writeConcern ignored for findOneAndDelete' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection_wc_majority + name: findOneAndDelete + arguments: + session: *session0 + filter: { _id: 3 } + expectResult: + _id: 3 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + remove: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/findOneAndReplace.json b/src/test/spec/json/transactions/unified/findOneAndReplace.json new file mode 100644 index 000000000..f0742f0c6 --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndReplace.json @@ -0,0 +1,356 @@ +{ + "description": "findOneAndReplace", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "findOneAndReplace", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "replacement": { + "x": 1 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "collection0", + "name": "findOneAndReplace", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + }, + "replacement": { + "x": 1 + }, + "upsert": true, + "returnDocument": "After" + }, + "expectResult": { + "_id": 4, + "x": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "x": 1 + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 4 + }, + "update": { + "x": 1 + }, + "new": true, + "upsert": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3, + "x": 1 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + }, + { + "description": "collection writeConcern ignored for findOneAndReplace", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection_wc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection_wc_majority", + "name": "findOneAndReplace", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "replacement": { + "x": 1 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "x": 1 + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/findOneAndReplace.yml b/src/test/spec/json/transactions/unified/findOneAndReplace.yml new file mode 100644 index 000000000..f1b79c958 --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndReplace.yml @@ -0,0 +1,202 @@ +description: findOneAndReplace + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + +tests: + - + description: findOneAndReplace + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: findOneAndReplace + arguments: + session: *session0 + filter: { _id: 3 } + replacement: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + - + object: *collection0 + name: findOneAndReplace + arguments: + session: *session0 + filter: { _id: 4 } + replacement: + x: 1 + upsert: true + returnDocument: After + expectResult: + _id: 4 + x: 1 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { x: 1 } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 4 } + update: { x: 1 } + new: true + upsert: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3, x: 1 } + - { _id: 4, x: 1 } + - + description: 'collection writeConcern ignored for findOneAndReplace' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection_wc_majority + name: findOneAndReplace + arguments: + session: *session0 + filter: { _id: 3 } + replacement: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { x: 1 } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/findOneAndUpdate.json b/src/test/spec/json/transactions/unified/findOneAndUpdate.json new file mode 100644 index 000000000..f5308efef --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndUpdate.json @@ -0,0 +1,546 @@ +{ + "description": "findOneAndUpdate", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "findOneAndUpdate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true, + "returnDocument": "After" + }, + "expectResult": { + "_id": 4, + "x": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3, + "x": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3, + "x": 2 + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": true, + "upsert": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3, + "x": 2 + }, + { + "_id": 4, + "x": 1 + } + ] + } + ] + }, + { + "description": "collection writeConcern ignored for findOneAndUpdate", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection_wc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection_wc_majority", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 3 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 3 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/findOneAndUpdate.yml b/src/test/spec/json/transactions/unified/findOneAndUpdate.yml new file mode 100644 index 000000000..49d68a209 --- /dev/null +++ b/src/test/spec/json/transactions/unified/findOneAndUpdate.yml @@ -0,0 +1,301 @@ +description: findOneAndUpdate + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + +tests: + - + description: findOneAndUpdate + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 3 } + update: + $inc: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + - + object: *collection0 + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 4 } + update: + $inc: + x: 1 + upsert: true + returnDocument: After + expectResult: + _id: 4 + x: 1 + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: startTransaction + # Test a second time to ensure txnNumber is incremented + - + object: *collection0 + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 3 } + update: + $inc: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + x: 1 + - + object: *session0 + name: commitTransaction + # Test a third time to ensure abort works + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 3 } + update: + $inc: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + x: 2 + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { $inc: { x: 1 } } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 4 } + update: { $inc: { x: 1 } } + new: true + upsert: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { $inc: { x: 1 } } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { $inc: { x: 1 } } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '3' } + startTransaction: true + autocommit: false + readConcern: + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '3' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3, x: 2 } + - { _id: 4, x: 1 } + - + description: 'collection writeConcern ignored for findOneAndUpdate' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection_wc_majority + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 3 } + update: + $inc: + x: 1 + returnDocument: Before + expectResult: + _id: 3 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 3 } + update: { $inc: { x: 1 } } + new: { $$unsetOrMatches: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: findAndModify + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/insert.json b/src/test/spec/json/transactions/unified/insert.json new file mode 100644 index 000000000..9a80d8bf4 --- /dev/null +++ b/src/test/spec/json/transactions/unified/insert.json @@ -0,0 +1,895 @@ +{ + "description": "insert", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "session": { + "id": "session1", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 5 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 5 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 5 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + ] + }, + { + "description": "insert with session1", + "operations": [ + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "session": "session1" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 4 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ] + }, + { + "description": "collection writeConcern without transaction", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection_wc_majority", + "database": "database1", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + }, + { + "session": { + "id": "session2", + "client": "client1" + } + } + ] + } + }, + { + "object": "collection_wc_majority", + "name": "insertOne", + "arguments": { + "session": "session2", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session2" + }, + "txnNumber": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "collection writeConcern ignored for insert", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection_wc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection_wc_majority", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection_wc_majority", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 2, + "1": 3 + } + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + }, + { + "_id": 3 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/insert.yml b/src/test/spec/json/transactions/unified/insert.yml new file mode 100644 index 000000000..1e9706971 --- /dev/null +++ b/src/test/spec/json/transactions/unified/insert.yml @@ -0,0 +1,485 @@ +description: insert + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + - + session: + id: &session1 session1 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: insert + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2 } + - { _id: 3 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 5 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 5 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + - { _id: 3 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 5 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + # This test proves that the driver uses "session1" correctly in operations + # and APM expectations + - + description: 'insert with session1' + operations: + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 2 } + - { _id: 3 } + session: *session1 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 4 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + - { _id: 3 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + # This test proves that the driver parses the collectionOptions writeConcern + - + description: 'collection writeConcern without transaction' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database1 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - session: + id: &session2 session2 + client: *client1 + - + object: *collection_wc_majority + name: insertOne + arguments: + session: *session2 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session2 } + txnNumber: { $$exists: false } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + writeConcern: + w: majority + commandName: insert + databaseName: *database_name + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'collection writeConcern ignored for insert' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection_wc_majority collection_wc_majority + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection_wc_majority + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection_wc_majority + name: insertMany + arguments: + documents: + - { _id: 2 } + - { _id: 3 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 2 + '1': 3 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + - { _id: 3 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } diff --git a/src/test/spec/json/transactions/unified/isolation.json b/src/test/spec/json/transactions/unified/isolation.json new file mode 100644 index 000000000..5d0a0139f --- /dev/null +++ b/src/test/spec/json/transactions/unified/isolation.json @@ -0,0 +1,281 @@ +{ + "description": "isolation", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "session": { + "id": "session1", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "one transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectResult": [] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [] + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1 + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "two transactions", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectResult": [] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [] + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectResult": [] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1 + } + ] + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/isolation.yml b/src/test/spec/json/transactions/unified/isolation.yml new file mode 100644 index 000000000..ff8b14f87 --- /dev/null +++ b/src/test/spec/json/transactions/unified/isolation.yml @@ -0,0 +1,174 @@ +# Test snapshot isolation. +# This test doesn't check contents of command-started events. +description: isolation + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + - + session: + id: &session1 session1 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'one transaction' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: find + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + - { _id: 1 } + - + object: *collection0 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectResult: [] + - + object: *collection0 + name: find + arguments: + filter: { _id: 1 } + expectResult: [] + - + object: *session0 + name: commitTransaction + - + object: *collection0 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectResult: + - { _id: 1 } + - + object: *collection0 + name: find + arguments: + filter: { _id: 1 } + expectResult: + - { _id: 1 } + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'two transactions' + operations: + - + object: *session0 + name: startTransaction + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection0 + name: find + arguments: + session: *session0 + filter: { _id: 1 } + expectResult: + - { _id: 1 } + - + object: *collection0 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectResult: [] + - + object: *collection0 + name: find + arguments: + filter: { _id: 1 } + expectResult: [] + - + object: *session0 + name: commitTransaction + # Snapshot isolation in session1, not read-committed. + - + object: *collection0 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectResult: [] + - + object: *collection0 + name: find + arguments: + filter: { _id: 1 } + expectResult: + - { _id: 1 } + - + object: *session1 + name: commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/mongos-pin-auto-tests.py b/src/test/spec/json/transactions/unified/mongos-pin-auto-tests.py new file mode 100644 index 000000000..09306780f --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-pin-auto-tests.py @@ -0,0 +1,353 @@ +import itertools +import sys + +# Require Python 3.7+ for ordered dictionaries so that the order of the +# generated tests remain the same. +# Usage: +# python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml +if sys.version_info[:2] < (3, 7): + print('ERROR: This script requires Python >= 3.7, not:') + print(sys.version) + print('Usage: python3.7 mongos-pin-auto-tests.py > mongos-pin-auto.yml') + exit(1) + +HEADER = '''# Autogenerated tests that transient errors in a transaction unpin the session. +# See mongos-pin-auto-tests.py + +description: mongos-pin-auto + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: "4.1.8" + # Note: tests utilize targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: "forbid" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + +tests: + - description: remain pinned after non-transient Interrupted error on insertOne + operations: + - &startTransaction + object: session0 + name: startTransaction + - &initialCommand + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + errorCode: 11601 + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + errorCodeName: Interrupted + - &assertSessionPinned + object: testRunner + name: assertSessionPinned + arguments: + session: *session0 + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: 'unpin after transient error within a transaction' + operations: + - *startTransaction + - *initialCommand + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + closeConnection: true + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + errorLabelsOmit: ["UnknownTransactionCommitResult"] + - &assertSessionUnpinned + object: testRunner + name: assertSessionUnpinned + arguments: + session: *session0 + - &abortTransaction + object: *session0 + name: abortTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collection_name + databaseName: *database_name + documents: *data + + # The rest of the tests in this file test every operation type against + # multiple types of transient errors (connection and error code).''' + +TEMPLATE = ''' + - description: {test_name} {error_name} error on {op_name} {command_name} + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {{times: 1}} + data: + failCommands: ["{command_name}"] + {error_data} + - name: {op_name} + object: {object_name} + arguments: + session: *session0 + {op_args} + expectError: + {error_labels}: ["TransientTransactionError"] + - *{assertion} + - *abortTransaction + outcome: *outcome +''' + + +# Maps from op_name to (command_name, object_name, op_args) +OPS = { + # Write ops: + 'insertOne': ('insert', '*collection0', r'document: { _id: 4 }'), + 'insertMany': ('insert', '*collection0', r'documents: [ { _id: 4 }, { _id: 5 } ]'), + 'updateOne': ('update', '*collection0', r'''filter: { _id: 1 } + update: { $inc: { x: 1 } }'''), + 'replaceOne': ('update', '*collection0', r'''filter: { _id: 1 } + replacement: { y: 1 }'''), + 'updateMany': ('update', '*collection0', r'''filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } }'''), + 'deleteOne': ('delete', '*collection0', r'filter: { _id: 1 }'), + 'deleteMany': ('delete', '*collection0', r'filter: { _id: { $gte: 1 } }'), + 'findOneAndDelete': ('findAndModify', '*collection0', r'filter: { _id: 1 }'), + 'findOneAndUpdate': ('findAndModify', '*collection0', r'''filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before'''), + 'findOneAndReplace': ('findAndModify', '*collection0', r'''filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before'''), + # Bulk write insert/update/delete: + 'bulkWrite insert': ('insert', '*collection0', r'''requests: + - insertOne: + document: { _id: 1 }'''), + 'bulkWrite update': ('update', '*collection0', r'''requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } }'''), + 'bulkWrite delete': ('delete', '*collection0', r'''requests: + - deleteOne: + filter: { _id: 1 }'''), + # Read ops: + 'find': ('find', '*collection0', r'filter: { _id: 1 }'), + 'countDocuments': ('aggregate', '*collection0', r'filter: {}'), + 'aggregate': ('aggregate', '*collection0', r'pipeline: []'), + 'distinct': ('distinct', '*collection0', r'''fieldName: _id + filter: {}'''), + # runCommand: + 'runCommand': ('insert', '*database0', r'''commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 }'''), + # clientBulkWrite: + 'clientBulkWrite': ('bulkWrite', '*client0', r'''models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 }'''), +} + +# Maps from error_name to error_data. +NON_TRANSIENT_ERRORS = { + 'Interrupted': 'errorCode: 11601', +} + +# Maps from error_name to error_data. +TRANSIENT_ERRORS = { + 'connection': 'closeConnection: true', + 'ShutdownInProgress': 'errorCode: 91', +} + + +def create_pin_test(op_name, error_name): + test_name = 'remain pinned after non-transient' + assertion = 'assertSessionPinned' + error_labels = 'errorLabelsOmit' + command_name, object_name, op_args = OPS[op_name] + error_data = NON_TRANSIENT_ERRORS[error_name] + if op_name.startswith('bulkWrite'): + op_name = 'bulkWrite' + test = TEMPLATE.format(**locals()) + if op_name == 'clientBulkWrite': + test += ' runOnRequirements:\n' + test += ' - minServerVersion: "8.0" # `bulkWrite` added to server 8.0"\n' + test += ' serverless: forbid\n' + return test + + +def create_unpin_test(op_name, error_name): + test_name = 'unpin after transient' + assertion = 'assertSessionUnpinned' + error_labels = 'errorLabelsContain' + command_name, object_name, op_args = OPS[op_name] + error_data = TRANSIENT_ERRORS[error_name] + if op_name.startswith('bulkWrite'): + op_name = 'bulkWrite' + test = TEMPLATE.format(**locals()) + if op_name == 'clientBulkWrite': + test += ' runOnRequirements:\n' + test += ' - minServerVersion: "8.0" # `bulkWrite` added to server 8.0"\n' + test += ' serverless: forbid\n' + return test + + + +tests = [] +for op_name, error_name in itertools.product(OPS, NON_TRANSIENT_ERRORS): + tests.append(create_pin_test(op_name, error_name)) +for op_name, error_name in itertools.product(OPS, TRANSIENT_ERRORS): + tests.append(create_unpin_test(op_name, error_name)) + +print(HEADER) +print(''.join(tests)) diff --git a/src/test/spec/json/transactions/unified/mongos-pin-auto.json b/src/test/spec/json/transactions/unified/mongos-pin-auto.json new file mode 100644 index 000000000..27db52040 --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-pin-auto.json @@ -0,0 +1,5474 @@ +{ + "description": "mongos-pin-auto", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded" + ], + "serverless": "forbid" + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "remain pinned after non-transient Interrupted error on insertOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError", + "UnknownTransactionCommitResult" + ], + "errorCodeName": "Interrupted" + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ] + }, + { + "description": "unpin after transient error within a transaction", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 11601 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient connection error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on insertOne insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on insertMany insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "insertMany", + "object": "collection0", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on updateOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "updateOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on replaceOne update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "replaceOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on updateMany update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "updateMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on deleteOne delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "deleteOne", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on deleteMany delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "deleteMany", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 1 + } + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndDelete", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndUpdate", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "closeConnection": true + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "findAndModify" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "findOneAndReplace", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + }, + "replacement": { + "y": 1 + }, + "returnDocument": "Before" + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "update" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "updateOne": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 1 + } + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "closeConnection": true + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on bulkWrite delete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "delete" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "bulkWrite", + "object": "collection0", + "arguments": { + "session": "session0", + "requests": [ + { + "deleteOne": { + "filter": { + "_id": 1 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "closeConnection": true + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on find find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "find" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on countDocuments aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "countDocuments", + "object": "collection0", + "arguments": { + "session": "session0", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "closeConnection": true + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on aggregate aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "aggregate" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "aggregate", + "object": "collection0", + "arguments": { + "session": "session0", + "pipeline": [] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "closeConnection": true + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on distinct distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "distinct" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "distinct", + "object": "collection0", + "arguments": { + "session": "session0", + "fieldName": "_id", + "filter": {} + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on runCommand insert", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "session": "session0", + "commandName": "insert", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient connection error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "closeConnection": true + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + }, + { + "description": "unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "name": "targetedFailPoint", + "object": "testRunner", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "bulkWrite" + ], + "errorCode": 91 + } + } + } + }, + { + "name": "clientBulkWrite", + "object": "client0", + "arguments": { + "session": "session0", + "models": [ + { + "insertOne": { + "namespace": "database0.collection0", + "document": { + "_id": 8, + "x": 88 + } + } + } + ] + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "testRunner", + "name": "assertSessionUnpinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "runOnRequirements": [ + { + "minServerVersion": "8.0" + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/mongos-pin-auto.yml b/src/test/spec/json/transactions/unified/mongos-pin-auto.yml new file mode 100644 index 000000000..a80dd6203 --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-pin-auto.yml @@ -0,0 +1,1706 @@ +# Autogenerated tests that transient errors in a transaction unpin the session. +# See mongos-pin-auto-tests.py + +description: mongos-pin-auto + +schemaVersion: '1.4' + +runOnRequirements: + - minServerVersion: "4.1.8" + # Note: tests utilize targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: "forbid" + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - session: + id: &session0 session0 + client: *client0 + +initialData: + - collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + +tests: + - description: remain pinned after non-transient Interrupted error on insertOne + operations: + - &startTransaction + object: session0 + name: startTransaction + - &initialCommand + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: { $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } } + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + errorCode: 11601 + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError", "UnknownTransactionCommitResult"] + errorCodeName: Interrupted + - &assertSessionPinned + object: testRunner + name: assertSessionPinned + arguments: + session: *session0 + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + + - description: 'unpin after transient error within a transaction' + operations: + - *startTransaction + - *initialCommand + - object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ "insert" ] + closeConnection: true + - object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + errorLabelsOmit: ["UnknownTransactionCommitResult"] + - &assertSessionUnpinned + object: testRunner + name: assertSessionUnpinned + arguments: + session: *session0 + - &abortTransaction + object: *session0 + name: abortTransaction + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: &outcome + - collectionName: *collection_name + databaseName: *database_name + documents: *data + + # The rest of the tests in this file test every operation type against + # multiple types of transient errors (connection and error code). + + - description: remain pinned after non-transient Interrupted error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 11601 + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 11601 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + errorCode: 11601 + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 11601 + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 11601 + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + errorCode: 11601 + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 11601 + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + + - description: remain pinned after non-transient Interrupted error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 11601 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsOmit: ["TransientTransactionError"] + - *assertSessionPinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + + - description: unpin after transient connection error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on insertOne insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: insertOne + object: *collection0 + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on insertMany insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: insertMany + object: *collection0 + arguments: + session: *session0 + documents: [ { _id: 4 }, { _id: 5 } ] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on updateOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: updateOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on replaceOne update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: replaceOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on updateMany update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: updateMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + update: {$set: { z: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on deleteOne delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: deleteOne + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on deleteMany delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: deleteMany + object: *collection0 + arguments: + session: *session0 + filter: { _id: { $gte: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndDelete findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndDelete + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndUpdate findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndUpdate + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + closeConnection: true + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on findOneAndReplace findAndModify + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["findAndModify"] + errorCode: 91 + - name: findOneAndReplace + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + replacement: { y: 1 } + returnDocument: Before + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - insertOne: + document: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite update + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["update"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - updateOne: + filter: { _id: 1 } + update: { $set: { x: 1 } } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + closeConnection: true + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on bulkWrite delete + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["delete"] + errorCode: 91 + - name: bulkWrite + object: *collection0 + arguments: + session: *session0 + requests: + - deleteOne: + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + closeConnection: true + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on find find + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["find"] + errorCode: 91 + - name: find + object: *collection0 + arguments: + session: *session0 + filter: { _id: 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + closeConnection: true + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on countDocuments aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 91 + - name: countDocuments + object: *collection0 + arguments: + session: *session0 + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + closeConnection: true + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on aggregate aggregate + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["aggregate"] + errorCode: 91 + - name: aggregate + object: *collection0 + arguments: + session: *session0 + pipeline: [] + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + closeConnection: true + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on distinct distinct + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["distinct"] + errorCode: 91 + - name: distinct + object: *collection0 + arguments: + session: *session0 + fieldName: _id + filter: {} + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + closeConnection: true + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient ShutdownInProgress error on runCommand insert + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["insert"] + errorCode: 91 + - name: runCommand + object: *database0 + arguments: + session: *session0 + commandName: insert + command: + insert: *collection_name + documents: + - { _id : 1 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + + - description: unpin after transient connection error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + closeConnection: true + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + + - description: unpin after transient ShutdownInProgress error on clientBulkWrite bulkWrite + operations: + - *startTransaction + - *initialCommand + - name: targetedFailPoint + object: testRunner + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: {times: 1} + data: + failCommands: ["bulkWrite"] + errorCode: 91 + - name: clientBulkWrite + object: *client0 + arguments: + session: *session0 + models: + - insertOne: + namespace: database0.collection0 + document: { _id: 8, x: 88 } + expectError: + errorLabelsContain: ["TransientTransactionError"] + - *assertSessionUnpinned + - *abortTransaction + outcome: *outcome + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0" + diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.json b/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.json new file mode 100644 index 000000000..13345c6a2 --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.json @@ -0,0 +1,211 @@ +{ + "description": "mongos-recovery-token-errorLabels", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "serverless": "forbid", + "topologies": [ + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction retry succeeds on new mongos", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.yml b/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.yml new file mode 100644 index 000000000..e93c89b6d --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token-errorLabels.yml @@ -0,0 +1,136 @@ +description: mongos-recovery-token-errorLabels + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + # serverless proxy doesn't use recovery tokens + serverless: forbid + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'commitTransaction retry succeeds on new mongos' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # Enable the fail point only on the mongos that session0 is pinned to + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + # The client sees a retryable writeConcernError on the first + # commitTransaction due to the fail point but it actually succeeds on the + # server (SERVER-39346). The retry will succeed both on a new mongos and + # on the original. + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token.json b/src/test/spec/json/transactions/unified/mongos-recovery-token.json new file mode 100644 index 000000000..bb88aa16b --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token.json @@ -0,0 +1,568 @@ +{ + "description": "mongos-recovery-token", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.1.8", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction explicit retries include recoveryToken", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction retry fails on new mongos", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": true, + "uriOptions": { + "heartbeatFrequencyMS": 30000, + "appName": "transactionsClient" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 7 + }, + "data": { + "failCommands": [ + "commitTransaction", + "isMaster", + "hello" + ], + "closeConnection": true, + "appName": "transactionsClient" + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ], + "errorCodeName": "NoSuchTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction sends recoveryToken", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/mongos-recovery-token.yml b/src/test/spec/json/transactions/unified/mongos-recovery-token.yml new file mode 100644 index 000000000..9eed826ec --- /dev/null +++ b/src/test/spec/json/transactions/unified/mongos-recovery-token.yml @@ -0,0 +1,353 @@ +description: mongos-recovery-token + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: 4.1.8 + # serverless proxy doesn't use recovery tokens + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'commitTransaction explicit retries include recoveryToken' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: commitTransaction + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction retry fails on new mongos' + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + runOnRequirements: + - topologies: [ sharded ] + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: true + uriOptions: + # Increase heartbeatFrequencyMS to avoid the race condition where an in + # flight heartbeat refreshes the first mongoes' SDAM state in between + # the initial commitTransaction and the retry attempt. + heartbeatFrequencyMS: 30000 + appName: &appName transactionsClient + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # Enable the fail point only on the Mongos that session0 is pinned to. + # Fail hello/legacy hello to prevent the heartbeat requested directly after the + # retryable commit error from racing with server selection for the retry. + # Note: times: 7 is slightly artbitrary but it accounts for one failed + # commit and some SDAM heartbeats. A test runner will have multiple + # clients connected to this server so this fail point configuration + # is also racy. + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session1 + failPoint: + configureFailPoint: failCommand + mode: { times: 7 } + data: + failCommands: + - commitTransaction + - isMaster + - hello + closeConnection: true + appName: *appName + # The first commitTransaction sees a retryable connection error due to + # the fail point and also fails on the server. The retry attempt on a + # new mongos will wait for the transaction to timeout and will fail + # because the transaction was aborted. Note that the retry attempt should + # not select the original mongos because that server's SDAM state is + # reset by the connection error, heartbeatFrequencyMS is high, and + # subsequent heartbeats should fail. + - + object: *session1 + name: commitTransaction + expectError: + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + errorCodeName: NoSuchTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction sends recoveryToken' + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + runOnRequirements: + - topologies: [ sharded ] + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # Enable the fail point only on the mongos that session0 is pinned to + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + closeConnection: true + # The first abortTransaction sees a retryable connection error due to + # the fail point. The retry attempt on a new mongos will send the + # recoveryToken. Note that the retry attempt will also fail because the + # server does not yet support aborting from a new mongos, however this + # operation should "succeed" since abortTransaction ignores errors. + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] diff --git a/src/test/spec/json/transactions/unified/mongos-unpin.json b/src/test/spec/json/transactions/unified/mongos-unpin.json index 356f4fd9b..4d1ebc87b 100644 --- a/src/test/spec/json/transactions/unified/mongos-unpin.json +++ b/src/test/spec/json/transactions/unified/mongos-unpin.json @@ -52,7 +52,10 @@ "description": "unpin after TransientTransactionError error on commit", "runOnRequirements": [ { - "serverless": "forbid" + "serverless": "forbid", + "topologies": [ + "sharded" + ] } ], "operations": [ @@ -163,7 +166,10 @@ "description": "unpin after non-transient error on abort", "runOnRequirements": [ { - "serverless": "forbid" + "serverless": "forbid", + "topologies": [ + "sharded" + ] } ], "operations": [ @@ -233,6 +239,13 @@ }, { "description": "unpin after TransientTransactionError error on abort", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], "operations": [ { "name": "startTransaction", diff --git a/src/test/spec/json/transactions/unified/mongos-unpin.yml b/src/test/spec/json/transactions/unified/mongos-unpin.yml index 5f7e78e5e..44844a185 100644 --- a/src/test/spec/json/transactions/unified/mongos-unpin.yml +++ b/src/test/spec/json/transactions/unified/mongos-unpin.yml @@ -35,9 +35,13 @@ _yamlAnchors: tests: - description: unpin after TransientTransactionError error on commit runOnRequirements: + - # serverless proxy doesn't append error labels to errors in transactions # caused by failpoints (CLOUDP-88216) - - serverless: "forbid" + serverless: "forbid" + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] operations: - &startTransaction name: startTransaction @@ -87,9 +91,13 @@ tests: - description: unpin after non-transient error on abort runOnRequirements: + - # serverless proxy doesn't append error labels to errors in transactions # caused by failpoints (CLOUDP-88216) - - serverless: "forbid" + serverless: "forbid" + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] operations: - *startTransaction - *insertOne @@ -112,6 +120,11 @@ tests: - *abortTransaction - description: unpin after TransientTransactionError error on abort + runOnRequirements: + - + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + topologies: [ sharded ] operations: - *startTransaction - *insertOne diff --git a/src/test/spec/json/transactions/unified/pin-mongos.json b/src/test/spec/json/transactions/unified/pin-mongos.json new file mode 100644 index 000000000..c96f3f341 --- /dev/null +++ b/src/test/spec/json/transactions/unified/pin-mongos.json @@ -0,0 +1,1466 @@ +{ + "description": "pin-mongos", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.1.8", + "serverless": "forbid", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": true, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ], + "tests": [ + { + "description": "countDocuments", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2 + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "filter": { + "_id": 2 + }, + "session": "session0" + }, + "expectResult": [ + { + "_id": 2 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "insertOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 4 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 5 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 5 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 6 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 7 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 8 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 8 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 9 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 9 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 10 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 10 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + }, + { + "_id": 6 + }, + { + "_id": 7 + }, + { + "_id": 8 + }, + { + "_id": 9 + }, + { + "_id": 10 + } + ] + } + ] + }, + { + "description": "mixed read write operations", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": 3 + }, + "session": "session0" + }, + "expectResult": 1 + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 4 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 4 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 5 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 5 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 6 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 6 + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "document": { + "_id": 7 + }, + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 7 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + }, + { + "_id": 5 + }, + { + "_id": 6 + }, + { + "_id": 7 + } + ] + } + ] + }, + { + "description": "multiple commits", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 3, + "1": 4 + } + } + } + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "remain pinned after non-transient error on commit", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 3, + "1": 4 + } + } + } + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 51 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "TransientTransactionError" + ], + "errorCode": 51 + } + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "testRunner", + "name": "assertSessionPinned", + "arguments": { + "session": "session0" + } + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "unpin after transient error within a transaction", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unpin after transient error within a transaction and commit", + "runOnRequirements": [ + { + "topologies": [ + "sharded" + ] + } + ], + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": true, + "uriOptions": { + "heartbeatFrequencyMS": 30000, + "appName": "transactionsClient" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "testRunner", + "name": "targetedFailPoint", + "arguments": { + "session": "session1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 7 + }, + "data": { + "failCommands": [ + "insert", + "isMaster", + "hello" + ], + "closeConnection": true, + "appName": "transactionsClient" + } + } + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 4 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ] + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ], + "errorLabelsOmit": [ + "UnknownTransactionCommitResult" + ], + "errorCodeName": "NoSuchTransaction" + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + }, + "recoveryToken": { + "$$exists": true + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/pin-mongos.yml b/src/test/spec/json/transactions/unified/pin-mongos.yml new file mode 100644 index 000000000..4869ac3c1 --- /dev/null +++ b/src/test/spec/json/transactions/unified/pin-mongos.yml @@ -0,0 +1,660 @@ +# Test that all the operations go to the same mongos. +# +# In tests that don't include command-started events the assertion is implicit: +# that all the read operations succeed. If the driver does not properly pin to +# a single mongos then one of the operations in a transaction will eventually +# be sent to a different mongos, which is unaware of the transaction, and the +# mongos will return a command error. An example of such an error is: +# { +# 'ok': 0.0, +# 'errmsg': 'cannot continue txnId -1 for session 28938f50-9d29-4ca5-8de5-ddaf261267c4 - 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= with txnId 1', +# 'code': 251, +# 'codeName': 'NoSuchTransaction', +# 'errorLabels': ['TransientTransactionError'] +# } +description: pin-mongos + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: 4.1.8 + # serverless proxy doesn't append error labels to errors in transactions + # caused by failpoints (CLOUDP-88216) + serverless: forbid + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: true + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + +tests: + - + description: countDocuments + operations: + - &startTransaction + object: *session0 + name: startTransaction + - &countDocuments + object: *collection0 + name: countDocuments + arguments: + filter: { _id: 2 } + session: *session0 + expectResult: 1 + - *countDocuments + - *countDocuments + - *countDocuments + - *countDocuments + - *countDocuments + - *countDocuments + - *countDocuments + - &commitTransaction + object: *session0 + name: commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: distinct + operations: + - *startTransaction + - &distinct + object: *collection0 + name: distinct + arguments: + fieldName: _id + filter: {} + session: *session0 + expectResult: [ 1, 2 ] + - *distinct + - *distinct + - *distinct + - *distinct + - *distinct + - *distinct + - *distinct + - *commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: find + operations: + - + object: *session0 + name: startTransaction + - &find + object: *collection0 + name: find + arguments: + filter: { _id: 2 } + session: *session0 + expectResult: + - { _id: 2 } + - *find + - *find + - *find + - *find + - *find + - *find + - *find + - *commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: insertOne + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 5 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 5 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 6 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 6 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 7 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 7 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 8 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 8 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 9 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 9 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 10 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 10 } } + - *commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + - { _id: 6 } + - { _id: 7 } + - { _id: 8 } + - { _id: 9 } + - { _id: 10 } + - + description: 'mixed read write operations' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 3 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + - &countDocuments + object: *collection0 + name: countDocuments + arguments: + filter: { _id: 3 } + session: *session0 + expectResult: 1 + - *countDocuments + - *countDocuments + - *countDocuments + - *countDocuments + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 4 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 5 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 5 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 6 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 6 } } + - + object: *collection0 + name: insertOne + arguments: + document: { _id: 7 } + session: *session0 + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 7 } } + - *commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - { _id: 5 } + - { _id: 6 } + - { _id: 7 } + - + description: 'multiple commits' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 3 + '1': 4 + # Session is pinned and remains pinned after successful commits + - &assertSessionPinned + object: testRunner + name: assertSessionPinned + arguments: + session: *session0 + - *commitTransaction + - *assertSessionPinned + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *commitTransaction + - *assertSessionPinned + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - + description: 'remain pinned after non-transient error on commit' + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + runOnRequirements: + - topologies: [ sharded ] + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 3 + '1': 4 + - *assertSessionPinned + # Fail the commit with a non-transient error + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 51 # ManualInterventionRequired + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsOmit: + - TransientTransactionError + errorCode: 51 + - *assertSessionPinned + # The next commit should succeed + - + object: *session0 + name: commitTransaction + - *assertSessionPinned + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + - + description: 'unpin after transient error within a transaction' + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + runOnRequirements: + - topologies: [ sharded ] + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + # Enable the fail point only on the mongos that session0 is pinned to + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + closeConnection: true + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 4 } + expectError: + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + # Session unpins from the first mongos after the insert error and + # abortTransaction succeeds immediately on any mongos. + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + # Applications should not run commitTransaction after transient errors but + # the transactions API allows it and this test confirms unpinning behavior. + # In a sharded cluster, a transient error within a transaction unpins the + # session. This way a subsequent abort can "succeed" immediately instead of + # blocking for serverSelectionTimeoutMS in the case the mongos went down. + # However since the abortTransaction helper ignores errors, this test uses + # commitTransaction to prove the session was unpinned. + - + description: 'unpin after transient error within a transaction and commit' + # Note: test utilizes targetedFailPoint, which is incompatible with + # load-balanced and useMultipleMongoses:true + runOnRequirements: + - topologies: [ sharded ] + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: true + uriOptions: + # Increase heartbeatFrequencyMS to avoid the race condition where an in + # flight heartbeat refreshes the first mongoes' SDAM state in between + # the insert connection error and the single commit attempt. + heartbeatFrequencyMS: 30000 + appName: &appName transactionsClient + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 3 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + # Enable the fail point only on the Mongos that session0 is pinned to. + # Fail hello/legacy hello to prevent the heartbeat requested directly after the + # insert error from racing with server selection for the commit. + # Note: times: 7 is slightly artbitrary but it accounts for one failed + # insert and some SDAM heartbeats. A test runner will have multiple + # clients connected to this server so this fail point configuration + # is also racy. + - + object: testRunner + name: targetedFailPoint + arguments: + session: *session1 + failPoint: + configureFailPoint: failCommand + mode: { times: 7 } + data: + failCommands: + - insert + - isMaster + - hello + closeConnection: true + appName: *appName + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 4 } + expectError: + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + # Session unpins from the first mongos after the insert error and + # commitTransaction selects the second mongos which is unaware of the + # transaction and therefore fails with NoSuchTransaction error. If this + # commit succeeds it indicates a bug, either: + # - the driver mistakenly remained pinned even after the insert error, or + # - the test client was initialized with a single mongos seed + # + # Note that the commit attempt should not select the original mongos + # because that server's SDAM state is reset by the connection error, + # heartbeatFrequencyMS is high, and subsequent heartbeats + # should fail. + - + object: *session1 + name: commitTransaction + expectError: + errorLabelsContain: + - TransientTransactionError + errorLabelsOmit: + - UnknownTransactionCommitResult + errorCodeName: NoSuchTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + recoveryToken: { $$exists: true } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } diff --git a/src/test/spec/json/transactions/unified/read-concern.json b/src/test/spec/json/transactions/unified/read-concern.json new file mode 100644 index 000000000..b3bd967c0 --- /dev/null +++ b/src/test/spec/json/transactions/unified/read-concern.json @@ -0,0 +1,1924 @@ +{ + "description": "read-concern", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "database": { + "id": "database_rc_majority", + "client": "client0", + "databaseName": "transaction-tests", + "databaseOptions": { + "readConcern": { + "level": "majority" + } + } + } + }, + { + "collection": { + "id": "collection_rc_majority", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "readConcern": { + "level": "majority" + } + } + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ], + "tests": [ + { + "description": "only first countDocuments includes readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + } + } + }, + { + "object": "collection_rc_majority", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gte": 2 + } + }, + "session": "session0" + }, + "expectResult": 3 + }, + { + "object": "collection_rc_majority", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gte": 2 + } + }, + "session": "session0" + }, + "expectResult": 3 + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": { + "$gte": 2 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "cursor": {}, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "level": "majority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": { + "$gte": 2 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "cursor": {}, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "only first find includes readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + } + } + }, + { + "object": "collection_rc_majority", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rc_majority", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "level": "majority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "only first aggregate includes readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + } + } + }, + { + "object": "collection_rc_majority", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rc_majority", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "level": "majority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "only first distinct includes readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + } + } + }, + { + "object": "collection_rc_majority", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2, + 3, + 4 + ] + }, + { + "object": "collection_rc_majority", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2, + 3, + 4 + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "level": "majority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "only first runCommand includes readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + } + } + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "commandName": "find" + } + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "commandName": "find" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "level": "majority" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "countDocuments ignores collection readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_rc_majority", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gte": 2 + } + }, + "session": "session0" + }, + "expectResult": 3 + }, + { + "object": "collection_rc_majority", + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gte": 2 + } + }, + "session": "session0" + }, + "expectResult": 3 + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": { + "$gte": 2 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "cursor": {}, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$match": { + "_id": { + "$gte": 2 + } + } + }, + { + "$group": { + "_id": 1, + "n": { + "$sum": 1 + } + } + } + ], + "cursor": {}, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "find ignores collection readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_rc_majority", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rc_majority", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "aggregate ignores collection readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_rc_majority", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rc_majority", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "distinct ignores collection readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_rc_majority", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2, + 3, + 4 + ] + }, + { + "object": "collection_rc_majority", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2, + 3, + 4 + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "runCommand ignores database readConcern", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database_rc_majority", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "commandName": "find" + } + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "commandName": "find" + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/read-concern.yml b/src/test/spec/json/transactions/unified/read-concern.yml new file mode 100644 index 000000000..3eff32a0d --- /dev/null +++ b/src/test/spec/json/transactions/unified/read-concern.yml @@ -0,0 +1,634 @@ +description: read-concern + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + database: + id: &database_rc_majority database_rc_majority + client: *client0 + databaseName: &database_name transaction-tests + databaseOptions: + readConcern: { level: majority } + - + collection: + id: &collection_rc_majority collection_rc_majority + database: *database0 + collectionName: &collection_name test + collectionOptions: + readConcern: { level: majority } + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + +tests: + - + description: 'only first countDocuments includes readConcern' + operations: + - &startTransaction + object: *session0 + name: startTransaction + arguments: + readConcern: + level: majority + - &countDocuments + object: *collection_rc_majority + name: countDocuments + arguments: + filter: { _id: { $gte: 2 } } + session: *session0 + expectResult: 3 + - *countDocuments + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $match: { _id: { $gte: 2 } } } + - { $group: { _id: 1, n: { $sum: 1 } } } + cursor: { } + lsid: { $$sessionLsid: *session0 } + readConcern: + level: majority + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $match: { _id: { $gte: 2 } } } + - { $group: { _id: 1, n: { $sum: 1 } } } + cursor: { } + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - &commitTransactionEvent + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: &outcome + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: 'only first find includes readConcern' + operations: + - *startTransaction + - &find + object: *collection_rc_majority + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectResult: *data + - *find + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: + level: majority + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - *commitTransactionEvent + outcome: &outcome + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: 'only first aggregate includes readConcern' + operations: + - *startTransaction + - &aggregate + object: *collection_rc_majority + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectResult: *data + - *aggregate + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: + level: majority + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'only first distinct includes readConcern' + operations: + - *startTransaction + - &distinct + object: *collection_rc_majority + name: distinct + arguments: + fieldName: _id + filter: {} + session: *session0 + expectResult: [ 1, 2, 3, 4 ] + - *distinct + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: + level: majority + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: distinct + databaseName: *database_name + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: distinct + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'only first runCommand includes readConcern' + operations: + - *startTransaction + - &runCommand + object: *database0 + name: runCommand + arguments: + session: *session0 + command: + find: *collection_name + commandName: find + - *runCommand + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + lsid: { $$sessionLsid: *session0 } + readConcern: + level: majority + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: find + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'countDocuments ignores collection readConcern' + operations: + - &startTransactionNoReadConcern + object: *session0 + name: startTransaction + - *countDocuments + - *countDocuments + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $match: { _id: { $gte: 2 } } } + - { $group: { _id: 1, n: { $sum: 1 } } } + cursor: { } + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $match: { _id: { $gte: 2 } } } + - { $group: { _id: 1, n: { $sum: 1 } } } + cursor: { } + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'find ignores collection readConcern' + operations: + - *startTransactionNoReadConcern + - *find + - *find + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'aggregate ignores collection readConcern' + operations: + - *startTransactionNoReadConcern + - *aggregate + - *aggregate + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'distinct ignores collection readConcern' + operations: + - *startTransactionNoReadConcern + - *distinct + - *distinct + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: distinct + databaseName: *database_name + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: distinct + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome + - + description: 'runCommand ignores database readConcern' + operations: + - *startTransactionNoReadConcern + - + object: *database_rc_majority + name: runCommand + arguments: + session: *session0 + command: + find: *collection_name + commandName: find + - *runCommand + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: find + databaseName: *database_name + - *commitTransactionEvent + outcome: *outcome \ No newline at end of file diff --git a/src/test/spec/json/transactions/unified/read-pref.json b/src/test/spec/json/transactions/unified/read-pref.json new file mode 100644 index 000000000..eda00bd10 --- /dev/null +++ b/src/test/spec/json/transactions/unified/read-pref.json @@ -0,0 +1,728 @@ +{ + "description": "read-pref", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "collection": { + "id": "collection_rp_primary", + "database": "database0", + "collectionName": "test" + } + }, + { + "collection": { + "id": "collection_rp_secondary", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "default readPreference", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2, + "2": 3, + "3": 4 + } + } + } + } + }, + { + "object": "collection_rp_secondary", + "name": "aggregate", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + }, + { + "$count": "count" + } + ] + }, + "expectResult": [ + { + "count": 1 + } + ] + }, + { + "object": "collection_rp_secondary", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rp_secondary", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "primary readPreference", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "primary" + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2, + "2": 3, + "3": 4 + } + } + } + } + }, + { + "object": "collection_rp_secondary", + "name": "aggregate", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + }, + { + "$count": "count" + } + ] + }, + "expectResult": [ + { + "count": 1 + } + ] + }, + { + "object": "collection_rp_secondary", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection_rp_secondary", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "secondary readPreference", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "secondary" + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2, + "2": 3, + "3": 4 + } + } + } + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + }, + { + "$count": "count" + } + ] + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "primaryPreferred readPreference", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "primaryPreferred" + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2, + "2": 3, + "3": 4 + } + } + } + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + }, + { + "$count": "count" + } + ] + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "nearest readPreference", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "nearest" + } + } + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2, + "2": 3, + "3": 4 + } + } + } + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "session": "session0", + "pipeline": [ + { + "$match": { + "_id": 1 + } + }, + { + "$count": "count" + } + ] + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "collection_rp_primary", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "secondary write only", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "secondary" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/read-pref.yml b/src/test/spec/json/transactions/unified/read-pref.yml new file mode 100644 index 000000000..5217fea47 --- /dev/null +++ b/src/test/spec/json/transactions/unified/read-pref.yml @@ -0,0 +1,394 @@ +# This test doesn't check contents of command-started events. +description: read-pref + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + collection: + id: &collection_rp_primary collection_rp_primary + database: *database0 + collectionName: *collection_name + - + collection: + id: &collection_rp_secondary collection_rp_secondary + database: *database0 + collectionName: *collection_name + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'default readPreference' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertMany + arguments: + documents: &insertedDocs + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + '2': 3 + '3': 4 + - + object: *collection_rp_secondary + name: aggregate + arguments: + session: *session0 + pipeline: + - { $match: { _id: 1 } } + - { $count: count } + expectResult: + - { count: 1 } + - + object: *collection_rp_secondary + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectResult: *insertedDocs + - + object: *collection_rp_secondary + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectResult: *insertedDocs + - + object: *session0 + name: commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *insertedDocs + - + description: 'primary readPreference' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: primary + - + object: *collection0 + name: insertMany + arguments: + documents: &insertedDocs + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + '2': 3 + '3': 4 + - + object: *collection_rp_secondary + name: aggregate + arguments: + session: *session0 + pipeline: + - { $match: { _id: 1 } } + - { $count: count } + expectResult: + - { count: 1 } + - + object: *collection_rp_secondary + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectResult: *insertedDocs + - + object: *collection_rp_secondary + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectResult: *insertedDocs + - + object: *session0 + name: commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *insertedDocs + - + description: 'secondary readPreference' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: secondary + - + object: *collection0 + name: insertMany + arguments: + documents: &insertedDocs + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + '2': 3 + '3': 4 + - + object: *collection_rp_primary + name: aggregate + arguments: + session: *session0 + pipeline: + - { $match: { _id: 1 } } + - { $count: count } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session0 + name: abortTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'primaryPreferred readPreference' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: primaryPreferred + - + object: *collection0 + name: insertMany + arguments: + documents: &insertedDocs + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + '2': 3 + '3': 4 + - + object: *collection_rp_primary + name: aggregate + arguments: + session: *session0 + pipeline: + - { $match: { _id: 1 } } + - { $count: count } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session0 + name: abortTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'nearest readPreference' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: nearest + - + object: *collection0 + name: insertMany + arguments: + documents: &insertedDocs + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + '2': 3 + '3': 4 + - + object: *collection_rp_primary + name: aggregate + arguments: + session: *session0 + pipeline: + - { $match: { _id: 1 } } + - { $count: count } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *collection_rp_primary + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session0 + name: abortTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'secondary write only' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: secondary + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/reads.json b/src/test/spec/json/transactions/unified/reads.json new file mode 100644 index 000000000..52e845763 --- /dev/null +++ b/src/test/spec/json/transactions/unified/reads.json @@ -0,0 +1,706 @@ +{ + "description": "reads", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ], + "tests": [ + { + "description": "collection readConcern without transaction", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "readConcern": { + "level": "majority" + } + } + } + } + ] + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "readConcern": { + "level": "majority" + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$$exists": false + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + } + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "find", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection0", + "name": "find", + "arguments": { + "batchSize": 3, + "filter": {}, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "find", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "aggregate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "collection0", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "batchSize": 3, + "session": "session0" + }, + "expectResult": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "aggregate": "test", + "pipeline": [ + { + "$project": { + "_id": 1 + } + } + ], + "cursor": { + "batchSize": 3 + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "aggregate", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "test", + "batchSize": 3, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false + }, + "commandName": "getMore", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + }, + { + "description": "distinct", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "distinct", + "arguments": { + "fieldName": "_id", + "filter": {}, + "session": "session0" + }, + "expectResult": [ + 1, + 2, + 3, + 4 + ] + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "distinct": "test", + "key": "_id", + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "distinct", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "readConcern": { + "$$exists": false + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + }, + { + "_id": 4 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/reads.yml b/src/test/spec/json/transactions/unified/reads.yml new file mode 100644 index 000000000..66d7e2f79 --- /dev/null +++ b/src/test/spec/json/transactions/unified/reads.yml @@ -0,0 +1,298 @@ +description: reads + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + - { _id: 4 } + +tests: + - + description: 'collection readConcern without transaction' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection1 collection1 + database: *database0 + collectionName: *collection_name + collectionOptions: + readConcern: { level: majority } + - + object: *collection1 + name: find + arguments: + filter: {} + session: *session0 + expectResult: *data + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + readConcern: + level: majority + lsid: { $$sessionLsid: *session0 } + txnNumber: { $$exists: false } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + commandName: find + databaseName: *database_name + outcome: &outcome + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: find + operations: + - &startTransaction + object: *session0 + name: startTransaction + - &find + object: *collection0 + name: find + arguments: + batchSize: 3 + filter: {} + session: *session0 + expectResult: *data + - *find + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + find: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: find + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: aggregate + operations: + - *startTransaction + - &aggregate + object: *collection0 + name: aggregate + arguments: + pipeline: + - { $project: { _id: 1 } } + batchSize: 3 + session: *session0 + expectResult: *data + - *aggregate + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + aggregate: *collection_name + pipeline: + - { $project: { _id: 1 } } + cursor: + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: aggregate + databaseName: *database_name + - + commandStartedEvent: + command: + getMore: { $$type: [ int, long ] } + collection: *collection_name + batchSize: 3 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + commandName: getMore + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: distinct + operations: + - *startTransaction + - + object: *collection0 + name: distinct + arguments: + fieldName: _id + filter: {} + session: *session0 + expectResult: [ 1, 2, 3, 4] + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + distinct: *collection_name + key: _id + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: distinct + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + readConcern: { $$exists: false } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: *outcome \ No newline at end of file diff --git a/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.json b/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.json new file mode 100644 index 000000000..77a1b03eb --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.json @@ -0,0 +1,2436 @@ +{ + "description": "retryable-abort-errorLabels", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "abortTransaction only retries once with RetryableWriteError from server", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction does not retry without RetryableWriteError label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 10107, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 13436, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 13435, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 11602, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 11600, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 91, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 7, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 6, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 9001, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after WriteConcernError InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11600, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11602, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after WriteConcernError PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 189, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.yml b/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.yml new file mode 100644 index 000000000..8a4ddf994 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-abort-errorLabels.yml @@ -0,0 +1,1381 @@ +description: retryable-abort-errorLabels + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'abortTransaction only retries once with RetryableWriteError from server' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - abortTransaction + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + # Driver retries abort once + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction does not retry without RetryableWriteError label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + # Override server behavior: do not send RetryableWriteError label with retryable code + errorLabels: [] + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + # Driver does not retry abort + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 10107 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 13436 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 13435 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 11602 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 11600 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 189 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 91 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 7 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 6 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 9001 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 89 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after WriteConcernError InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11600 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11602 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after WriteConcernError PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 189 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after WriteConcernError ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] diff --git a/src/test/spec/json/transactions/unified/retryable-abort-handshake.yml b/src/test/spec/json/transactions/unified/retryable-abort-handshake.yml index d0e9ec86b..2d91b75a9 100644 --- a/src/test/spec/json/transactions/unified/retryable-abort-handshake.yml +++ b/src/test/spec/json/transactions/unified/retryable-abort-handshake.yml @@ -106,8 +106,7 @@ tests: - commandStartedEvent: command: abortTransaction: 1 - lsid: - $$sessionLsid: *session0 + lsid: { $$sessionLsid: *session0 } commandName: abortTransaction databaseName: admin diff --git a/src/test/spec/json/transactions/unified/retryable-abort.json b/src/test/spec/json/transactions/unified/retryable-abort.json new file mode 100644 index 000000000..381cfa91f --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-abort.json @@ -0,0 +1,600 @@ +{ + "description": "retryable-abort", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "abortTransaction only performs a single retry", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction does not retry after Interrupted", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "errorCode": 11601, + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction does not retry after WriteConcernError Interrupted", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "writeConcernError": { + "code": 11601, + "errmsg": "operation was interrupted" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "abortTransaction succeeds after connection error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "abortTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/retryable-abort.yml b/src/test/spec/json/transactions/unified/retryable-abort.yml new file mode 100644 index 000000000..602c92aec --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-abort.yml @@ -0,0 +1,352 @@ +description: retryable-abort + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + # Define a second set of entities for a retryWrites=false client + - + client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - + database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + session: + id: &session1 session1 + client: *client1 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'abortTransaction only performs a single retry' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - abortTransaction + closeConnection: true + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction does not retry after Interrupted' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + errorCode: 11601 + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction does not retry after WriteConcernError Interrupted' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + writeConcernError: + code: 11601 + errmsg: 'operation was interrupted' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'abortTransaction succeeds after connection error' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - abortTransaction + closeConnection: true + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] diff --git a/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.json b/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.json new file mode 100644 index 000000000..d3ce8b148 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.json @@ -0,0 +1,2564 @@ +{ + "description": "retryable-commit-errorLabels", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.3.1", + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction does not retry error without RetryableWriteError label", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 11600, + "errorLabels": [] + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError" + ] + } + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "commitTransaction retries once with RetryableWriteError from server", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 112, + "errorLabels": [ + "RetryableWriteError" + ] + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after NotWritablePrimary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 10107, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after NotPrimaryOrSecondary", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 13436, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after NotPrimaryNoSecondaryOk", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 13435, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 11602, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 189, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after HostNotFound", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 7, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after HostUnreachable", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 6, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after SocketException", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 9001, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after NetworkTimeout", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 89, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after WriteConcernError InterruptedAtShutdown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11600, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 11602, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after WriteConcernError PrimarySteppedDown", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 189, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after WriteConcernError ShutdownInProgress", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorLabels": [ + "RetryableWriteError" + ], + "writeConcernError": { + "code": 91, + "errmsg": "Replication is being shut down" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after InterruptedAtShutdown", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 11600, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after ShutdownInProgress", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 91, + "errorLabels": [ + "RetryableWriteError" + ], + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.yml b/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.yml new file mode 100644 index 000000000..a29f56516 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-commit-errorLabels.yml @@ -0,0 +1,1474 @@ +description: retryable-commit-errorLabels + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: 4.3.1 # failCommand errorLabels option + topologies: + - replicaset + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + # Define a second set of entities for a retryWrites=false client + - + client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - + database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + session: + id: &session1 session1 + client: *client1 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'commitTransaction does not retry error without RetryableWriteError label' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 11600 # InterruptedAtShutdown, normally a retryable error code + errorLabels: [] # Override server behavior: do not send RetryableWriteError label with retryable code + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + expectError: + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + # Driver does not retry commit because there was no RetryableWriteError label on response + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'commitTransaction retries once with RetryableWriteError from server' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 112 # WriteConflict, not a retryable error code + # Override server behavior: send RetryableWriteError label with non-retryable error code + errorLabels: + - RetryableWriteError + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + # Driver retries commit and it succeeds + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after NotWritablePrimary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 10107 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after NotPrimaryOrSecondary' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 13436 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after NotPrimaryNoSecondaryOk' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 13435 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 11602 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 189 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after HostNotFound' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 7 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after HostUnreachable' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 6 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after SocketException' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 9001 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after NetworkTimeout' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 89 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after WriteConcernError InterruptedAtShutdown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11600 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after WriteConcernError InterruptedDueToReplStateChange' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 11602 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after WriteConcernError PrimarySteppedDown' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 189 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after WriteConcernError ShutdownInProgress' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorLabels: + - RetryableWriteError + writeConcernError: + code: 91 + errmsg: 'Replication is being shut down' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after InterruptedAtShutdown' + # Fail points using state change errors (e.g. InterruptedAtShutdown, + # ShutdownInProgress) may remain enabled indefinitely (CLOUDP-84573). + runOnRequirements: + - serverless: forbid + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 11600 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after ShutdownInProgress' + # Fail points using state change errors (e.g. InterruptedAtShutdown, + # ShutdownInProgress) may remain enabled indefinitely (CLOUDP-84573). + runOnRequirements: + - serverless: forbid + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 91 + errorLabels: + - RetryableWriteError + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/retryable-commit-handshake.yml b/src/test/spec/json/transactions/unified/retryable-commit-handshake.yml index e9904fdf5..6a03ba6b6 100644 --- a/src/test/spec/json/transactions/unified/retryable-commit-handshake.yml +++ b/src/test/spec/json/transactions/unified/retryable-commit-handshake.yml @@ -105,8 +105,7 @@ tests: - commandStartedEvent: command: commitTransaction: 1 - lsid: - $$sessionLsid: *session0 + lsid: { $$sessionLsid: *session0 } commandName: commitTransaction databaseName: admin diff --git a/src/test/spec/json/transactions/unified/retryable-commit.json b/src/test/spec/json/transactions/unified/retryable-commit.json new file mode 100644 index 000000000..b794c1c55 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-commit.json @@ -0,0 +1,868 @@ +{ + "description": "retryable-commit", + "schemaVersion": "1.4", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + }, + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "retryWrites": false + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "commitTransaction fails after Interrupted", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "errorCode": 11601, + "closeConnection": false + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorCodeName": "Interrupted", + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "commitTransaction is not retried after UnsatisfiableWriteConcern error", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "writeConcernError": { + "code": 100, + "errmsg": "Not enough data-bearing nodes" + } + } + } + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction", + "expectError": { + "errorLabelsOmit": [ + "RetryableWriteError", + "TransientTransactionError", + "UnknownTransactionCommitResult" + ] + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction fails after two errors", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction applies majority write concern on retries", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client1", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 2 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 2, + "journal": true, + "wtimeoutMS": 5000 + } + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction", + "expectError": { + "errorLabelsContain": [ + "RetryableWriteError", + "UnknownTransactionCommitResult" + ], + "errorLabelsOmit": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": 2, + "j": true, + "wtimeout": 5000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "j": true, + "wtimeout": 5000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "j": true, + "wtimeout": 5000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commitTransaction succeeds after connection error", + "runOnRequirements": [ + { + "serverless": "forbid" + } + ], + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "commitTransaction" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority", + "wtimeout": 10000 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/retryable-commit.yml b/src/test/spec/json/transactions/unified/retryable-commit.yml new file mode 100644 index 000000000..916944065 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-commit.yml @@ -0,0 +1,521 @@ +description: retryable-commit + +schemaVersion: '1.4' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + # Define a second set of entities for a retryWrites=false client + - + client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + retryWrites: false + observeEvents: + - commandStartedEvent + - + database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - + collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - + session: + id: &session1 session1 + client: *client1 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'commitTransaction fails after Interrupted' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + errorCode: 11601 + closeConnection: false + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorCodeName: Interrupted + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - UnknownTransactionCommitResult + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'commitTransaction is not retried after UnsatisfiableWriteConcern error' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + writeConcernError: + code: 100 + errmsg: 'Not enough data-bearing nodes' + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectError: + errorLabelsOmit: + - RetryableWriteError + - TransientTransactionError + - UnknownTransactionCommitResult + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction fails after two errors' + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309). + runOnRequirements: + - serverless: forbid + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + closeConnection: true + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # First call to commit fails after a single retry attempt. + - + object: *session1 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + # Second call to commit succeeds because the failpoint was disabled. + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction applies majority write concern on retries' + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309). + runOnRequirements: + - serverless: forbid + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client1 + failPoint: + configureFailPoint: failCommand + mode: { times: 2 } + data: + failCommands: + - commitTransaction + closeConnection: true + - + object: *session1 + name: startTransaction + arguments: + writeConcern: + w: 2 + journal: true + wtimeoutMS: 5000 + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + # First call to commit fails after a single retry attempt. + - + object: *session1 + name: commitTransaction + expectError: + errorLabelsContain: + - RetryableWriteError + - UnknownTransactionCommitResult + errorLabelsOmit: + - TransientTransactionError + # Second call to commit succeeds because the failpoint was disabled. + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: 2 + j: true + wtimeout: 5000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + j: true + wtimeout: 5000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + j: true + wtimeout: 5000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'commitTransaction succeeds after connection error' + # Failing commitTransaction with closeConnection:true may abort the + # transaction (CLOUDP-202309). + runOnRequirements: + - serverless: forbid + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - commitTransaction + closeConnection: true + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + # commitTransaction applies w:majority on retries + writeConcern: + w: majority + wtimeout: 10000 + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/retryable-writes.json b/src/test/spec/json/transactions/unified/retryable-writes.json new file mode 100644 index 000000000..c196e6862 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-writes.json @@ -0,0 +1,468 @@ +{ + "description": "retryable-writes", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "increment txnNumber", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 3 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 3 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + }, + { + "object": "collection0", + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ], + "session": "session0" + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 4, + "1": 5 + } + } + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 3 + } + ], + "ordered": true, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "3" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 4 + }, + { + "_id": 5 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "4" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 4 + }, + { + "_id": 5 + } + ] + } + ] + }, + { + "description": "writes are not retried", + "operations": [ + { + "object": "testRunner", + "name": "failPoint", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "insert" + ], + "closeConnection": true + } + } + } + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectError": { + "errorLabelsContain": [ + "TransientTransactionError" + ] + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/retryable-writes.yml b/src/test/spec/json/transactions/unified/retryable-writes.yml new file mode 100644 index 000000000..aa9c037d4 --- /dev/null +++ b/src/test/spec/json/transactions/unified/retryable-writes.yml @@ -0,0 +1,259 @@ +description: retryable-writes + +schemaVersion: '1.3' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'increment txnNumber' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 3 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 3 } } + - + object: *session0 + name: abortTransaction + - + object: *collection0 + name: insertMany + arguments: + documents: + - { _id: 4 } + - { _id: 5 } + session: *session0 + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 4 + '1': 5 + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 3 } + ordered: true + readConcern: + afterClusterTime: { $$exists: true } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '3' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '3' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 4 } + - { _id: 5 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '4' } + startTransaction: { $$exists: false } + autocommit: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 4 } + - { _id: 5 } + - + description: 'writes are not retried' + operations: + - + object: testRunner + name: failPoint + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: + - insert + closeConnection: true + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectError: + errorLabelsContain: + - TransientTransactionError + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] diff --git a/src/test/spec/json/transactions/unified/run-command.json b/src/test/spec/json/transactions/unified/run-command.json new file mode 100644 index 000000000..7bd420ef7 --- /dev/null +++ b/src/test/spec/json/transactions/unified/run-command.json @@ -0,0 +1,421 @@ +{ + "description": "run-command", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "run command with default read preference", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + }, + "commandName": "insert" + }, + "expectResult": { + "n": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "run command with secondary read preference in client option and primary read preference in transaction options", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readPreference": "secondary" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "primary" + } + } + }, + { + "object": "database1", + "name": "runCommand", + "arguments": { + "session": "session1", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + }, + "commandName": "insert" + }, + "expectResult": { + "n": 1 + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "run command with explicit primary read preference", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ] + }, + "readPreference": { + "mode": "primary" + }, + "commandName": "insert" + }, + "expectResult": { + "n": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + }, + { + "description": "run command fails with explicit secondary read preference", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "readPreference": { + "mode": "secondary" + }, + "commandName": "find" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + } + ] + }, + { + "description": "run command fails with secondary read preference from transaction options", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "secondary" + } + } + }, + { + "object": "database0", + "name": "runCommand", + "arguments": { + "session": "session0", + "command": { + "find": "test" + }, + "commandName": "find" + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/run-command.yml b/src/test/spec/json/transactions/unified/run-command.yml new file mode 100644 index 000000000..b9e1033d4 --- /dev/null +++ b/src/test/spec/json/transactions/unified/run-command.yml @@ -0,0 +1,254 @@ +description: run-command + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'run command with default read preference' + operations: + - + object: *session0 + name: startTransaction + - + object: *database0 + name: runCommand + arguments: + session: *session0 + command: + insert: *collection_name + documents: + - { _id: 1 } + commandName: insert + expectResult: + 'n': 1 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + description: 'run command with secondary read preference in client option and primary read preference in transaction options' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: { readPreference: secondary } + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + arguments: + readPreference: + mode: primary + - + object: *database1 + name: runCommand + arguments: + session: *session1 + command: + insert: *collection_name + documents: + - { _id: 1 } + commandName: insert + expectResult: + 'n': 1 + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + description: 'run command with explicit primary read preference' + operations: + - + object: *session0 + name: startTransaction + - + object: *database0 + name: runCommand + arguments: + session: *session0 + command: + insert: *collection_name + documents: + - { _id: 1 } + readPreference: + mode: primary + commandName: insert + expectResult: + 'n': 1 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + description: 'run command fails with explicit secondary read preference' + operations: + - + object: *session0 + name: startTransaction + - + object: *database0 + name: runCommand + arguments: + session: *session0 + command: + find: *collection_name + readPreference: + mode: secondary + commandName: find + expectError: + errorContains: 'read preference in a transaction must be primary' + - + description: 'run command fails with secondary read preference from transaction options' + operations: + - + object: *session0 + name: startTransaction + arguments: + readPreference: + mode: secondary + - + object: *database0 + name: runCommand + arguments: + session: *session0 + command: + find: *collection_name + commandName: find + expectError: + errorContains: 'read preference in a transaction must be primary' diff --git a/src/test/spec/json/transactions/unified/transaction-options-repl.json b/src/test/spec/json/transactions/unified/transaction-options-repl.json new file mode 100644 index 000000000..dc2cb7758 --- /dev/null +++ b/src/test/spec/json/transactions/unified/transaction-options-repl.json @@ -0,0 +1,267 @@ +{ + "description": "transaction-options-repl", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "readConcern snapshot in startTransaction options", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "session": { + "id": "session1", + "client": "client0", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "majority" + } + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "snapshot" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "snapshot" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "snapshot" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "snapshot", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/transaction-options-repl.yml b/src/test/spec/json/transactions/unified/transaction-options-repl.yml new file mode 100644 index 000000000..f801c13dd --- /dev/null +++ b/src/test/spec/json/transactions/unified/transaction-options-repl.yml @@ -0,0 +1,155 @@ +description: transaction-options-repl + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'readConcern snapshot in startTransaction options' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - session: + id: &session1 session1 + client: *client0 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: majority } + - + object: *session1 + name: startTransaction + arguments: + readConcern: + level: snapshot + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + # Now test abort + - + object: *session1 + name: startTransaction + arguments: + readConcern: + level: snapshot + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: snapshot + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: snapshot + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/transaction-options.json b/src/test/spec/json/transactions/unified/transaction-options.json new file mode 100644 index 000000000..78e4c8207 --- /dev/null +++ b/src/test/spec/json/transactions/unified/transaction-options.json @@ -0,0 +1,2081 @@ +{ + "description": "transaction-options", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ], + "tests": [ + { + "description": "no transaction options set", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + }, + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "transaction options inherited from client", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local" + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "transaction options inherited from defaultTransactionOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "session": { + "id": "session1", + "client": "client0", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": 1 + }, + "maxCommitTimeMS": 60000 + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "startTransaction options override defaults", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "snapshot" + }, + "writeConcern": { + "w": 1 + }, + "maxCommitTimeMS": 30000 + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": "majority" + }, + "maxCommitTimeMS": 60000 + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "defaultTransactionOptions override client options", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readConcernLevel": "local", + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "w": "majority" + }, + "maxCommitTimeMS": 60000 + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority" + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": 60000 + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "majority", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": "majority" + }, + "maxTimeMS": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "readConcern local in defaultTransactionOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "w": 1 + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1", + "sessionOptions": { + "defaultTransactionOptions": { + "readConcern": { + "level": "local" + } + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session1", + "name": "commitTransaction" + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 2 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 2 + } + } + } + }, + { + "object": "session1", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local" + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "level": "local", + "afterClusterTime": { + "$$exists": true + } + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "2" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "w": 1 + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "client writeConcern ignored for bulk", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "w": "majority" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 1 + } + } + }, + { + "object": "collection1", + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ], + "session": "session1" + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": 1 + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "readPreference inherited from client", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readPreference": "secondary" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "readPreference inherited from defaultTransactionOptions", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readPreference": "primary" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1", + "sessionOptions": { + "defaultTransactionOptions": { + "readPreference": { + "mode": "secondary" + } + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction" + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "startTransaction overrides readPreference", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "readPreference": "primary" + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database1", + "client": "client1", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection1", + "database": "database1", + "collectionName": "test" + } + }, + { + "session": { + "id": "session1", + "client": "client1", + "sessionOptions": { + "defaultTransactionOptions": { + "readPreference": { + "mode": "primary" + } + } + } + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "arguments": { + "readPreference": { + "mode": "secondary" + } + } + }, + { + "object": "collection1", + "name": "insertOne", + "arguments": { + "session": "session1", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "collection1", + "name": "find", + "arguments": { + "session": "session1", + "filter": { + "_id": 1 + } + }, + "expectError": { + "errorContains": "read preference in a transaction must be primary" + } + }, + { + "object": "session1", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client1", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session1" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/transaction-options.yml b/src/test/spec/json/transactions/unified/transaction-options.yml new file mode 100644 index 000000000..f3ab07cfc --- /dev/null +++ b/src/test/spec/json/transactions/unified/transaction-options.yml @@ -0,0 +1,1116 @@ +description: transaction-options + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + +tests: + - + description: 'no transaction options set' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: commitTransaction + # Now test abort + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: &outcome + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'transaction options inherited from client' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readConcernLevel: local + w: 1 + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: local + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + maxTimeMS: { $$exists: false } + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: local + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + maxTimeMS: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: *outcome + - + description: 'transaction options inherited from defaultTransactionOptions' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - session: + id: &session1 session1 + client: *client0 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: majority } + writeConcern: { w: 1 } + maxCommitTimeMS: 60000 + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: majority + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: majority + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + commandName: abortTransaction + databaseName: admin + outcome: *outcome + - + description: 'startTransaction options override defaults' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readConcernLevel: local + w: 1 + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: snapshot } + writeConcern: { w: 1 } + maxCommitTimeMS: 30000 + - + object: *session1 + name: startTransaction + arguments: + readConcern: + level: majority + writeConcern: + w: majority + maxCommitTimeMS: 60000 + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + arguments: + readConcern: + level: majority + writeConcern: + w: majority + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: majority + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: majority + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + maxTimeMS: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: *outcome + - + description: 'defaultTransactionOptions override client options' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readConcernLevel: local + w: 1 + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: majority } + writeConcern: { w: majority } + maxCommitTimeMS: 60000 + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: majority + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + maxTimeMS: 60000 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: majority + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + maxTimeMS: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: majority + maxTimeMS: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: *outcome + - + description: 'readConcern local in defaultTransactionOptions' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + w: 1 + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + sessionOptions: + defaultTransactionOptions: + readConcern: { level: local } + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session1 + name: commitTransaction + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 2 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 2 } } + - + object: *session1 + name: abortTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: + level: local + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + commandName: commitTransaction + databaseName: admin + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 2 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: true + autocommit: false + readConcern: + level: local + afterClusterTime: { $$exists: true } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '2' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: + w: 1 + commandName: abortTransaction + databaseName: admin + outcome: *outcome + - + description: 'client writeConcern ignored for bulk' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + w: majority + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + arguments: + writeConcern: + w: 1 + - + object: *collection1 + name: bulkWrite + arguments: + requests: + - + insertOne: + document: { _id: 1 } + session: *session1 + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: + $$unsetOrMatches: + '0': 1 + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + # No writeConcern + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: 1 + commandName: commitTransaction + databaseName: admin + outcome: *outcome + - + description: 'readPreference inherited from client' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readPreference: secondary + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection1 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'readPreference inherited from defaultTransactionOptions' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readPreference: primary + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + sessionOptions: + defaultTransactionOptions: + readPreference: { mode: secondary } + - + object: *session1 + name: startTransaction + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection1 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - + description: 'startTransaction overrides readPreference' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: + readPreference: primary + observeEvents: + - commandStartedEvent + - database: + id: &database1 database1 + client: *client1 + databaseName: *database_name + - collection: + id: &collection1 collection1 + database: *database1 + collectionName: *collection_name + - session: + id: &session1 session1 + client: *client1 + sessionOptions: + defaultTransactionOptions: + readPreference: { mode: primary } + - + object: *session1 + name: startTransaction + arguments: + readPreference: + mode: secondary + - + object: *collection1 + name: insertOne + arguments: + session: *session1 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *collection1 + name: find + arguments: + session: *session1 + filter: { _id: 1 } + expectError: + errorContains: 'read preference in a transaction must be primary' + - + object: *session1 + name: commitTransaction + expectEvents: + - + client: *client1 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session1 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } diff --git a/src/test/spec/json/transactions/unified/update.json b/src/test/spec/json/transactions/unified/update.json new file mode 100644 index 000000000..8090fc908 --- /dev/null +++ b/src/test/spec/json/transactions/unified/update.json @@ -0,0 +1,565 @@ +{ + "description": "update", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3 + } + ] + } + ], + "tests": [ + { + "description": "update", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + }, + { + "object": "collection0", + "name": "replaceOne", + "arguments": { + "session": "session0", + "filter": { + "x": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "collection0", + "name": "updateMany", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 3 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": true, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "x": 1 + }, + "u": { + "y": 1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gte": 3 + } + }, + "u": { + "$set": { + "z": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + }, + { + "_id": 3, + "z": 1 + }, + { + "_id": 4, + "y": 1, + "z": 1 + } + ] + } + ] + }, + { + "description": "collections writeConcern ignored for update", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "collection": { + "id": "collection1", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": "majority" + } + } + } + } + ] + } + }, + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection1", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + } + }, + { + "object": "collection1", + "name": "replaceOne", + "arguments": { + "session": "session0", + "filter": { + "x": 1 + }, + "replacement": { + "y": 1 + } + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "collection1", + "name": "updateMany", + "arguments": { + "session": "session0", + "filter": { + "_id": { + "$gte": 3 + } + }, + "update": { + "$set": { + "z": 1 + } + } + }, + "expectResult": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 4 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": true, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "readConcern": { + "$$exists": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "x": 1 + }, + "u": { + "y": 1 + }, + "multi": { + "$$unsetOrMatches": false + }, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": { + "$gte": 3 + } + }, + "u": { + "$set": { + "z": 1 + } + }, + "multi": true, + "upsert": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/update.yml b/src/test/spec/json/transactions/unified/update.yml new file mode 100644 index 000000000..4e4935065 --- /dev/null +++ b/src/test/spec/json/transactions/unified/update.yml @@ -0,0 +1,299 @@ +description: update + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3 } + +tests: + - + description: update + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: updateOne + arguments: + session: *session0 + filter: { _id: 4 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + - + object: *collection0 + name: replaceOne + arguments: + session: *session0 + filter: { x: 1 } + replacement: { y: 1 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - + object: *collection0 + name: updateMany + arguments: + session: *session0 + filter: { _id: { $gte: 3 } } + update: { $set: { z: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 4 } + u: { $inc: { x: 1 } } + upsert: true + multi: { $$unsetOrMatches: false } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { x: 1 } + u: { y: 1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: { $gte: 3 } } + u: { $set: { z: 1 } } + multi: true + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 1 } + - { _id: 2 } + - { _id: 3, z: 1 } + - { _id: 4, y: 1, z: 1 } + - + description: 'collections writeConcern ignored for update' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - collection: + id: &collection1 collection1 + database: *database0 + collectionName: *collection_name + collectionOptions: + writeConcern: { w: majority } + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection1 + name: updateOne + arguments: + session: *session0 + filter: { _id: 4 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 1 + upsertedId: 4 + - + object: *collection1 + name: replaceOne + arguments: + session: *session0 + filter: { x: 1 } + replacement: { y: 1 } + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - + object: *collection1 + name: updateMany + arguments: + session: *session0 + filter: { _id: { $gte: 3 } } + update: { $set: { z: 1 } } + expectResult: + matchedCount: 2 + modifiedCount: 2 + upsertedCount: 0 + - + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 4 } + u: { $inc: { x: 1 } } + upsert: true + multi: { $$unsetOrMatches: false } + ordered: true + readConcern: { $$exists: false } + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { x: 1 } + u: { y: 1 } + multi: { $$unsetOrMatches: false } + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: { $gte: 3 } } + u: { $set: { z: 1 } } + multi: true + upsert: { $$unsetOrMatches: false } + ordered: true + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: update + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin diff --git a/src/test/spec/json/transactions/unified/write-concern.json b/src/test/spec/json/transactions/unified/write-concern.json new file mode 100644 index 000000000..29d1977a8 --- /dev/null +++ b/src/test/spec/json/transactions/unified/write-concern.json @@ -0,0 +1,1588 @@ +{ + "description": "write-concern", + "schemaVersion": "1.9", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "replicaset" + ] + }, + { + "minServerVersion": "4.1.8", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "transaction-tests" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "test" + } + }, + { + "collection": { + "id": "collection_w0", + "database": "database0", + "collectionName": "test", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + } + } + }, + { + "session": { + "id": "session0", + "client": "client0" + } + } + ], + "initialData": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + } + ] + } + ], + "tests": [ + { + "description": "commit with majority", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + }, + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "commit with default", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + }, + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "abort with majority", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": "majority" + } + } + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "w": "majority" + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + } + ] + } + ] + }, + { + "description": "abort with default", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "abortTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "abortTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "abortTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + } + ] + } + ] + }, + { + "description": "start with unacknowledged write concern", + "operations": [ + { + "object": "session0", + "name": "startTransaction", + "arguments": { + "writeConcern": { + "w": 0 + } + }, + "expectError": { + "isClientError": true, + "errorContains": "transactions do not support unacknowledged write concern" + } + } + ] + }, + { + "description": "start with implicit unacknowledged write concern", + "operations": [ + { + "object": "testRunner", + "name": "createEntities", + "arguments": { + "entities": [ + { + "client": { + "id": "client1", + "useMultipleMongoses": false, + "uriOptions": { + "w": 0 + } + } + }, + { + "session": { + "id": "session1", + "client": "client1" + } + } + ] + } + }, + { + "object": "session1", + "name": "startTransaction", + "expectError": { + "isClientError": true, + "errorContains": "transactions do not support unacknowledged write concern" + } + } + ] + }, + { + "description": "unacknowledged write concern coll insertOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "insertOne", + "arguments": { + "session": "session0", + "document": { + "_id": 1 + } + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedId": { + "$$unsetOrMatches": 1 + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + }, + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll insertMany", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "insertMany", + "arguments": { + "session": "session0", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ] + }, + "expectResult": { + "$$unsetOrMatches": { + "insertedIds": { + "$$unsetOrMatches": { + "0": 1, + "1": 2 + } + } + } + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + }, + { + "_id": 2 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + }, + { + "_id": 1 + }, + { + "_id": 2 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll bulkWrite", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "bulkWrite", + "arguments": { + "session": "session0", + "requests": [ + { + "insertOne": { + "document": { + "_id": 1 + } + } + } + ] + }, + "expectResult": { + "deletedCount": 0, + "insertedCount": 1, + "insertedIds": { + "$$unsetOrMatches": { + "0": 1 + } + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "test", + "documents": [ + { + "_id": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "insert", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0 + }, + { + "_id": 1 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll deleteOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "deleteOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 0 + }, + "limit": 1 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "unacknowledged write concern coll deleteMany", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "deleteMany", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + } + }, + "expectResult": { + "deletedCount": 1 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "delete": "test", + "deletes": [ + { + "q": { + "_id": 0 + }, + "limit": 0 + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "delete", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "unacknowledged write concern coll updateOne", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "updateOne", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 0 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "upsert": true, + "multi": { + "$$unsetOrMatches": false + } + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0, + "x": 1 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll updateMany", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "updateMany", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + }, + "expectResult": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 0 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "upsert": true + } + ], + "ordered": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "update", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0, + "x": 1 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll findOneAndDelete", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "findOneAndDelete", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + } + }, + "expectResult": { + "_id": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 0 + }, + "remove": true, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [] + } + ] + }, + { + "description": "unacknowledged write concern coll findOneAndReplace", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "findOneAndReplace", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + }, + "replacement": { + "x": 1 + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 0 + }, + "update": { + "x": 1 + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0, + "x": 1 + } + ] + } + ] + }, + { + "description": "unacknowledged write concern coll findOneAndUpdate", + "operations": [ + { + "object": "session0", + "name": "startTransaction" + }, + { + "object": "collection_w0", + "name": "findOneAndUpdate", + "arguments": { + "session": "session0", + "filter": { + "_id": 0 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "returnDocument": "Before" + }, + "expectResult": { + "_id": 0 + } + }, + { + "object": "session0", + "name": "commitTransaction" + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "findAndModify": "test", + "query": { + "_id": 0 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "new": { + "$$unsetOrMatches": false + }, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": true, + "autocommit": false, + "readConcern": { + "$$exists": false + }, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "findAndModify", + "databaseName": "transaction-tests" + } + }, + { + "commandStartedEvent": { + "command": { + "commitTransaction": 1, + "lsid": { + "$$sessionLsid": "session0" + }, + "txnNumber": { + "$numberLong": "1" + }, + "startTransaction": { + "$$exists": false + }, + "autocommit": false, + "writeConcern": { + "$$exists": false + } + }, + "commandName": "commitTransaction", + "databaseName": "admin" + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "test", + "databaseName": "transaction-tests", + "documents": [ + { + "_id": 0, + "x": 1 + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/transactions/unified/write-concern.yml b/src/test/spec/json/transactions/unified/write-concern.yml new file mode 100644 index 000000000..1eb27cf1d --- /dev/null +++ b/src/test/spec/json/transactions/unified/write-concern.yml @@ -0,0 +1,655 @@ +# Assumes the default for transactions is the same as for all ops, tests +# setting the writeConcern to "majority". +description: write-concern + +schemaVersion: '1.9' + +runOnRequirements: + - + minServerVersion: '4.0' + topologies: + - replicaset + - + minServerVersion: 4.1.8 + topologies: + - sharded + - load-balanced + +createEntities: + - + client: + id: &client0 client0 + useMultipleMongoses: false + observeEvents: + - commandStartedEvent + - + database: + id: &database0 database0 + client: *client0 + databaseName: &database_name transaction-tests + - + collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection_name test + - + collection: + id: &collection_w0 collection_w0 + database: *database0 + collectionName: &collection_name test + collectionOptions: + writeConcern: { w: 0 } + - + session: + id: &session0 session0 + client: *client0 + +initialData: + - + collectionName: *collection_name + databaseName: *database_name + documents: &data + - { _id: 0 } + +tests: + - + description: 'commit with majority' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - &commitTransaction + object: *session0 + name: commitTransaction + expectEvents: + - + client: *client0 + events: + - &insertOneEvent + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + <<: &transactionCommandArgs + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: true + autocommit: false + readConcern: { $$exists: false } + writeConcern: { $$exists: false } + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0 } + - { _id: 1 } + - + description: 'commit with default' + operations: + - &startTransaction + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + <<: *transactionCommandArgs + commandName: insert + databaseName: *database_name + - &commitWithDefaultWCEvent + commandStartedEvent: + command: + commitTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: commitTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0 } + - { _id: 1 } + - + description: 'abort with majority' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: majority + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + <<: *transactionCommandArgs + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: + w: majority + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: 'abort with default' + operations: + - + object: *session0 + name: startTransaction + - + object: *collection0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - + object: *session0 + name: abortTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + ordered: true + <<: *transactionCommandArgs + commandName: insert + databaseName: *database_name + - + commandStartedEvent: + command: + abortTransaction: 1 + lsid: { $$sessionLsid: *session0 } + txnNumber: { $numberLong: '1' } + startTransaction: { $$exists: false } + autocommit: false + writeConcern: { $$exists: false } + commandName: abortTransaction + databaseName: admin + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: *data + - + description: 'start with unacknowledged write concern' + operations: + - + object: *session0 + name: startTransaction + arguments: + writeConcern: + w: 0 + expectError: + isClientError: true + errorContains: 'transactions do not support unacknowledged write concern' + - + description: 'start with implicit unacknowledged write concern' + operations: + - + object: testRunner + name: createEntities + arguments: + entities: + - client: + id: &client1 client1 + useMultipleMongoses: false + uriOptions: { w: 0 } + - session: + id: &session1 session1 + client: *client1 + - + object: *session1 + name: startTransaction + expectError: + isClientError: true + errorContains: 'transactions do not support unacknowledged write concern' + - + description: 'unacknowledged write concern coll insertOne' + operations: + - *startTransaction + - + object: *collection_w0 + name: insertOne + arguments: + session: *session0 + document: { _id: 1 } + expectResult: + $$unsetOrMatches: { insertedId: { $$unsetOrMatches: 1 } } + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - *insertOneEvent + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0 } + - { _id: 1 } + - + description: 'unacknowledged write concern coll insertMany' + operations: + - *startTransaction + - + object: *collection_w0 + name: insertMany + arguments: + session: *session0 + documents: + - { _id: 1 } + - { _id: 2 } + expectResult: + $$unsetOrMatches: + insertedIds: + $$unsetOrMatches: + '0': 1 + '1': 2 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + insert: *collection_name + documents: + - { _id: 1 } + - { _id: 2 } + ordered: true + <<: *transactionCommandArgs + commandName: insert + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0 } + - { _id: 1 } + - { _id: 2 } + - + description: 'unacknowledged write concern coll bulkWrite' + operations: + - *startTransaction + - + object: *collection_w0 + name: bulkWrite + arguments: + session: *session0 + requests: + - + insertOne: + document: { _id: 1 } + expectResult: + deletedCount: 0 + insertedCount: 1 + insertedIds: + $$unsetOrMatches: + '0': 1 + matchedCount: 0 + modifiedCount: 0 + upsertedCount: 0 + upsertedIds: { } + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - *insertOneEvent + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0 } + - { _id: 1 } + - + description: 'unacknowledged write concern coll deleteOne' + operations: + - *startTransaction + - + object: *collection_w0 + name: deleteOne + arguments: + session: *session0 + filter: { _id: 0 } + expectResult: + deletedCount: 1 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 0 } + limit: 1 + ordered: true + <<: *transactionCommandArgs + commandName: delete + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'unacknowledged write concern coll deleteMany' + operations: + - *startTransaction + - + object: *collection_w0 + name: deleteMany + arguments: + session: *session0 + filter: { _id: 0 } + expectResult: + deletedCount: 1 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + delete: *collection_name + deletes: + - + q: { _id: 0 } + limit: 0 + ordered: true + <<: *transactionCommandArgs + commandName: delete + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'unacknowledged write concern coll updateOne' + operations: + - *startTransaction + - + object: *collection_w0 + name: updateOne + arguments: + session: *session0 + filter: { _id: 0 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 0 } + u: { $inc: { x: 1 } } + upsert: true + multi: { $$unsetOrMatches: false } + ordered: true + <<: *transactionCommandArgs + commandName: update + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0, x: 1 } + - + description: 'unacknowledged write concern coll updateMany' + operations: + - *startTransaction + - + object: *collection_w0 + name: updateMany + arguments: + session: *session0 + filter: { _id: 0 } + update: { $inc: { x: 1 } } + upsert: true + expectResult: + matchedCount: 1 + modifiedCount: 1 + upsertedCount: 0 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + update: *collection_name + updates: + - + q: { _id: 0 } + u: { $inc: { x: 1 } } + multi: true + upsert: true + ordered: true + <<: *transactionCommandArgs + commandName: update + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0, x: 1 } + - + description: 'unacknowledged write concern coll findOneAndDelete' + operations: + - *startTransaction + - + object: *collection_w0 + name: findOneAndDelete + arguments: + session: *session0 + filter: { _id: 0 } + expectResult: + _id: 0 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 0 } + remove: true + <<: *transactionCommandArgs + commandName: findAndModify + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: [] + - + description: 'unacknowledged write concern coll findOneAndReplace' + operations: + - *startTransaction + - + object: *collection_w0 + name: findOneAndReplace + arguments: + session: *session0 + filter: { _id: 0 } + replacement: { x: 1 } + returnDocument: Before + expectResult: + _id: 0 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 0 } + update: { x: 1 } + new: { $$unsetOrMatches: false } + <<: *transactionCommandArgs + commandName: findAndModify + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0, x: 1 } + - + description: 'unacknowledged write concern coll findOneAndUpdate' + operations: + - *startTransaction + - + object: *collection_w0 + name: findOneAndUpdate + arguments: + session: *session0 + filter: { _id: 0 } + update: { $inc: { x: 1 } } + returnDocument: Before + expectResult: + _id: 0 + - *commitTransaction + expectEvents: + - + client: *client0 + events: + - + commandStartedEvent: + command: + findAndModify: *collection_name + query: { _id: 0 } + update: { $inc: { x: 1 } } + new: { $$unsetOrMatches: false } + <<: *transactionCommandArgs + commandName: findAndModify + databaseName: *database_name + - *commitWithDefaultWCEvent + outcome: + - + collectionName: *collection_name + databaseName: *database_name + documents: + - { _id: 0, x: 1 } diff --git a/src/test/spec/json/unified-test-format/Makefile b/src/test/spec/json/unified-test-format/Makefile index b21d6e426..b4d1bee85 100644 --- a/src/test/spec/json/unified-test-format/Makefile +++ b/src/test/spec/json/unified-test-format/Makefile @@ -1,49 +1,152 @@ -SCHEMA=../schema-1.13.json +SCHEMA=../schema-1.23.json + +.PHONY: all \ + atlas-data-lake \ + auth \ + change-streams \ + client-side-encryption \ + client-side-operations-timeout \ + collection-management \ + command-logging-and-monitoring \ + command-logging-and-monitoring/logging \ + command-logging-and-monitoring/monitoring \ + connection-monitoring-and-pooling \ + connection-monitoring-and-pooling/logging \ + crud \ + gridfs \ + index-management \ + load-balancers \ + read-write-concern \ + retryable-reads \ + retryable-writes \ + run-command \ + server-discovery-and-monitoring \ + server-selection \ + server-selection/logging \ + sessions \ + transactions-convenient-api \ + transactions \ + unified-test-format \ + unified-test-format/invalid \ + unified-test-format/valid-fail \ + unified-test-format/valid-pass \ + versioned-api \ + HAS_AJV + +all: atlas-data-lake \ + auth \ + change-streams \ + client-side-encryption \ + client-side-operations-timeout \ + collection-management \ + command-logging-and-monitoring \ + connection-monitoring-and-pooling \ + crud \ + gridfs \ + index-management \ + load-balancers \ + read-write-concern \ + retryable-reads \ + retryable-writes \ + run-command \ + server-discovery-and-monitoring \ + server-selection \ + sessions \ + transactions-convenient-api \ + transactions \ + unified-test-format \ + versioned-api + +# Keep specifications sorted alphabetically +# When adding a new specification, remember to add it to the all and .PHONY targets above +# For specifications that contain multiple test folders, create a target for each folder +# in addition to a target for the specification itself +atlas-data-lake: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../atlas-data-lake-testing/tests/unified/*.yml" --valid + +auth: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../auth/tests/unified/*.yml" --valid -.PHONY: all invalid valid-fail valid-pass versioned-api load-balancers gridfs transactions crud collection-management sessions command-logging-and-monitoring client-side-operations-timeout HAS_AJV +change-streams: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../change-streams/tests/unified/*.yml" --valid -all: invalid valid-fail valid-pass versioned-api load-balancers gridfs transactions change-streams crud collection-management sessions command-logging-and-monitoring client-side-operations-timeout +client-side-encryption: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../client-side-encryption/tests/unified/*.yml" --valid -invalid: HAS_AJV - @# Redirect stdout to hide expected validation errors - @ajv test -s $(SCHEMA) -d "invalid/*.yml" --invalid > /dev/null && echo "invalid/*.yml passed test" +client-side-operations-timeout: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../client-side-operations-timeout/tests/*.yml" --valid -valid-fail: HAS_AJV - @ajv test -s $(SCHEMA) -d "valid-fail/*.yml" --valid +collection-management: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../collection-management/tests/*.yml" --valid -valid-pass: HAS_AJV - @ajv test -s $(SCHEMA) -d "valid-pass/*.yml" --valid +command-logging-and-monitoring: command-logging-and-monitoring/logging command-logging-and-monitoring/monitoring -versioned-api: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../versioned-api/tests/*.yml" --valid +command-logging-and-monitoring/logging: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../command-logging-and-monitoring/tests/logging/*.yml" --valid -load-balancers: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../load-balancers/tests/*.yml" --valid +command-logging-and-monitoring/monitoring: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../command-logging-and-monitoring/tests/monitoring/*.yml" --valid + +connection-monitoring-and-pooling: connection-monitoring-and-pooling/logging + +connection-monitoring-and-pooling/logging: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../connection-monitoring-and-pooling/tests/logging/*.yml" --valid + +crud: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../crud/tests/unified/*.yml" --valid gridfs: HAS_AJV @ajv test -s $(SCHEMA) -d "../../gridfs/tests/*.yml" --valid -transactions: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../transactions/tests/unified/*.yml" --valid +index-management: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../index-management/tests/*.yml" --valid -change-streams: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../change-streams/tests/unified/*.yml" --valid +load-balancers: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../load-balancers/tests/*.yml" --valid -client-side-operations-timeout: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../client-side-operations-timeout/tests/*.yml" --valid +read-write-concern: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../read-write-concern/tests/operation/*.yml" --valid -crud: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../crud/tests/unified/*.yml" --valid +retryable-reads: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../retryable-reads/tests/unified/*.yml" --valid -collection-management: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../collection-management/tests/*.yml" --valid +retryable-writes: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../retryable-writes/tests/unified/*.yml" --valid + +run-command: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../run-command/tests/unified/*.yml" --valid + +server-discovery-and-monitoring: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../server-discovery-and-monitoring/tests/unified/*.yml" --valid + +server-selection: server-selection/logging + +server-selection/logging: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../server-selection/tests/logging/*.yml" --valid sessions: HAS_AJV @ajv test -s $(SCHEMA) -d "../../sessions/tests/*.yml" --valid -command-logging-and-monitoring: HAS_AJV - @ajv test -s $(SCHEMA) -d "../../command-logging-and-monitoring/tests/logging/*.yml" --valid - @ajv test -s $(SCHEMA) -d "../../command-logging-and-monitoring/tests/monitoring/*.yml" --valid +transactions-convenient-api: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../transactions-convenient-api/tests/unified/*.yml" --valid + +transactions: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../transactions/tests/unified/*.yml" --valid + +unified-test-format: unified-test-format/invalid unified-test-format/valid-fail unified-test-format/valid-pass + +unified-test-format/invalid: HAS_AJV + @# Redirect stdout to hide expected validation errors + @ajv test -s $(SCHEMA) -d "invalid/*.yml" --invalid > /dev/null && echo "invalid/*.yml passed test" + +unified-test-format/valid-fail: HAS_AJV + @ajv test -s $(SCHEMA) -d "valid-fail/*.yml" --valid + +unified-test-format/valid-pass: HAS_AJV + @ajv test -s $(SCHEMA) -d "valid-pass/*.yml" --valid + +versioned-api: HAS_AJV + @ajv test -s $(SCHEMA) -d "../../versioned-api/tests/*.yml" --valid HAS_AJV: @if ! command -v ajv > /dev/null; then \ diff --git a/src/test/spec/json/unified-test-format/README.md b/src/test/spec/json/unified-test-format/README.md new file mode 100644 index 000000000..53e8180e4 --- /dev/null +++ b/src/test/spec/json/unified-test-format/README.md @@ -0,0 +1,32 @@ +# Unified Test Format Tests + +______________________________________________________________________ + +## Introduction + +This directory contains tests for the Unified Test Format's schema and test runner implementation(s). Tests are +organized in the following directories: + +- `invalid`: These files do not validate against the schema and are used to test the schema itself. +- `valid-pass`: These files validate against the schema and should pass when executed with a test runner. +- `valid-fail`: These files validate against the schema but should produce runtime errors or failures when executed with + a test runner. Some do so by violating the "SHOULD" and "SHOULD NOT" guidance in the spec (e.g. referencing an + undefined entity). + +## Validating Test Files + +JSON and YAML test files can be validated using [Ajv](https://ajv.js.org/) and a schema from the parent directory (e.g. +`schema-1.0.json`). + +Test files can be validated individually like so: + +```bash +ajv -s ../schema-1.0.json -d path/to/test.yml +``` + +Ajv can also be used to assert the validity of test files: + +```bash +ajv test -s ../schema-1.0.json -d "invalid/*.yml" --invalid +ajv test -s ../schema-1.0.json -d "valid-*/*.yml" --valid +``` diff --git a/src/test/spec/json/unified-test-format/README.rst b/src/test/spec/json/unified-test-format/README.rst deleted file mode 100644 index dd422f7f0..000000000 --- a/src/test/spec/json/unified-test-format/README.rst +++ /dev/null @@ -1,39 +0,0 @@ -========================= -Unified Test Format Tests -========================= - -.. contents:: - ----- - -Introduction -============ - -This directory contains tests for the Unified Test Format's schema and test -runner implementation(s). Tests are organized in the following directories: - -- ``invalid``: These files do not validate against the schema and are used to - test the schema itself. - -- ``valid-pass``: These files validate against the schema and should pass when - executed with a test runner. - -- ``valid-fail``: These files validate against the schema but should produce - runtime errors or failures when executed with a test runner. Some do so by - violating the "SHOULD" and "SHOULD NOT" guidance in the spec (e.g. referencing - an undefined entity). - -Validating Test Files -===================== - -JSON and YAML test files can be validated using `Ajv `__ -and a schema from the parent directory (e.g. ``schema-1.0.json``). - -Test files can be validated individually like so:: - - ajv -s ../schema-1.0.json -d path/to/test.yml - -Ajv can also be used to assert the validity of test files:: - - ajv test -s ../schema-1.0.json -d "invalid/*.yml" --invalid - ajv test -s ../schema-1.0.json -d "valid-*/*.yml" --valid diff --git a/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.json b/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.json new file mode 100644 index 000000000..9c659c8f7 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.json @@ -0,0 +1,29 @@ +{ + "description": "clientEncryptionOpts-kmsProviders-invalidName", + "schemaVersion": "1.18", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "clientEncryption": { + "id": "clientEncryption0", + "clientEncryptionOpts": { + "keyVaultClient": "client0", + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws:name_with_invalid_character*": {} + } + } + } + } + ], + "tests": [ + { + "description": "", + "operations": [] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.yml b/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.yml new file mode 100644 index 000000000..a8bda7ff8 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/clientEncryptionOpts-kmsProviders-invalidName.yml @@ -0,0 +1,19 @@ +description: clientEncryptionOpts-kmsProviders-invalidName + +schemaVersion: "1.18" + +createEntities: + - client: + id: &client0 client0 + - clientEncryption: + id: &clientEncryption0 clientEncryption0 + clientEncryptionOpts: + keyVaultClient: *client0 + keyVaultNamespace: keyvault.datakeys + kmsProviders: + # The `*` is an invalid character. + "aws:name_with_invalid_character*": {} + +tests: + - description: "" + operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.json b/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.json deleted file mode 100644 index d94863ed1..000000000 --- a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities-minItems", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.yml b/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.yml deleted file mode 100644 index b52648f4f..000000000 --- a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-minItems.yml +++ /dev/null @@ -1,12 +0,0 @@ -description: "entity-client-storeEventsAsEntities-minItems" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: [] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.json b/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.json deleted file mode 100644 index 79f6b85ed..000000000 --- a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities-type", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": 0 - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.yml b/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.yml deleted file mode 100644 index 8230566ae..000000000 --- a/src/test/spec/json/unified-test-format/invalid/entity-client-storeEventsAsEntities-type.yml +++ /dev/null @@ -1,12 +0,0 @@ -description: "entity-client-storeEventsAsEntities-type" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: 0 - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.json b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.json new file mode 100644 index 000000000..f6a305b89 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.json @@ -0,0 +1,29 @@ +{ + "description": "expectedCommandEvent-commandFailedEvent-databaseName-type", + "schemaVersion": "1.15", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandFailedEvent": { + "databaseName": 0 + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.yml b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.yml new file mode 100644 index 000000000..9ab33ad59 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandFailedEvent-databaseName-type.yml @@ -0,0 +1,16 @@ +description: "expectedCommandEvent-commandFailedEvent-databaseName-type" + +schemaVersion: "1.15" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: [] + expectEvents: + - client: *client0 + events: + - commandFailedEvent: + databaseName: 0 diff --git a/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.json b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.json new file mode 100644 index 000000000..47b8c8bb9 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.json @@ -0,0 +1,29 @@ +{ + "description": "expectedCommandEvent-commandSucceededEvent-databaseName-type", + "schemaVersion": "1.15", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandSucceededEvent": { + "databaseName": 0 + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.yml b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.yml new file mode 100644 index 000000000..94adc2b73 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedCommandEvent-commandSucceededEvent-databaseName-type.yml @@ -0,0 +1,16 @@ +description: "expectedCommandEvent-commandSucceededEvent-databaseName-type" + +schemaVersion: "1.15" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: [] + expectEvents: + - client: *client0 + events: + - commandSucceededEvent: + databaseName: 0 diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.json b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.json new file mode 100644 index 000000000..a9f2da9bc --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.json @@ -0,0 +1,24 @@ +{ + "description": "expectedLogMessagesForClient-ignoreExtraMessages-type", + "schemaVersion": "1.16", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [], + "expectLogMessages": [ + { + "client": "client0", + "ignoreExtraMessages": "true", + "messages": [] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.yml b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.yml new file mode 100644 index 000000000..3e11d35d8 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreExtraMessages-type.yml @@ -0,0 +1,15 @@ +description: "expectedLogMessagesForClient-ignoreExtraMessages-type" + +schemaVersion: "1.16" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: [] + expectLogMessages: + - client: *client0 + ignoreExtraMessages: "true" + messages: [] diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.json b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.json new file mode 100644 index 000000000..345faf41f --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.json @@ -0,0 +1,26 @@ +{ + "description": "expectedLogMessagesForClient-ignoreMessages-items", + "schemaVersion": "1.16", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [], + "expectLogMessages": [ + { + "client": "client0", + "messages": [], + "ignoreMessages": [ + 0 + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.yml b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.yml new file mode 100644 index 000000000..f429c3e5b --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-items.yml @@ -0,0 +1,15 @@ +description: "expectedLogMessagesForClient-ignoreMessages-items" + +schemaVersion: "1.16" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: [] + expectLogMessages: + - client: *client0 + messages: [] + ignoreMessages: [0] diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.json b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.json new file mode 100644 index 000000000..4bc2d41db --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.json @@ -0,0 +1,24 @@ +{ + "description": "expectedLogMessagesForClient-ignoreMessages-type", + "schemaVersion": "1.16", + "createEntities": [ + { + "client": { + "id": "client0" + } + } + ], + "tests": [ + { + "description": "foo", + "operations": [], + "expectLogMessages": [ + { + "client": "client0", + "messages": [], + "ignoreMessages": 0 + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.yml b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.yml new file mode 100644 index 000000000..9512f3358 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedLogMessagesForClient-ignoreMessages-type.yml @@ -0,0 +1,15 @@ +description: "expectedLogMessagesForClient-ignoreMessages-type" + +schemaVersion: "1.16" + +createEntities: + - client: + id: &client0 "client0" + +tests: + - description: "foo" + operations: [] + expectLogMessages: + - client: *client0 + messages: [] + ignoreMessages: 0 diff --git a/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.json b/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.json new file mode 100644 index 000000000..ef2686e93 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.json @@ -0,0 +1,23 @@ +{ + "description": "expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties", + "schemaVersion": "1.14", + "tests": [ + { + "description": "foo", + "operations": [], + "expectEvents": [ + { + "client": "client0", + "eventType": "sdam", + "events": [ + { + "topologyDescriptionChangedEvent": { + "foo": "bar" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.yml b/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.yml new file mode 100644 index 000000000..9599d8c85 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties.yml @@ -0,0 +1,13 @@ +description: expectedSdamEvent-topologyDescriptionChangedEvent-additionalProperties + +schemaVersion: '1.14' + +tests: + - description: foo + operations: [] + expectEvents: + - client: client0 + eventType: sdam + events: + - topologyDescriptionChangedEvent: + foo: bar diff --git a/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.json b/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.json new file mode 100644 index 000000000..007f3f304 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.json @@ -0,0 +1,15 @@ +{ + "description": "runOnRequirement-authMechanism-type", + "schemaVersion": "1.19", + "runOnRequirements": [ + { + "authMechanism": 0 + } + ], + "tests": [ + { + "description": "foo", + "operations": [] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.yml b/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.yml new file mode 100644 index 000000000..a0e835612 --- /dev/null +++ b/src/test/spec/json/unified-test-format/invalid/runOnRequirement-authMechanism-type.yml @@ -0,0 +1,10 @@ +description: runOnRequirement-authMechanism-type + +schemaVersion: '1.19' + +runOnRequirements: + - authMechanism: 0 + +tests: + - description: foo + operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.json deleted file mode 100644 index 5357da8d8..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "description": "storeEventsAsEntity-additionalProperties", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events", - "events": [ - "CommandStartedEvent" - ], - "foo": 0 - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.yml deleted file mode 100644 index 5c1b511ef..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-additionalProperties.yml +++ /dev/null @@ -1,15 +0,0 @@ -description: "storeEventsAsEntity-additionalProperties" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: "client0_events" - events: ["CommandStartedEvent"] - foo: 0 - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.json deleted file mode 100644 index ee99a5538..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "description": "storeEventsAsEntity-events-enum", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events", - "events": [ - "foo" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.yml deleted file mode 100644 index efaa05a34..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-enum.yml +++ /dev/null @@ -1,14 +0,0 @@ -description: "storeEventsAsEntity-events-enum" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: "client0_events" - events: ["foo"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.json deleted file mode 100644 index ddab042b1..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "description": "storeEventsAsEntity-events-minItems", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events", - "events": [] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.yml deleted file mode 100644 index c42124768..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-minItems.yml +++ /dev/null @@ -1,14 +0,0 @@ -description: "storeEventsAsEntity-events-minItems" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: "client0_events" - events: [] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.json deleted file mode 100644 index 90b45918c..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "description": "storeEventsAsEntity-events-required", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events" - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.yml deleted file mode 100644 index a6a1069c8..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-required.yml +++ /dev/null @@ -1,13 +0,0 @@ -description: "storeEventsAsEntity-events-required" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: "client0_events" - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.json deleted file mode 100644 index 1b920ebd5..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "description": "storeEventsAsEntity-events-type", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events", - "events": 0 - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.yml deleted file mode 100644 index aef85082b..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-events-type.yml +++ /dev/null @@ -1,14 +0,0 @@ -description: "storeEventsAsEntity-events-type" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: "client0_events" - events: 0 - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.json deleted file mode 100644 index 71387c531..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "description": "storeEventsAsEntity-id-required", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "events": [ - "CommandStartedEvent" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.yml deleted file mode 100644 index ef3342264..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-required.yml +++ /dev/null @@ -1,13 +0,0 @@ -description: "storeEventsAsEntity-id-required" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - events: ["CommandStartedEvent"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.json b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.json deleted file mode 100644 index 4f52dc253..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "description": "storeEventsAsEntity-id-type", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": 0, - "events": [ - "CommandStartedEvent" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.yml b/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.yml deleted file mode 100644 index 1fbfdab69..000000000 --- a/src/test/spec/json/unified-test-format/invalid/storeEventsAsEntity-id-type.yml +++ /dev/null @@ -1,14 +0,0 @@ -description: "storeEventsAsEntity-id-type" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 "client0" - storeEventsAsEntities: - - id: 0 - events: ["CommandStartedEvent"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.json b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.json deleted file mode 100644 index 8c0c4d204..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities-conflict_with_client_id", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0", - "events": [ - "PoolCreatedEvent", - "PoolReadyEvent", - "PoolClearedEvent", - "PoolClosedEvent" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.yml b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.yml deleted file mode 100644 index b7055c9db..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_with_client_id.yml +++ /dev/null @@ -1,16 +0,0 @@ -description: "entity-client-storeEventsAsEntities-conflict_with_client_id" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 client0 - storeEventsAsEntities: - # Using the client ID here will ensure that test runners also detect - # conflicts with the same entity being defined - - id: *client0 - events: ["PoolCreatedEvent", "PoolReadyEvent", "PoolClearedEvent", "PoolClosedEvent"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.json b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.json deleted file mode 100644 index 77bc4abf2..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities-conflict_within_different_array", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "events", - "events": [ - "PoolCreatedEvent", - "PoolReadyEvent", - "PoolClearedEvent", - "PoolClosedEvent" - ] - } - ] - } - }, - { - "client": { - "id": "client1", - "storeEventsAsEntities": [ - { - "id": "events", - "events": [ - "CommandStartedEvent", - "CommandSucceededEvent", - "CommandFailedEvent" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.yml b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.yml deleted file mode 100644 index 8836445c9..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_different_array.yml +++ /dev/null @@ -1,19 +0,0 @@ -description: "entity-client-storeEventsAsEntities-conflict_within_different_array" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 client0 - storeEventsAsEntities: - - id: &events events - events: ["PoolCreatedEvent", "PoolReadyEvent", "PoolClearedEvent", "PoolClosedEvent"] - - client: - id: &client1 client1 - storeEventsAsEntities: - - id: *events - events: ["CommandStartedEvent", "CommandSucceededEvent", "CommandFailedEvent"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.json b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.json deleted file mode 100644 index e1a949988..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities-conflict_within_same_array", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "events", - "events": [ - "PoolCreatedEvent", - "PoolReadyEvent", - "PoolClearedEvent", - "PoolClosedEvent" - ] - }, - { - "id": "events", - "events": [ - "CommandStartedEvent", - "CommandSucceededEvent", - "CommandFailedEvent" - ] - } - ] - } - } - ], - "tests": [ - { - "description": "foo", - "operations": [] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.yml b/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.yml deleted file mode 100644 index 25ee7400d..000000000 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-client-storeEventsAsEntities-conflict_within_same_array.yml +++ /dev/null @@ -1,16 +0,0 @@ -description: "entity-client-storeEventsAsEntities-conflict_within_same_array" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 client0 - storeEventsAsEntities: - - id: &events events - events: ["PoolCreatedEvent", "PoolReadyEvent", "PoolClearedEvent", "PoolClosedEvent"] - - id: *events - events: ["CommandStartedEvent", "CommandSucceededEvent", "CommandFailedEvent"] - -tests: - - description: "foo" - operations: [] diff --git a/src/test/spec/json/unified-test-format/valid-fail/entity-findCursor-malformed.yml b/src/test/spec/json/unified-test-format/valid-fail/entity-findCursor-malformed.yml index d4ebddda0..d7cf4856d 100644 --- a/src/test/spec/json/unified-test-format/valid-fail/entity-findCursor-malformed.yml +++ b/src/test/spec/json/unified-test-format/valid-fail/entity-findCursor-malformed.yml @@ -1,4 +1,4 @@ -# This test is split out into a separate file to accomodate drivers that validate operation structure while decoding +# This test is split out into a separate file to accommodate drivers that validate operation structure while decoding # from JSON/YML. Such drivers fail to decode any files containing invalid operations. Combining this test in a file # with other entity-findCursor valid-fail tests, which test failures that occur during test execution, would prevent # such drivers from decoding the file and running any of the tests. diff --git a/src/test/spec/json/unified-test-format/valid-fail/ignoreResultAndError-malformed.yml b/src/test/spec/json/unified-test-format/valid-fail/ignoreResultAndError-malformed.yml index a2bee4222..4822bbe62 100644 --- a/src/test/spec/json/unified-test-format/valid-fail/ignoreResultAndError-malformed.yml +++ b/src/test/spec/json/unified-test-format/valid-fail/ignoreResultAndError-malformed.yml @@ -1,4 +1,4 @@ -# This test is split out into a separate file to accomodate drivers that validate operation structure while decoding +# This test is split out into a separate file to accommodate drivers that validate operation structure while decoding # from JSON/YML. Such drivers fail to decode any files containing invalid operations. Combining this test in a file # with other ignoreResultAndError valid-fail tests, which test failures that occur during test execution, would prevent # such drivers from decoding the file and running any of the tests. diff --git a/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.json b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.json new file mode 100644 index 000000000..24f6be9cb --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.json @@ -0,0 +1,205 @@ +{ + "description": "operator-matchAsDocument", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "test", + "documents": [ + { + "_id": 1, + "json": "{ \"x\": 1, \"y\": 2 }" + }, + { + "_id": 2, + "json": "1" + }, + { + "_id": 3, + "json": "[ \"foo\" ]" + }, + { + "_id": 4, + "json": "{ \"x\" }" + } + ] + } + ], + "tests": [ + { + "description": "matchAsDocument with non-matching filter", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "json": { + "$$matchAsDocument": { + "x": 1, + "y": "two" + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument evaluates special operators", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "json": { + "$$matchAsDocument": { + "x": 1, + "y": { + "$$exists": false + } + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument does not permit extra fields", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "json": { + "$$matchAsDocument": { + "x": 1 + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument expects JSON object but given scalar", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 2, + "json": { + "$$matchAsDocument": { + "$$matchAsRoot": {} + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument expects JSON object but given array", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 3 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 3, + "json": { + "$$matchAsDocument": { + "$$matchAsRoot": {} + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument fails to decode Extended JSON", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 4 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 4, + "json": { + "$$matchAsDocument": { + "$$matchAsRoot": {} + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.yml b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.yml new file mode 100644 index 000000000..ca5de0056 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsDocument.yml @@ -0,0 +1,88 @@ +description: operator-matchAsDocument + +schemaVersion: "1.13" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, json: '{ "x": 1, "y": 2 }' } + # Documents with non-objects or invalid JSON + - { _id: 2, json: '1' } + - { _id: 3, json: '[ "foo" ]' } + - { _id: 4, json: '{ "x" }' } + +tests: + - description: matchAsDocument with non-matching filter + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, json: { $$matchAsDocument: { x: 1, y: "two" } } } + - + description: matchAsDocument evaluates special operators + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, json: { $$matchAsDocument: { x: 1, y: { $$exists: false } } } } + - + description: matchAsDocument does not permit extra fields + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, json: { $$matchAsDocument: { x: 1 } } } + - + description: matchAsDocument expects JSON object but given scalar + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 2 } + limit: 1 + expectResult: + # The following $$matchAsRoot expression would match any document, so + # this ensures the failure is due to the actual value. + - { _id: 2, json: &match_any_document { $$matchAsDocument: { $$matchAsRoot: { } } } } + - + description: matchAsDocument expects JSON object but given array + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 3 } + limit: 1 + expectResult: + - { _id: 3, json: *match_any_document } + - + description: matchAsDocument fails to decode Extended JSON + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 4 } + limit: 1 + expectResult: + - { _id: 4, json: *match_any_document } diff --git a/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.json b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.json new file mode 100644 index 000000000..ec6309418 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.json @@ -0,0 +1,67 @@ +{ + "description": "operator-matchAsRoot", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "test", + "documents": [ + { + "_id": 1, + "x": { + "y": 2, + "z": 3 + } + } + ] + } + ], + "tests": [ + { + "description": "matchAsRoot with nested document does not match", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$matchAsRoot": { + "y": 3 + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.yml b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.yml new file mode 100644 index 000000000..77e3ce579 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-fail/operator-matchAsRoot.yml @@ -0,0 +1,33 @@ +description: operator-matchAsRoot + +schemaVersion: "1.13" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: { y: 2, z: 3 } } + +tests: + - + description: matchAsRoot with nested document does not match + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, x: { $$matchAsRoot: { y: 3 } } } diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.json b/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.json deleted file mode 100644 index e37e5a1ac..000000000 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "description": "entity-client-storeEventsAsEntities", - "schemaVersion": "1.2", - "createEntities": [ - { - "client": { - "id": "client0", - "storeEventsAsEntities": [ - { - "id": "client0_events", - "events": [ - "CommandStartedEvent", - "CommandSucceededEvent", - "CommandFailedEvent" - ] - } - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "test" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "coll0" - } - } - ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "test", - "documents": [ - { - "_id": 1, - "x": 11 - } - ] - } - ], - "tests": [ - { - "description": "storeEventsAsEntities captures events", - "operations": [ - { - "name": "find", - "object": "collection0", - "arguments": { - "filter": {} - }, - "expectResult": [ - { - "_id": 1, - "x": 11 - } - ] - } - ] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.yml b/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.yml deleted file mode 100644 index 52a9e0ddc..000000000 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-client-storeEventsAsEntities.yml +++ /dev/null @@ -1,37 +0,0 @@ -description: "entity-client-storeEventsAsEntities" - -schemaVersion: "1.2" - -createEntities: - - client: - id: &client0 client0 - storeEventsAsEntities: - - id: client0_events - events: ["CommandStartedEvent", "CommandSucceededEvent", "CommandFailedEvent"] - - database: - id: &database0 database0 - client: *client0 - databaseName: &database0Name test - - collection: - id: &collection0 collection0 - database: *database0 - collectionName: &collection0Name coll0 - -initialData: - - collectionName: *collection0Name - databaseName: *database0Name - documents: - - { _id: 1, x: 11 } - -tests: - # Note: this test does not assert that the events are actually saved to the - # entity since there is presently no assertion syntax to do so. We are only - # asserting that the test executes successfully. - - description: "storeEventsAsEntities captures events" - operations: - - name: find - object: *collection0 - arguments: - filter: {} - expectResult: - - { _id: 1, x: 11 } diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.json b/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.json new file mode 100644 index 000000000..72b74b4a9 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.json @@ -0,0 +1,278 @@ +{ + "description": "entity-commandCursor", + "schemaVersion": "1.3", + "createEntities": [ + { + "client": { + "id": "client", + "useMultipleMongoses": false, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "db", + "client": "client", + "databaseName": "db" + } + }, + { + "collection": { + "id": "collection", + "database": "db", + "collectionName": "collection" + } + } + ], + "initialData": [ + { + "collectionName": "collection", + "databaseName": "db", + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "tests": [ + { + "description": "runCursorCommand creates and exhausts cursor by running getMores", + "operations": [ + { + "name": "runCursorCommand", + "object": "db", + "arguments": { + "commandName": "find", + "batchSize": 2, + "command": { + "find": "collection", + "filter": {}, + "batchSize": 2 + } + }, + "expectResult": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ] + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "filter": {}, + "batchSize": 2, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + }, + { + "commandStartedEvent": { + "command": { + "getMore": { + "$$type": [ + "int", + "long" + ] + }, + "collection": "collection", + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "getMore" + } + } + ] + } + ] + }, + { + "description": "createCommandCursor creates a cursor and stores it as an entity that can be iterated one document at a time", + "operations": [ + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "commandName": "find", + "batchSize": 2, + "command": { + "find": "collection", + "filter": {}, + "batchSize": 2 + } + }, + "saveResultAsEntity": "myRunCommandCursor" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 1, + "x": 11 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 2, + "x": 22 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 3, + "x": 33 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 4, + "x": 44 + } + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 5, + "x": 55 + } + } + ] + }, + { + "description": "createCommandCursor's cursor can be closed and will perform a killCursors operation", + "operations": [ + { + "name": "createCommandCursor", + "object": "db", + "arguments": { + "commandName": "find", + "batchSize": 2, + "command": { + "find": "collection", + "filter": {}, + "batchSize": 2 + } + }, + "saveResultAsEntity": "myRunCommandCursor" + }, + { + "name": "iterateUntilDocumentOrError", + "object": "myRunCommandCursor", + "expectResult": { + "_id": 1, + "x": 11 + } + }, + { + "name": "close", + "object": "myRunCommandCursor" + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "command": { + "find": "collection", + "filter": {}, + "batchSize": 2, + "$db": "db", + "lsid": { + "$$exists": true + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "killCursors": "collection", + "cursors": { + "$$type": "array" + } + }, + "commandName": "killCursors" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.yml b/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.yml new file mode 100644 index 000000000..3becf2095 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-commandCursor.yml @@ -0,0 +1,115 @@ +description: entity-commandCursor +schemaVersion: '1.3' +createEntities: + - client: + id: &client client + useMultipleMongoses: false + observeEvents: [commandStartedEvent] + - database: + id: &db db + client: *client + databaseName: *db + - collection: + id: &collection collection + database: *db + collectionName: *collection +initialData: + - collectionName: collection + databaseName: *db + documents: &documents + - { _id: 1, x: 11 } + - { _id: 2, x: 22 } + - { _id: 3, x: 33 } + - { _id: 4, x: 44 } + - { _id: 5, x: 55 } +tests: + - description: runCursorCommand creates and exhausts cursor by running getMores + operations: + - name: runCursorCommand + object: *db + arguments: + commandName: find + batchSize: 2 + command: { find: *collection, filter: {}, batchSize: 2 } + expectResult: *documents + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + find: *collection + filter: {} + batchSize: 2 + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$exists: true } + commandName: getMore + - commandStartedEvent: + command: + getMore: { $$type: [int, long] } + collection: *collection + $db: *db + lsid: { $$exists: true } + commandName: getMore + + - description: createCommandCursor creates a cursor and stores it as an entity that can be iterated one document at a time + operations: + - name: createCommandCursor + object: *db + arguments: + commandName: find + batchSize: 2 + command: { find: *collection, filter: {}, batchSize: 2 } + saveResultAsEntity: &myRunCommandCursor myRunCommandCursor + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 1, x: 11 } + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 2, x: 22 } + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 3, x: 33 } + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 4, x: 44 } + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 5, x: 55 } + + - description: createCommandCursor's cursor can be closed and will perform a killCursors operation + operations: + - name: createCommandCursor + object: *db + arguments: + commandName: find + batchSize: 2 + command: { find: *collection, filter: {}, batchSize: 2 } + saveResultAsEntity: myRunCommandCursor + - name: iterateUntilDocumentOrError + object: *myRunCommandCursor + expectResult: { _id: 1, x: 11 } + - name: close + object: *myRunCommandCursor + expectEvents: + - client: *client + events: + - commandStartedEvent: + command: + find: *collection + filter: {} + batchSize: 2 + $db: *db + lsid: { $$exists: true } + commandName: find + - commandStartedEvent: + command: + killCursors: *collection + cursors: { $$type: array } + commandName: killCursors diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.json b/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.json index 88fc28e34..b17ae78b9 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.json +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.json @@ -93,7 +93,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.yml b/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.yml index d84fd7689..508e594a5 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.yml +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-cursor-iterateOnce.yml @@ -54,6 +54,6 @@ tests: databaseName: *database0Name - commandStartedEvent: command: - getMore: { $$type: long } + getMore: { $$type: [ int, long ] } collection: *collection0Name commandName: getMore diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.json b/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.json index 85b8f69d7..6f955d81f 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.json +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.json @@ -109,7 +109,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "ns": { "$$type": "string" @@ -126,7 +129,10 @@ "commandStartedEvent": { "command": { "getMore": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "collection": "coll0" }, @@ -138,7 +144,10 @@ "reply": { "cursor": { "id": { - "$$type": "long" + "$$type": [ + "int", + "long" + ] }, "ns": { "$$type": "string" diff --git a/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.yml b/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.yml index 61c9f8835..3ecdf6da1 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.yml +++ b/src/test/spec/json/unified-test-format/valid-pass/entity-find-cursor.yml @@ -61,19 +61,19 @@ tests: - commandSucceededEvent: reply: cursor: - id: { $$type: long } + id: { $$type: [ int, long ] } ns: { $$type: string } firstBatch: { $$type: array } commandName: find - commandStartedEvent: command: - getMore: { $$type: long } + getMore: { $$type: [ int, long ] } collection: *collection0Name commandName: getMore - commandSucceededEvent: reply: cursor: - id: { $$type: long } + id: { $$type: [ int, long ] } ns: { $$type: string } nextBatch: { $$type: array } commandName: getMore diff --git a/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.json b/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.json new file mode 100644 index 000000000..9c6beda58 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.json @@ -0,0 +1,74 @@ +{ + "description": "expectedError-isClientError", + "schemaVersion": "1.3", + "runOnRequirements": [ + { + "minServerVersion": "4.0", + "topologies": [ + "single", + "replicaset" + ] + }, + { + "minServerVersion": "4.1.7", + "topologies": [ + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "useMultipleMongoses": false + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + } + ], + "tests": [ + { + "description": "isClientError considers network errors", + "operations": [ + { + "name": "failPoint", + "object": "testRunner", + "arguments": { + "client": "client0", + "failPoint": { + "configureFailPoint": "failCommand", + "mode": { + "times": 1 + }, + "data": { + "failCommands": [ + "ping" + ], + "closeConnection": true + } + } + } + }, + { + "name": "runCommand", + "object": "database0", + "arguments": { + "commandName": "ping", + "command": { + "ping": 1 + } + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.yml b/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.yml new file mode 100644 index 000000000..3bc12e73f --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/expectedError-isClientError.yml @@ -0,0 +1,39 @@ +description: "expectedError-isClientError" + +schemaVersion: "1.3" + +runOnRequirements: + - minServerVersion: "4.0" + topologies: [single, replicaset] + - minServerVersion: "4.1.7" + topologies: [sharded, load-balanced] + +createEntities: + - client: + id: &client0 client0 + useMultipleMongoses: false + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + +tests: + - description: "isClientError considers network errors" + operations: + - name: failPoint + object: testRunner + arguments: + client: *client0 + failPoint: + configureFailPoint: failCommand + mode: { times: 1 } + data: + failCommands: [ ping ] + closeConnection: true + - name: runCommand + object: *database0 + arguments: + commandName: ping + command: { ping: 1 } + expectError: + isClientError: true diff --git a/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json b/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json new file mode 100644 index 000000000..cf7bd6082 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.json @@ -0,0 +1,68 @@ +{ + "description": "expectedEventsForClient-topologyDescriptionChangedEvent", + "schemaVersion": "1.20", + "runOnRequirements": [ + { + "topologies": [ + "replicaset" + ], + "minServerVersion": "4.4" + } + ], + "tests": [ + { + "description": "can assert on values of newDescription and previousDescription fields", + "operations": [ + { + "name": "createEntities", + "object": "testRunner", + "arguments": { + "entities": [ + { + "client": { + "id": "client", + "uriOptions": { + "directConnection": true + }, + "observeEvents": [ + "topologyDescriptionChangedEvent" + ] + } + } + ] + } + }, + { + "name": "waitForEvent", + "object": "testRunner", + "arguments": { + "client": "client", + "event": { + "topologyDescriptionChangedEvent": {} + }, + "count": 1 + } + } + ], + "expectEvents": [ + { + "client": "client", + "eventType": "sdam", + "ignoreExtraEvents": true, + "events": [ + { + "topologyDescriptionChangedEvent": { + "previousDescription": { + "type": "Unknown" + }, + "newDescription": { + "type": "Single" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.yml b/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.yml new file mode 100644 index 000000000..c8dacc391 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/expectedEventsForClient-topologyDescriptionChangedEvent.yml @@ -0,0 +1,40 @@ +description: "expectedEventsForClient-topologyDescriptionChangedEvent" + +schemaVersion: "1.20" + +runOnRequirements: + - topologies: + - replicaset + minServerVersion: "4.4" # awaitable hello + +tests: + - description: "can assert on values of newDescription and previousDescription fields" + operations: + - name: createEntities + object: testRunner + arguments: + entities: + - client: + id: &client client + uriOptions: + directConnection: true + observeEvents: + - topologyDescriptionChangedEvent + - name: waitForEvent + object: testRunner + arguments: + client: *client + event: + topologyDescriptionChangedEvent: {} + count: 1 + expectEvents: + - client: *client + eventType: sdam + ignoreExtraEvents: true + events: + - topologyDescriptionChangedEvent: + previousDescription: + type: "Unknown" + newDescription: + type: "Single" + diff --git a/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.json b/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.json deleted file mode 100644 index 4de65c583..000000000 --- a/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "description": "matches-lte-operator", - "schemaVersion": "1.9", - "createEntities": [ - { - "client": { - "id": "client0", - "observeEvents": [ - "commandStartedEvent" - ] - } - }, - { - "database": { - "id": "database0", - "client": "client0", - "databaseName": "database0Name" - } - }, - { - "collection": { - "id": "collection0", - "database": "database0", - "collectionName": "coll0" - } - } - ], - "initialData": [ - { - "collectionName": "coll0", - "databaseName": "database0Name", - "documents": [] - } - ], - "tests": [ - { - "description": "special lte matching operator", - "operations": [ - { - "name": "insertOne", - "object": "collection0", - "arguments": { - "document": { - "_id": 1, - "y": 1 - } - } - } - ], - "expectEvents": [ - { - "client": "client0", - "events": [ - { - "commandStartedEvent": { - "command": { - "insert": "coll0", - "documents": [ - { - "_id": { - "$$lte": 1 - }, - "y": { - "$$lte": 2 - } - } - ] - }, - "commandName": "insert", - "databaseName": "database0Name" - } - } - ] - } - ] - } - ] -} diff --git a/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.yml b/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.yml deleted file mode 100644 index 4bec571f0..000000000 --- a/src/test/spec/json/unified-test-format/valid-pass/matches-lte-operator.yml +++ /dev/null @@ -1,41 +0,0 @@ -description: matches-lte-operator - -# Note: $$lte is not technically in the 1.8 schema but was introduced at the same time. -schemaVersion: "1.9" - -createEntities: - - client: - id: &client0 client0 - observeEvents: [ commandStartedEvent ] - - database: - id: &database0 database0 - client: *client0 - databaseName: &database0Name database0Name - - collection: - id: &collection0 collection0 - database: *database0 - collectionName: &collection0Name coll0 - -initialData: - - collectionName: *collection0Name - databaseName: *database0Name - documents: [] - -tests: - - description: special lte matching operator - operations: - - name: insertOne - object: *collection0 - arguments: - document: { _id : 1, y: 1 } - expectEvents: - - client: *client0 - events: - - commandStartedEvent: - command: - insert: *collection0Name - documents: - # We can make exact assertions here but we use the $$lte operator to ensure drivers support it. - - { _id: { $$lte: 1 }, y: { $$lte: 2 } } - commandName: insert - databaseName: *database0Name diff --git a/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.json b/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.json new file mode 100644 index 000000000..93b25c983 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.json @@ -0,0 +1,10 @@ +{ + "description": "operation-empty_array", + "schemaVersion": "1.0", + "tests": [ + { + "description": "Empty operations array", + "operations": [] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.yml b/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.yml new file mode 100644 index 000000000..35d5ba371 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operation-empty_array.yml @@ -0,0 +1,7 @@ +description: "operation-empty_array" + +schemaVersion: "1.0" + +tests: + - description: "Empty operations array" + operations: [] diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-lte.json b/src/test/spec/json/unified-test-format/valid-pass/operator-lte.json new file mode 100644 index 000000000..7a6a8057a --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-lte.json @@ -0,0 +1,88 @@ +{ + "description": "operator-lte", + "schemaVersion": "1.9", + "createEntities": [ + { + "client": { + "id": "client0", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "database0Name" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "database0Name", + "documents": [] + } + ], + "tests": [ + { + "description": "special lte matching operator", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": 2, + "y": 3, + "z": 4 + } + } + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "insert": "coll0", + "documents": [ + { + "_id": { + "$$lte": 2 + }, + "x": { + "$$lte": 2.1 + }, + "y": { + "$$lte": { + "$numberLong": "3" + } + }, + "z": { + "$$lte": 4 + } + } + ] + }, + "commandName": "insert", + "databaseName": "database0Name" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-lte.yml b/src/test/spec/json/unified-test-format/valid-pass/operator-lte.yml new file mode 100644 index 000000000..87c4ece92 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-lte.yml @@ -0,0 +1,41 @@ +description: operator-lte + +# Note: $$lte was introduced alongside schema changes for CSOT +schemaVersion: "1.9" + +createEntities: + - client: + id: &client0 client0 + observeEvents: [ commandStartedEvent ] + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name database0Name + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - description: special lte matching operator + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id : 1, x: 2, y: 3, z: 4 } + expectEvents: + - client: *client0 + events: + - commandStartedEvent: + command: + insert: *collection0Name + documents: + # We can make exact assertions here but we use the $$lte operator to ensure drivers support it. + - { _id: { $$lte: 2 }, x: { $$lte: 2.1 }, y: { $$lte: { $numberLong: "3"} }, z: { $$lte: 4 } } + commandName: insert + databaseName: *database0Name diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.json b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.json new file mode 100644 index 000000000..fd8b514d4 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.json @@ -0,0 +1,124 @@ +{ + "description": "operator-matchAsDocument", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "test", + "documents": [ + { + "_id": 1, + "json": "{ \"x\": 1, \"y\": 2.0 }" + }, + { + "_id": 2, + "json": "{ \"x\": { \"$oid\": \"57e193d7a9cc81b4027498b5\" } }" + } + ] + } + ], + "tests": [ + { + "description": "matchAsDocument performs flexible numeric comparisons", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "json": { + "$$matchAsDocument": { + "x": 1, + "y": 2 + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument evaluates special operators", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "json": { + "$$matchAsDocument": { + "x": 1, + "y": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "matchAsDocument decodes Extended JSON", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 2, + "json": { + "$$matchAsDocument": { + "x": { + "$$type": "objectId" + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.yml b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.yml new file mode 100644 index 000000000..9a811faaa --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsDocument.yml @@ -0,0 +1,54 @@ +description: operator-matchAsDocument + +schemaVersion: "1.13" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, json: '{ "x": 1, "y": 2.0 }' } + - { _id: 2, json: '{ "x": { "$oid": "57e193d7a9cc81b4027498b5" } }' } + +tests: + - + description: matchAsDocument performs flexible numeric comparisons + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, json: { $$matchAsDocument: { x: 1.0, y: 2 } } } + - + description: matchAsDocument evaluates special operators + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, json: { $$matchAsDocument: { x: 1, y: { $$exists: true } } } } + - + description: matchAsDocument decodes Extended JSON + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 2 } + limit: 1 + expectResult: + - { _id: 2, json: { $$matchAsDocument: { x: { $$type: "objectId" } } } } diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.json b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.json new file mode 100644 index 000000000..1966e3b37 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.json @@ -0,0 +1,151 @@ +{ + "description": "operator-matchAsRoot", + "schemaVersion": "1.13", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "test", + "documents": [ + { + "_id": 1, + "x": { + "y": 2, + "z": 3 + } + }, + { + "_id": 2, + "json": "{ \"x\": 1, \"y\": 2 }" + } + ] + } + ], + "tests": [ + { + "description": "matchAsRoot with nested document", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$matchAsRoot": { + "y": 2 + } + } + } + ] + } + ] + }, + { + "description": "matchAsRoot performs flexible numeric comparisons", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$matchAsRoot": { + "y": 2 + } + } + } + ] + } + ] + }, + { + "description": "matchAsRoot evaluates special operators", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$matchAsRoot": { + "y": 2, + "z": { + "$$exists": true + } + } + } + } + ] + } + ] + }, + { + "description": "matchAsRoot with matchAsDocument", + "operations": [ + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 2 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 2, + "json": { + "$$matchAsDocument": { + "$$matchAsRoot": { + "x": 1 + } + } + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.yml b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.yml new file mode 100644 index 000000000..bbf738f04 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-matchAsRoot.yml @@ -0,0 +1,64 @@ +description: operator-matchAsRoot + +schemaVersion: "1.13" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: + - { _id: 1, x: { y: 2, z: 3 } } + - { _id: 2, json: '{ "x": 1, "y": 2 }' } + +tests: + - + description: matchAsRoot with nested document + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, x: { $$matchAsRoot: { y: 2 } } } + - + description: matchAsRoot performs flexible numeric comparisons + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, x: { $$matchAsRoot: { y: 2.0 } } } + - + description: matchAsRoot evaluates special operators + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 1 } + limit: 1 + expectResult: + - { _id: 1, x: { $$matchAsRoot: { y: 2, z: { $$exists: true } } } } + - + description: matchAsRoot with matchAsDocument + operations: + - name: find + object: *collection0 + arguments: + filter: { _id : 2 } + limit: 1 + expectResult: + - { _id: 2, json: { $$matchAsDocument: { $$matchAsRoot: { x: 1 } } } } diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.json b/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.json new file mode 100644 index 000000000..e628d0d77 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.json @@ -0,0 +1,174 @@ +{ + "description": "operator-type-number_alias", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client0" + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "test" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "coll0" + } + } + ], + "initialData": [ + { + "collectionName": "coll0", + "databaseName": "test", + "documents": [] + } + ], + "tests": [ + { + "description": "type number alias matches int32", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": { + "$numberInt": "2147483647" + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$type": "number" + } + } + ] + } + ] + }, + { + "description": "type number alias matches int64", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": { + "$numberLong": "9223372036854775807" + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$type": "number" + } + } + ] + } + ] + }, + { + "description": "type number alias matches double", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": { + "$numberDouble": "2.71828" + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$type": "number" + } + } + ] + } + ] + }, + { + "description": "type number alias matches decimal128", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "_id": 1, + "x": { + "$numberDecimal": "3.14159" + } + } + } + }, + { + "name": "find", + "object": "collection0", + "arguments": { + "filter": { + "_id": 1 + }, + "limit": 1 + }, + "expectResult": [ + { + "_id": 1, + "x": { + "$$type": "number" + } + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.yml b/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.yml new file mode 100644 index 000000000..04357a024 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/operator-type-number_alias.yml @@ -0,0 +1,61 @@ +description: operator-type-number_alias + +schemaVersion: "1.0" + +createEntities: + - client: + id: &client0 client0 + - database: + id: &database0 database0 + client: *client0 + databaseName: &database0Name test + - collection: + id: &collection0 collection0 + database: *database0 + collectionName: &collection0Name coll0 + +initialData: + - collectionName: *collection0Name + databaseName: *database0Name + documents: [] + +tests: + - + description: type number alias matches int32 + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, x: { $numberInt: "2147483647" } } + - &find + name: find + object: *collection0 + arguments: + filter: { _id: 1 } + limit: 1 + expectResult: + - { _id: 1, x: { $$type: "number" } } + - + description: type number alias matches int64 + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, x: { $numberLong: "9223372036854775807" } } + - *find + - + description: type number alias matches double + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, x: { $numberDouble: "2.71828" } } + - *find + - + description: type number alias matches decimal128 + operations: + - name: insertOne + object: *collection0 + arguments: + document: { _id: 1, x: { $numberDecimal: "3.14159" } } + - *find diff --git a/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.json b/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.json new file mode 100644 index 000000000..309d1d3b4 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.json @@ -0,0 +1,193 @@ +{ + "description": "poc-queryable-encryption", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0", + "csfle": true, + "topologies": [ + "replicaset", + "load-balanced", + "sharded" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + } + } + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "poc-queryable-encryption" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "encrypted" + } + }, + { + "client": { + "id": "client1" + } + }, + { + "database": { + "id": "unencryptedDB", + "client": "client1", + "databaseName": "poc-queryable-encryption" + } + }, + { + "collection": { + "id": "unencryptedColl", + "database": "unencryptedDB", + "collectionName": "encrypted" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "poc-queryable-encryption", + "collectionName": "encrypted", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + } + ] + } + } + } + ], + "tests": [ + { + "description": "insert, replace, and find with queryable encryption", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": 11 + } + } + }, + { + "object": "encryptedColl", + "name": "replaceOne", + "arguments": { + "filter": { + "encryptedInt": 11 + }, + "replacement": { + "encryptedInt": 22 + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "encryptedInt": 22 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedInt": 22 + } + ] + }, + { + "object": "unencryptedColl", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rhS16TJojgDDBtbluxBokvcotP1mQTGeYpNt8xd3MJQ=", + "subType": "00" + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.yml b/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.yml new file mode 100644 index 000000000..797904ee9 --- /dev/null +++ b/src/test/spec/json/unified-test-format/valid-pass/poc-queryable-encryption.yml @@ -0,0 +1,86 @@ +description: poc-queryable-encryption + +schemaVersion: "1.23" + +runOnRequirements: + - minServerVersion: "7.0" + csfle: true + # QE is not supported on standalone servers + topologies: [ replicaset, load-balanced, sharded ] + +createEntities: + - client: + id: &client0 client0 + autoEncryptOpts: + keyVaultNamespace: keyvault.datakeys + kmsProviders: + local: + key: Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk + - database: + id: &encryptedDB encryptedDB + client: *client0 + databaseName: &encryptedDBName poc-queryable-encryption + - collection: + id: &encryptedColl encryptedColl + database: *encryptedDB + collectionName: &encryptedCollName encrypted + - client: + id: &client1 client1 + - database: + id: &unencryptedDB unencryptedDB + client: *client1 + databaseName: *encryptedDBName + - collection: + id: &unencryptedColl unencryptedColl + database: *unencryptedDB + collectionName: *encryptedCollName + +initialData: + - databaseName: keyvault + collectionName: datakeys + documents: + - _id: &keyid { $binary: { base64: EjRWeBI0mHYSNBI0VniQEg==, subType: "04" } } + keyMaterial: { $binary: { base64: sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==, subType: "00" } } + creationDate: { $date: { $numberLong: "1641024000000" } } + updateDate: { $date: { $numberLong: "1641024000000" } } + status: 1 + masterKey: + provider: local + - databaseName: *encryptedDBName + collectionName: *encryptedCollName + documents: [] + createOptions: + encryptedFields: + fields: + - keyId: *keyid + path: 'encryptedInt' + bsonType: 'int' + queries: {'queryType': 'equality', 'contention': {'$numberLong': '0'}} + +tests: + - description: insert, replace, and find with queryable encryption + operations: + - object: *encryptedColl + name: insertOne + arguments: + document: + _id: 1 + encryptedInt: 11 + - object: *encryptedColl + name: replaceOne + arguments: + filter: { encryptedInt: 11 } + replacement: { encryptedInt: 22 } + - object: *encryptedColl + name: find + arguments: + filter: { encryptedInt: 22 } + expectResult: + - _id: 1 + encryptedInt: 22 + - object: *unencryptedColl + name: find + arguments: + filter: {} + expectResult: + - { _id: 1, encryptedInt: { $$type: binData }, __safeContent__: [ { "$binary" : { "base64" : "rhS16TJojgDDBtbluxBokvcotP1mQTGeYpNt8xd3MJQ=", "subType" : "00" } } ] } \ No newline at end of file diff --git a/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.json b/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.json index da72b7f27..f19aa3f9d 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.json +++ b/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.json @@ -1,14 +1,6 @@ { "description": "poc-retryable-writes", "schemaVersion": "1.0", - "runOnRequirements": [ - { - "minServerVersion": "3.6", - "topologies": [ - "replicaset" - ] - } - ], "createEntities": [ { "client": { @@ -79,6 +71,14 @@ "tests": [ { "description": "FindOneAndUpdate is committed on first attempt", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], "operations": [ { "name": "failPoint", @@ -132,6 +132,14 @@ }, { "description": "FindOneAndUpdate is not committed on first attempt", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], "operations": [ { "name": "failPoint", @@ -188,6 +196,14 @@ }, { "description": "FindOneAndUpdate is never committed", + "runOnRequirements": [ + { + "minServerVersion": "3.6", + "topologies": [ + "replicaset" + ] + } + ], "operations": [ { "name": "failPoint", @@ -245,14 +261,9 @@ "description": "InsertMany succeeds after PrimarySteppedDown", "runOnRequirements": [ { - "minServerVersion": "4.0", - "topologies": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", + "minServerVersion": "4.3.1", "topologies": [ + "replicaset", "sharded" ] } @@ -406,14 +417,9 @@ "description": "InsertOne fails after multiple retryable writeConcernErrors", "runOnRequirements": [ { - "minServerVersion": "4.0", - "topologies": [ - "replicaset" - ] - }, - { - "minServerVersion": "4.1.7", + "minServerVersion": "4.3.1", "topologies": [ + "replicaset", "sharded" ] } @@ -433,6 +439,9 @@ "failCommands": [ "insert" ], + "errorLabels": [ + "RetryableWriteError" + ], "writeConcernError": { "code": 91, "errmsg": "Replication is being shut down" diff --git a/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.yml b/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.yml index 012c41a4b..c11e8b6ef 100644 --- a/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.yml +++ b/src/test/spec/json/unified-test-format/valid-pass/poc-retryable-writes.yml @@ -2,10 +2,6 @@ description: "poc-retryable-writes" schemaVersion: "1.0" -runOnRequirements: - - minServerVersion: "3.6" - topologies: [ replicaset ] - createEntities: - client: id: &client0 client0 @@ -42,6 +38,9 @@ initialData: tests: - description: "FindOneAndUpdate is committed on first attempt" + runOnRequirements: &onPrimaryTransactionalWrite_requirements + - minServerVersion: "3.6" + topologies: [ replicaset ] operations: - name: failPoint object: testRunner @@ -65,6 +64,7 @@ tests: - { _id: 2, x: 22 } - description: "FindOneAndUpdate is not committed on first attempt" + runOnRequirements: *onPrimaryTransactionalWrite_requirements operations: - name: failPoint object: testRunner @@ -89,6 +89,7 @@ tests: - { _id: 2, x: 22 } - description: "FindOneAndUpdate is never committed" + runOnRequirements: *onPrimaryTransactionalWrite_requirements operations: - name: failPoint object: testRunner @@ -113,11 +114,9 @@ tests: - { _id: 2, x: 22 } - description: "InsertMany succeeds after PrimarySteppedDown" - runOnRequirements: &failCommand_requirements - - minServerVersion: "4.0" - topologies: [ replicaset ] - - minServerVersion: "4.1.7" - topologies: [ sharded ] + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option + topologies: [ replicaset, sharded ] operations: - name: failPoint object: testRunner @@ -129,7 +128,7 @@ tests: data: failCommands: [ insert ] errorCode: 189 # PrimarySteppedDown - errorLabels: [ RetryableWriteError ] + errorLabels: [ RetryableWriteError ] - name: insertMany object: *collection0 arguments: @@ -151,7 +150,11 @@ tests: - { _id: 4, x: 44 } - description: "InsertOne fails after connection failure when retryWrites option is false" - runOnRequirements: *failCommand_requirements + runOnRequirements: # failCommand + - minServerVersion: "4.0" + topologies: [ replicaset ] + - minServerVersion: "4.1.7" + topologies: [ sharded ] operations: - name: failPoint object: testRunner @@ -179,7 +182,9 @@ tests: - { _id: 2, x: 22 } - description: "InsertOne fails after multiple retryable writeConcernErrors" - runOnRequirements: *failCommand_requirements + runOnRequirements: + - minServerVersion: "4.3.1" # failCommand errorLabels option + topologies: [ replicaset, sharded ] operations: - name: failPoint object: testRunner @@ -190,6 +195,7 @@ tests: mode: { times: 2 } data: failCommands: [ insert ] + errorLabels: [ RetryableWriteError ] writeConcernError: code: 91 # ShutdownInProgress errmsg: "Replication is being shut down" diff --git a/src/test/spec/json/uri-options/README.md b/src/test/spec/json/uri-options/README.md new file mode 100644 index 000000000..bda997051 --- /dev/null +++ b/src/test/spec/json/uri-options/README.md @@ -0,0 +1,43 @@ +# URI Options Tests + +The YAML and JSON files in this directory tree are platform-independent tests that drivers can use to prove their +conformance to the URI Options spec. + +These tests use the same format as the Connection String spec tests. + +## Version + +Files in the "specifications" repository have no version scheme. They are not tied to a MongoDB server version. + +## Format + +Each YAML file contains an object with a single `tests` key. This key is an array of test case objects, each of which +have the following keys: + +- `description`: A string describing the test. +- `uri`: A string containing the URI to be parsed. +- `valid`: A boolean indicating if the URI should be considered valid. +- `warning`: A boolean indicating whether URI parsing should emit a warning. +- `hosts`: Included for compatibility with the Connection String spec tests. This will always be `~`. +- `auth`: Included for compatibility with the Connection String spec tests. This will always be `~`. +- `options`: An object containing key/value pairs for each parsed query string option. + +If a test case includes a null value for one of these keys (e.g. `auth: ~`, `hosts: ~`), no assertion is necessary. This +both simplifies parsing of the test files (keys should always exist) and allows flexibility for drivers that might +substitute default values *during* parsing (e.g. omitted `hosts` could be parsed as `["localhost"]`). + +The `valid` and `warning` fields are boolean in order to keep the tests flexible. We are not concerned with asserting +the format of specific error or warnings messages strings. + +Under normal circumstances, it should not be necessary to specify both `valid: false` and `warning: true`. Typically, a +URI test case will either yield an error (e.g. options conflict) or a warning (e.g. invalid type or value for an +option), but not both. + +### Use as unit tests + +Testing whether a URI is valid or not requires testing whether URI parsing (or MongoClient construction) causes a +warning due to a URI option being invalid and asserting that the options parsed from the URI match those listed in the +`options` field. + +Note that there are tests for each of the options marked as optional; drivers will need to implement logic to skip over +the optional tests that they don't implement. diff --git a/src/test/spec/json/uri-options/README.rst b/src/test/spec/json/uri-options/README.rst deleted file mode 100644 index b1b4877cd..000000000 --- a/src/test/spec/json/uri-options/README.rst +++ /dev/null @@ -1,52 +0,0 @@ -======================= -URI Options Tests -======================= - -The YAML and JSON files in this directory tree are platform-independent tests -that drivers can use to prove their conformance to the URI Options spec. - -These tests use the same format as the Connection String spec tests. - -Version -------- - -Files in the "specifications" repository have no version scheme. They are not -tied to a MongoDB server version. - -Format ------- - -Each YAML file contains an object with a single ``tests`` key. This key is an -array of test case objects, each of which have the following keys: - -- ``description``: A string describing the test. -- ``uri``: A string containing the URI to be parsed. -- ``valid``: A boolean indicating if the URI should be considered valid. - This will always be true, as the Connection String spec tests the validity of the structure, but - it's still included to make it easier to reuse the connection string spec test runners that - drivers already have. -- ``warning``: A boolean indicating whether URI parsing should emit a warning. -- ``hosts``: Included for compatibility with the Connection String spec tests. This will always be ``~``. -- ``auth``: Included for compatibility with the Connection String spec tests. This will always be ``~``. -- ``options``: An object containing key/value pairs for each parsed query string - option. - -If a test case includes a null value for one of these keys (e.g. ``auth: ~``, -``hosts: ~``), no assertion is necessary. This both simplifies parsing of the -test files (keys should always exist) and allows flexibility for drivers that -might substitute default values *during* parsing (e.g. omitted ``hosts`` could be -parsed as ``["localhost"]``). - -The ``valid`` and ``warning`` fields are boolean in order to keep the tests -flexible. We are not concerned with asserting the format of specific error or -warnings messages strings. - -Use as unit tests -================= - -Testing whether a URI is valid or not requires testing whether URI parsing (or -MongoClient construction) causes a warning due to a URI option being invalid and asserting that the -options parsed from the URI match those listed in the ``options`` field. - -Note that there are tests for each of the options marked as optional; drivers will need to implement -logic to skip over the optional tests that they don’t implement. diff --git a/src/test/spec/json/uri-options/auth-options.json b/src/test/spec/json/uri-options/auth-options.json index fadbac35d..d7fa14a13 100644 --- a/src/test/spec/json/uri-options/auth-options.json +++ b/src/test/spec/json/uri-options/auth-options.json @@ -2,7 +2,7 @@ "tests": [ { "description": "Valid auth options are parsed correctly (GSSAPI)", - "uri": "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authSource=$external", + "uri": "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authSource=$external", "valid": true, "warning": false, "hosts": null, @@ -11,7 +11,8 @@ "authMechanism": "GSSAPI", "authMechanismProperties": { "SERVICE_NAME": "other", - "CANONICALIZE_HOST_NAME": true + "SERVICE_HOST": "example.com", + "CANONICALIZE_HOST_NAME": "forward" }, "authSource": "$external" } diff --git a/src/test/spec/json/uri-options/auth-options.yml b/src/test/spec/json/uri-options/auth-options.yml index cd63b0738..4a46516f1 100644 --- a/src/test/spec/json/uri-options/auth-options.yml +++ b/src/test/spec/json/uri-options/auth-options.yml @@ -1,7 +1,7 @@ tests: - description: "Valid auth options are parsed correctly (GSSAPI)" - uri: "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authSource=$external" + uri: "mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:forward,SERVICE_HOST:example.com&authSource=$external" valid: true warning: false hosts: ~ @@ -10,7 +10,8 @@ tests: authMechanism: "GSSAPI" authMechanismProperties: SERVICE_NAME: "other" - CANONICALIZE_HOST_NAME: true + SERVICE_HOST: "example.com" + CANONICALIZE_HOST_NAME: "forward" authSource: "$external" - description: "Valid auth options are parsed correctly (SCRAM-SHA-1)" diff --git a/src/test/spec/json/uri-options/compression-options.json b/src/test/spec/json/uri-options/compression-options.json index 16bd27b2c..3c13dee06 100644 --- a/src/test/spec/json/uri-options/compression-options.json +++ b/src/test/spec/json/uri-options/compression-options.json @@ -35,7 +35,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low zlibCompressionLevel causes a warning", @@ -44,7 +44,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too high zlibCompressionLevel causes a warning", @@ -53,7 +53,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null } ] } diff --git a/src/test/spec/json/uri-options/compression-options.yml b/src/test/spec/json/uri-options/compression-options.yml index 831a69928..5e140e59c 100644 --- a/src/test/spec/json/uri-options/compression-options.yml +++ b/src/test/spec/json/uri-options/compression-options.yml @@ -28,7 +28,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low zlibCompressionLevel causes a warning" uri: "mongodb://example.com/?compressors=zlib&zlibCompressionLevel=-2" @@ -36,7 +36,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too high zlibCompressionLevel causes a warning" uri: "mongodb://example.com/?compressors=zlib&zlibCompressionLevel=10" @@ -44,5 +44,5 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ diff --git a/src/test/spec/json/uri-options/concern-options.json b/src/test/spec/json/uri-options/concern-options.json index 2b3783746..f55f29808 100644 --- a/src/test/spec/json/uri-options/concern-options.json +++ b/src/test/spec/json/uri-options/concern-options.json @@ -36,15 +36,6 @@ "w": "arbitraryButStillValid" } }, - { - "description": "Too low w causes a warning", - "uri": "mongodb://example.com/?w=-2", - "valid": true, - "warning": true, - "hosts": null, - "auth": null, - "options": {} - }, { "description": "Non-numeric wTimeoutMS causes a warning", "uri": "mongodb://example.com/?wTimeoutMS=invalid", @@ -52,7 +43,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low wTimeoutMS causes a warning", @@ -61,7 +52,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Invalid journal causes a warning", @@ -70,7 +61,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null } ] } diff --git a/src/test/spec/json/uri-options/concern-options.yml b/src/test/spec/json/uri-options/concern-options.yml index c88324173..7d3145349 100644 --- a/src/test/spec/json/uri-options/concern-options.yml +++ b/src/test/spec/json/uri-options/concern-options.yml @@ -36,7 +36,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low wTimeoutMS causes a warning" uri: "mongodb://example.com/?wTimeoutMS=-2" @@ -44,7 +44,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Invalid journal causes a warning" uri: "mongodb://example.com/?journal=invalid" @@ -52,4 +52,4 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ diff --git a/src/test/spec/json/uri-options/connection-options.json b/src/test/spec/json/uri-options/connection-options.json index 8bb05cc72..bbaa295ec 100644 --- a/src/test/spec/json/uri-options/connection-options.json +++ b/src/test/spec/json/uri-options/connection-options.json @@ -2,7 +2,7 @@ "tests": [ { "description": "Valid connection and timeout options are parsed correctly", - "uri": "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500", + "uri": "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500&timeoutMS=100", "valid": true, "warning": false, "hosts": null, @@ -16,7 +16,8 @@ "replicaSet": "uri-options-spec", "retryWrites": true, "serverSelectionTimeoutMS": 15000, - "socketTimeoutMS": 7500 + "socketTimeoutMS": 7500, + "timeoutMS": 100 } }, { @@ -26,7 +27,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low connectTimeoutMS causes a warning", @@ -35,7 +36,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Non-numeric heartbeatFrequencyMS causes a warning", @@ -44,7 +45,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low heartbeatFrequencyMS causes a warning", @@ -53,7 +54,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Non-numeric localThresholdMS causes a warning", @@ -62,7 +63,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low localThresholdMS causes a warning", @@ -71,7 +72,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Invalid retryWrites causes a warning", @@ -80,7 +81,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Non-numeric serverSelectionTimeoutMS causes a warning", @@ -89,7 +90,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low serverSelectionTimeoutMS causes a warning", @@ -98,7 +99,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Non-numeric socketTimeoutMS causes a warning", @@ -107,7 +108,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low socketTimeoutMS causes a warning", @@ -116,7 +117,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "directConnection=true", @@ -136,7 +137,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "directConnection=false", @@ -167,7 +168,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "loadBalanced=true", @@ -210,7 +211,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "loadBalanced=true with multiple hosts causes an error", @@ -219,7 +220,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "loadBalanced=true with directConnection=true causes an error", @@ -228,7 +229,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "loadBalanced=true with replicaSet causes an error", @@ -237,7 +238,36 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null + }, + { + "description": "timeoutMS=0", + "uri": "mongodb://example.com/?timeoutMS=0", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "timeoutMS": 0 + } + }, + { + "description": "Non-numeric timeoutMS causes a warning", + "uri": "mongodb://example.com/?timeoutMS=invalid", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Too low timeoutMS causes a warning", + "uri": "mongodb://example.com/?timeoutMS=-2", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null } ] } diff --git a/src/test/spec/json/uri-options/connection-options.yml b/src/test/spec/json/uri-options/connection-options.yml index 32d22d592..2d312b086 100644 --- a/src/test/spec/json/uri-options/connection-options.yml +++ b/src/test/spec/json/uri-options/connection-options.yml @@ -1,7 +1,7 @@ tests: - description: "Valid connection and timeout options are parsed correctly" - uri: "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500" + uri: "mongodb://example.com/?appname=URI-OPTIONS-SPEC-TEST&connectTimeoutMS=20000&heartbeatFrequencyMS=5000&localThresholdMS=3000&maxIdleTimeMS=50000&replicaSet=uri-options-spec&retryWrites=true&serverSelectionTimeoutMS=15000&socketTimeoutMS=7500&timeoutMS=100" valid: true warning: false hosts: ~ @@ -16,6 +16,7 @@ tests: retryWrites: true serverSelectionTimeoutMS: 15000 socketTimeoutMS: 7500 + timeoutMS: 100 - description: "Non-numeric connectTimeoutMS causes a warning" uri: "mongodb://example.com/?connectTimeoutMS=invalid" @@ -23,7 +24,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low connectTimeoutMS causes a warning" uri: "mongodb://example.com/?connectTimeoutMS=-2" @@ -31,7 +32,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Non-numeric heartbeatFrequencyMS causes a warning" uri: "mongodb://example.com/?heartbeatFrequencyMS=invalid" @@ -39,7 +40,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low heartbeatFrequencyMS causes a warning" uri: "mongodb://example.com/?heartbeatFrequencyMS=-2" @@ -47,7 +48,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Non-numeric localThresholdMS causes a warning" uri: "mongodb://example.com/?localThresholdMS=invalid" @@ -55,7 +56,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low localThresholdMS causes a warning" uri: "mongodb://example.com/?localThresholdMS=-2" @@ -63,15 +64,15 @@ tests: warning: true hosts: ~ auth: ~ - options: {} - - + options: ~ + - description: "Invalid retryWrites causes a warning" uri: "mongodb://example.com/?retryWrites=invalid" valid: true warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Non-numeric serverSelectionTimeoutMS causes a warning" uri: "mongodb://example.com/?serverSelectionTimeoutMS=invalid" @@ -79,7 +80,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low serverSelectionTimeoutMS causes a warning" uri: "mongodb://example.com/?serverSelectionTimeoutMS=-2" @@ -87,7 +88,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Non-numeric socketTimeoutMS causes a warning" uri: "mongodb://example.com/?socketTimeoutMS=invalid" @@ -95,7 +96,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low socketTimeoutMS causes a warning" uri: "mongodb://example.com/?socketTimeoutMS=-2" @@ -103,7 +104,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: directConnection=true uri: "mongodb://example.com/?directConnection=true" @@ -120,7 +121,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: directConnection=false uri: "mongodb://example.com/?directConnection=false" @@ -146,7 +147,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: loadBalanced=true uri: "mongodb://example.com/?loadBalanced=true" @@ -182,7 +183,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: loadBalanced=true with multiple hosts causes an error uri: "mongodb://example1,example2/?loadBalanced=true" @@ -190,7 +191,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: loadBalanced=true with directConnection=true causes an error uri: "mongodb://example.com/?loadBalanced=true&directConnection=true" @@ -198,7 +199,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: loadBalanced=true with replicaSet causes an error uri: "mongodb://example.com/?loadBalanced=true&replicaSet=replset" @@ -206,4 +207,29 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ + - + description: "timeoutMS=0" + uri: "mongodb://example.com/?timeoutMS=0" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + timeoutMS: 0 + - + description: "Non-numeric timeoutMS causes a warning" + uri: "mongodb://example.com/?timeoutMS=invalid" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ + - + description: "Too low timeoutMS causes a warning" + uri: "mongodb://example.com/?timeoutMS=-2" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ diff --git a/src/test/spec/json/uri-options/connection-pool-options.json b/src/test/spec/json/uri-options/connection-pool-options.json index aae16190b..a582867d0 100644 --- a/src/test/spec/json/uri-options/connection-pool-options.json +++ b/src/test/spec/json/uri-options/connection-pool-options.json @@ -2,7 +2,7 @@ "tests": [ { "description": "Valid connection pool options are parsed correctly", - "uri": "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3", + "uri": "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3&maxConnecting=1", "valid": true, "warning": false, "hosts": null, @@ -10,7 +10,8 @@ "options": { "maxIdleTimeMS": 50000, "maxPoolSize": 5, - "minPoolSize": 3 + "minPoolSize": 3, + "maxConnecting": 1 } }, { @@ -20,7 +21,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low maxIdleTimeMS causes a warning", @@ -29,7 +30,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "maxPoolSize=0 does not error", @@ -52,6 +53,24 @@ "options": { "minPoolSize": 0 } + }, + { + "description": "maxConnecting=0 causes a warning", + "uri": "mongodb://example.com/?maxConnecting=0", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "maxConnecting<0 causes a warning", + "uri": "mongodb://example.com/?maxConnecting=-1", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null } ] } diff --git a/src/test/spec/json/uri-options/connection-pool-options.yml b/src/test/spec/json/uri-options/connection-pool-options.yml index c0aeb01e7..bb73093be 100644 --- a/src/test/spec/json/uri-options/connection-pool-options.yml +++ b/src/test/spec/json/uri-options/connection-pool-options.yml @@ -1,7 +1,7 @@ tests: - description: "Valid connection pool options are parsed correctly" - uri: "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3" + uri: "mongodb://example.com/?maxIdleTimeMS=50000&maxPoolSize=5&minPoolSize=3&maxConnecting=1" valid: true warning: false hosts: ~ @@ -10,6 +10,7 @@ tests: maxIdleTimeMS: 50000 maxPoolSize: 5 minPoolSize: 3 + maxConnecting: 1 - description: "Non-numeric maxIdleTimeMS causes a warning" uri: "mongodb://example.com/?maxIdleTimeMS=invalid" @@ -17,7 +18,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low maxIdleTimeMS causes a warning" uri: "mongodb://example.com/?maxIdleTimeMS=-2" @@ -25,7 +26,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "maxPoolSize=0 does not error" @@ -45,4 +46,22 @@ tests: hosts: ~ auth: ~ options: - minPoolSize: 0 + minPoolSize: 0 + + - + description: "maxConnecting=0 causes a warning" + uri: "mongodb://example.com/?maxConnecting=0" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ + + - + description: "maxConnecting<0 causes a warning" + uri: "mongodb://example.com/?maxConnecting=-1" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ diff --git a/src/test/spec/json/uri-options/proxy-options.json b/src/test/spec/json/uri-options/proxy-options.json new file mode 100644 index 000000000..585546ead --- /dev/null +++ b/src/test/spec/json/uri-options/proxy-options.json @@ -0,0 +1,139 @@ +{ + "tests": [ + { + "description": "proxyPort without proxyHost", + "uri": "mongodb://localhost/?proxyPort=1080", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "proxyUsername without proxyHost", + "uri": "mongodb://localhost/?proxyUsername=abc", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "proxyPassword without proxyHost", + "uri": "mongodb://localhost/?proxyPassword=def", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "all other proxy options without proxyHost", + "uri": "mongodb://localhost/?proxyPort=1080&proxyUsername=abc&proxyPassword=def", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "proxyUsername without proxyPassword", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "proxyPassword without proxyUsername", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyPassword=def", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "multiple proxyHost parameters", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyHost=localhost2", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "multiple proxyPort parameters", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=1234&proxyPort=12345", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "multiple proxyUsername parameters", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyUsername=def&proxyPassword=123", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "multiple proxyPassword parameters", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyPassword=123&proxyPassword=456", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "only host present", + "uri": "mongodb://localhost/?proxyHost=localhost", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "host and default port present", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=1080", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "host and non-default port present", + "uri": "mongodb://localhost/?proxyHost=localhost&proxyPort=12345", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "replicaset, host and non-default port present", + "uri": "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + }, + { + "description": "all options present", + "uri": "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345&proxyUsername=asdf&proxyPassword=qwerty", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": {} + } + ] +} diff --git a/src/test/spec/json/uri-options/proxy-options.yml b/src/test/spec/json/uri-options/proxy-options.yml new file mode 100644 index 000000000..a97863dd5 --- /dev/null +++ b/src/test/spec/json/uri-options/proxy-options.yml @@ -0,0 +1,121 @@ +tests: + - + description: "proxyPort without proxyHost" + uri: "mongodb://localhost/?proxyPort=1080" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "proxyUsername without proxyHost" + uri: "mongodb://localhost/?proxyUsername=abc" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "proxyPassword without proxyHost" + uri: "mongodb://localhost/?proxyPassword=def" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "all other proxy options without proxyHost" + uri: "mongodb://localhost/?proxyPort=1080&proxyUsername=abc&proxyPassword=def" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "proxyUsername without proxyPassword" + uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "proxyPassword without proxyUsername" + uri: "mongodb://localhost/?proxyHost=localhost&proxyPassword=def" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "multiple proxyHost parameters" + uri: "mongodb://localhost/?proxyHost=localhost&proxyHost=localhost2" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "multiple proxyPort parameters" + uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=1234&proxyPort=12345" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "multiple proxyUsername parameters" + uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyUsername=def&proxyPassword=123" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "multiple proxyPassword parameters" + uri: "mongodb://localhost/?proxyHost=localhost&proxyUsername=abc&proxyPassword=123&proxyPassword=456" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "only host present" + uri: "mongodb://localhost/?proxyHost=localhost" + valid: true + warning: false + hosts: ~ + auth: ~ + options: {} + - + description: "host and default port present" + uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=1080" + valid: true + warning: false + hosts: ~ + auth: ~ + options: {} + - + description: "host and non-default port present" + uri: "mongodb://localhost/?proxyHost=localhost&proxyPort=12345" + valid: true + warning: false + hosts: ~ + auth: ~ + options: {} + - + description: "replicaset, host and non-default port present" + uri: "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345" + valid: true + warning: false + hosts: ~ + auth: ~ + options: {} + - + description: "all options present" + uri: "mongodb://rs1,rs2,rs3/?proxyHost=localhost&proxyPort=12345&proxyUsername=asdf&proxyPassword=qwerty" + valid: true + warning: false + hosts: ~ + auth: ~ + options: {} diff --git a/src/test/spec/json/uri-options/read-preference-options.json b/src/test/spec/json/uri-options/read-preference-options.json index e62ce4fa7..abbf0d0cc 100644 --- a/src/test/spec/json/uri-options/read-preference-options.json +++ b/src/test/spec/json/uri-options/read-preference-options.json @@ -21,6 +21,36 @@ "maxStalenessSeconds": 120 } }, + { + "description": "Single readPreferenceTags is parsed as array of size one", + "uri": "mongodb://example.com/?readPreference=secondary&readPreferenceTags=dc:ny", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "readPreferenceTags": [ + { + "dc": "ny" + } + ] + } + }, + { + "description": "Read preference tags are case sensitive", + "uri": "mongodb://example.com/?readPreference=secondary&readPreferenceTags=dc:NY", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "readPreferenceTags": [ + { + "dc": "NY" + } + ] + } + }, { "description": "Invalid readPreferenceTags causes a warning", "uri": "mongodb://example.com/?readPreferenceTags=invalid", @@ -28,7 +58,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Non-numeric maxStalenessSeconds causes a warning", @@ -37,7 +67,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "Too low maxStalenessSeconds causes a warning", @@ -46,7 +76,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null } ] } diff --git a/src/test/spec/json/uri-options/read-preference-options.yml b/src/test/spec/json/uri-options/read-preference-options.yml index f197a8b1f..267454c0e 100644 --- a/src/test/spec/json/uri-options/read-preference-options.yml +++ b/src/test/spec/json/uri-options/read-preference-options.yml @@ -9,12 +9,34 @@ tests: options: readPreference: "primaryPreferred" readPreferenceTags: - - + - dc: "ny" rack: "1" - dc: "ny" maxStalenessSeconds: 120 + - + description: "Single readPreferenceTags is parsed as array of size one" + uri: "mongodb://example.com/?readPreference=secondary&readPreferenceTags=dc:ny" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + readPreferenceTags: + - + dc: "ny" + - + description: "Read preference tags are case sensitive" + uri: "mongodb://example.com/?readPreference=secondary&readPreferenceTags=dc:NY" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + readPreferenceTags: + - + dc: "NY" - description: "Invalid readPreferenceTags causes a warning" uri: "mongodb://example.com/?readPreferenceTags=invalid" @@ -22,7 +44,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Non-numeric maxStalenessSeconds causes a warning" uri: "mongodb://example.com/?maxStalenessSeconds=invalid" @@ -30,7 +52,7 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "Too low maxStalenessSeconds causes a warning" uri: "mongodb://example.com/?maxStalenessSeconds=-2" @@ -38,5 +60,5 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ diff --git a/src/test/spec/json/uri-options/sdam-options.json b/src/test/spec/json/uri-options/sdam-options.json new file mode 100644 index 000000000..ae0aeb282 --- /dev/null +++ b/src/test/spec/json/uri-options/sdam-options.json @@ -0,0 +1,46 @@ +{ + "tests": [ + { + "description": "serverMonitoringMode=auto", + "uri": "mongodb://example.com/?serverMonitoringMode=auto", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "serverMonitoringMode": "auto" + } + }, + { + "description": "serverMonitoringMode=stream", + "uri": "mongodb://example.com/?serverMonitoringMode=stream", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "serverMonitoringMode": "stream" + } + }, + { + "description": "serverMonitoringMode=poll", + "uri": "mongodb://example.com/?serverMonitoringMode=poll", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "serverMonitoringMode": "poll" + } + }, + { + "description": "invalid serverMonitoringMode", + "uri": "mongodb://example.com/?serverMonitoringMode=invalid", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + } + ] +} diff --git a/src/test/spec/json/uri-options/sdam-options.yml b/src/test/spec/json/uri-options/sdam-options.yml new file mode 100644 index 000000000..92294b6e5 --- /dev/null +++ b/src/test/spec/json/uri-options/sdam-options.yml @@ -0,0 +1,35 @@ +tests: + - description: "serverMonitoringMode=auto" + uri: "mongodb://example.com/?serverMonitoringMode=auto" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + serverMonitoringMode: "auto" + + - description: "serverMonitoringMode=stream" + uri: "mongodb://example.com/?serverMonitoringMode=stream" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + serverMonitoringMode: "stream" + + - description: "serverMonitoringMode=poll" + uri: "mongodb://example.com/?serverMonitoringMode=poll" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + serverMonitoringMode: "poll" + + - description: "invalid serverMonitoringMode" + uri: "mongodb://example.com/?serverMonitoringMode=invalid" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ diff --git a/src/test/spec/json/uri-options/single-threaded-options.json b/src/test/spec/json/uri-options/single-threaded-options.json index fcd24fb88..80ac3fa4e 100644 --- a/src/test/spec/json/uri-options/single-threaded-options.json +++ b/src/test/spec/json/uri-options/single-threaded-options.json @@ -18,7 +18,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null } ] } diff --git a/src/test/spec/json/uri-options/single-threaded-options.yml b/src/test/spec/json/uri-options/single-threaded-options.yml index 6c2b02e19..1a94c8e07 100644 --- a/src/test/spec/json/uri-options/single-threaded-options.yml +++ b/src/test/spec/json/uri-options/single-threaded-options.yml @@ -15,4 +15,4 @@ tests: warning: true hosts: ~ auth: ~ - options: {} + options: ~ diff --git a/src/test/spec/json/uri-options/srv-options.json b/src/test/spec/json/uri-options/srv-options.json new file mode 100644 index 000000000..0670612c0 --- /dev/null +++ b/src/test/spec/json/uri-options/srv-options.json @@ -0,0 +1,116 @@ +{ + "tests": [ + { + "description": "SRV URI with custom srvServiceName", + "uri": "mongodb+srv://test22.test.build.10gen.cc/?srvServiceName=customname", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "srvServiceName": "customname" + } + }, + { + "description": "Non-SRV URI with custom srvServiceName", + "uri": "mongodb://example.com/?srvServiceName=customname", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "SRV URI with srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "srvMaxHosts": 2 + } + }, + { + "description": "SRV URI with negative integer for srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=-1", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "SRV URI with invalid type for srvMaxHosts", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=foo", + "valid": true, + "warning": true, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "Non-SRV URI with srvMaxHosts", + "uri": "mongodb://example.com/?srvMaxHosts=2", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "SRV URI with positive srvMaxHosts and replicaSet", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&replicaSet=foo", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "SRV URI with positive srvMaxHosts and loadBalanced=true", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "SRV URI with positive srvMaxHosts and loadBalanced=false", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "loadBalanced": false, + "srvMaxHosts": 2 + } + }, + { + "description": "SRV URI with srvMaxHosts=0 and replicaSet", + "uri": "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=0&replicaSet=foo", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "replicaSet": "foo", + "srvMaxHosts": 0 + } + }, + { + "description": "SRV URI with srvMaxHosts=0 and loadBalanced=true", + "uri": "mongodb+srv://test3.test.build.10gen.cc/?srvMaxHosts=0&loadBalanced=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "loadBalanced": true, + "srvMaxHosts": 0 + } + } + ] +} diff --git a/src/test/spec/json/uri-options/srv-options.yml b/src/test/spec/json/uri-options/srv-options.yml new file mode 100644 index 000000000..991749b0e --- /dev/null +++ b/src/test/spec/json/uri-options/srv-options.yml @@ -0,0 +1,89 @@ +tests: + - description: "SRV URI with custom srvServiceName" + uri: "mongodb+srv://test22.test.build.10gen.cc/?srvServiceName=customname" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + srvServiceName: "customname" + - description: "Non-SRV URI with custom srvServiceName" + uri: "mongodb://example.com/?srvServiceName=customname" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - description: "SRV URI with srvMaxHosts" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + srvMaxHosts: 2 + - description: "SRV URI with negative integer for srvMaxHosts" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=-1" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ + - description: "SRV URI with invalid type for srvMaxHosts" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=foo" + valid: true + warning: true + hosts: ~ + auth: ~ + options: ~ + - description: "Non-SRV URI with srvMaxHosts" + uri: "mongodb://example.com/?srvMaxHosts=2" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # Note: Testing URI validation for srvMaxHosts conflicting with either + # loadBalanced=true or replicaSet specified via TXT records is covered by + # the Initial DNS Seedlist Discovery test suite. + - description: "SRV URI with positive srvMaxHosts and replicaSet" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&replicaSet=foo" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - description: "SRV URI with positive srvMaxHosts and loadBalanced=true" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - description: "SRV URI with positive srvMaxHosts and loadBalanced=false" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=2&loadBalanced=false" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + loadBalanced: false + srvMaxHosts: 2 + - description: "SRV URI with srvMaxHosts=0 and replicaSet" + uri: "mongodb+srv://test1.test.build.10gen.cc/?srvMaxHosts=0&replicaSet=foo" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + replicaSet: foo + srvMaxHosts: 0 + - description: "SRV URI with srvMaxHosts=0 and loadBalanced=true" + uri: "mongodb+srv://test3.test.build.10gen.cc/?srvMaxHosts=0&loadBalanced=true" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + loadBalanced: true + srvMaxHosts: 0 diff --git a/src/test/spec/json/uri-options/tls-options.json b/src/test/spec/json/uri-options/tls-options.json index bc251795a..526cde1cb 100644 --- a/src/test/spec/json/uri-options/tls-options.json +++ b/src/test/spec/json/uri-options/tls-options.json @@ -13,17 +13,17 @@ "tlsCertificateKeyFile": "cert.pem" } }, - { - "description": "Valid tlsCertificateKeyFilePassword is parsed correctly", - "uri": "mongodb://example.com/?tlsCertificateKeyFilePassword=hunter2", - "valid": true, - "warning": false, - "hosts": null, - "auth": null, - "options": { - "tlsCertificateKeyFilePassword": "hunter2" - } - }, + { + "description": "Valid tlsCertificateKeyFilePassword is parsed correctly", + "uri": "mongodb://example.com/?tlsCertificateKeyFilePassword=hunter2", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "tlsCertificateKeyFilePassword": "hunter2" + } + }, { "description": "Invalid tlsAllowInvalidCertificates causes a warning", "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=invalid", @@ -31,7 +31,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsAllowInvalidCertificates is parsed correctly", @@ -44,15 +44,6 @@ "tlsAllowInvalidCertificates": true } }, - { - "description": "Invalid tlsAllowInvalidCertificates causes a warning", - "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=invalid", - "valid": true, - "warning": true, - "hosts": null, - "auth": null, - "options": {} - }, { "description": "tlsAllowInvalidHostnames is parsed correctly", "uri": "mongodb://example.com/?tlsAllowInvalidHostnames=true", @@ -71,7 +62,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsInsecure is parsed correctly", @@ -91,7 +82,7 @@ "warning": true, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsInsecure and tlsAllowInvalidCertificates both present (and true) raises an error", @@ -100,7 +91,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsInsecure and tlsAllowInvalidCertificates both present (and false) raises an error", @@ -109,7 +100,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsAllowInvalidCertificates and tlsInsecure both present (and true) raises an error", @@ -118,7 +109,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsAllowInvalidCertificates and tlsInsecure both present (and false) raises an error", @@ -127,7 +118,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsInsecure and tlsAllowInvalidHostnames both present (and true) raises an error", @@ -136,7 +127,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsInsecure and tlsAllowInvalidHostnames both present (and false) raises an error", @@ -145,7 +136,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsAllowInvalidHostnames and tlsInsecure both present (and true) raises an error", @@ -154,7 +145,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tlsAllowInvalidHostnames and tlsInsecure both present (and false) raises an error", @@ -163,7 +154,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tls=true and ssl=true doesn't warn", @@ -208,7 +199,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "tls=true and ssl=false raises error", @@ -217,7 +208,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "ssl=false and tls=true raises error", @@ -226,7 +217,7 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null }, { "description": "ssl=true and tls=false raises error", @@ -235,7 +226,415 @@ "warning": false, "hosts": null, "auth": null, - "options": {} + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck can be set to true", + "uri": "mongodb://example.com/?tls=true&tlsDisableCertificateRevocationCheck=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "tls": true, + "tlsDisableCertificateRevocationCheck": true + } + }, + { + "description": "tlsDisableCertificateRevocationCheck can be set to false", + "uri": "mongodb://example.com/?tls=true&tlsDisableCertificateRevocationCheck=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "tls": true, + "tlsDisableCertificateRevocationCheck": false + } + }, + { + "description": "tlsAllowInvalidCertificates and tlsDisableCertificateRevocationCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates=true and tlsDisableCertificateRevocationCheck=false raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates=false and tlsDisableCertificateRevocationCheck=true raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates and tlsDisableCertificateRevocationCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsAllowInvalidCertificates both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsAllowInvalidCertificates=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=true and tlsAllowInvalidCertificates=false raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsAllowInvalidCertificates=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=false and tlsAllowInvalidCertificates=true raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsAllowInvalidCertificates=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsAllowInvalidCertificates both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsAllowInvalidCertificates=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure and tlsDisableCertificateRevocationCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsInsecure=true&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure=true and tlsDisableCertificateRevocationCheck=false raises an error", + "uri": "mongodb://example.com/?tlsInsecure=true&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure=false and tlsDisableCertificateRevocationCheck=true raises an error", + "uri": "mongodb://example.com/?tlsInsecure=false&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure and tlsDisableCertificateRevocationCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsInsecure=false&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsInsecure both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsInsecure=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=true and tlsInsecure=false raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsInsecure=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=false and tlsInsecure=true raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsInsecure=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsInsecure both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsInsecure=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsDisableOCSPEndpointCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=true and tlsDisableOCSPEndpointCheck=false raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck=false and tlsDisableOCSPEndpointCheck=true raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableCertificateRevocationCheck and tlsDisableOCSPEndpointCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=true and tlsDisableCertificateRevocationCheck=false raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=false and tlsDisableCertificateRevocationCheck=true raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsDisableCertificateRevocationCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsDisableCertificateRevocationCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck can be set to true", + "uri": "mongodb://example.com/?tls=true&tlsDisableOCSPEndpointCheck=true", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "tls": true, + "tlsDisableOCSPEndpointCheck": true + } + }, + { + "description": "tlsDisableOCSPEndpointCheck can be set to false", + "uri": "mongodb://example.com/?tls=true&tlsDisableOCSPEndpointCheck=false", + "valid": true, + "warning": false, + "hosts": null, + "auth": null, + "options": { + "tls": true, + "tlsDisableOCSPEndpointCheck": false + } + }, + { + "description": "tlsInsecure and tlsDisableOCSPEndpointCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsInsecure=true&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure=true and tlsDisableOCSPEndpointCheck=false raises an error", + "uri": "mongodb://example.com/?tlsInsecure=true&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure=false and tlsDisableOCSPEndpointCheck=true raises an error", + "uri": "mongodb://example.com/?tlsInsecure=false&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsInsecure and tlsDisableOCSPEndpointCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsInsecure=false&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsInsecure both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsInsecure=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=true and tlsInsecure=false raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsInsecure=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=false and tlsInsecure=true raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsInsecure=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsInsecure both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsInsecure=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates and tlsDisableOCSPEndpointCheck both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates=true and tlsDisableOCSPEndpointCheck=false raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates=false and tlsDisableOCSPEndpointCheck=true raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableOCSPEndpointCheck=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsAllowInvalidCertificates and tlsDisableOCSPEndpointCheck both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableOCSPEndpointCheck=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsAllowInvalidCertificates both present (and true) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsAllowInvalidCertificates=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=true and tlsAllowInvalidCertificates=false raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsAllowInvalidCertificates=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck=false and tlsAllowInvalidCertificates=true raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsAllowInvalidCertificates=true", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null + }, + { + "description": "tlsDisableOCSPEndpointCheck and tlsAllowInvalidCertificates both present (and false) raises an error", + "uri": "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsAllowInvalidCertificates=false", + "valid": false, + "warning": false, + "hosts": null, + "auth": null, + "options": null } ] } diff --git a/src/test/spec/json/uri-options/tls-options.yml b/src/test/spec/json/uri-options/tls-options.yml index 2c34c2456..891b4582a 100644 --- a/src/test/spec/json/uri-options/tls-options.yml +++ b/src/test/spec/json/uri-options/tls-options.yml @@ -19,14 +19,14 @@ tests: auth: ~ options: tlsCertificateKeyFilePassword: "hunter2" - - + - description: "Invalid tlsAllowInvalidCertificates causes a warning" uri: "mongodb://example.com/?tlsAllowInvalidCertificates=invalid" valid: true warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsAllowInvalidCertificates is parsed correctly" uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true" @@ -36,14 +36,6 @@ tests: auth: ~ options: tlsAllowInvalidCertificates: true - - - description: "Invalid tlsAllowInvalidCertificates causes a warning" - uri: "mongodb://example.com/?tlsAllowInvalidCertificates=invalid" - valid: true - warning: true - hosts: ~ - auth: ~ - options: {} - description: "tlsAllowInvalidHostnames is parsed correctly" uri: "mongodb://example.com/?tlsAllowInvalidHostnames=true" @@ -53,14 +45,14 @@ tests: auth: ~ options: tlsAllowInvalidHostnames: true - - + - description: "Invalid tlsAllowInvalidHostnames causes a warning" uri: "mongodb://example.com/?tlsAllowInvalidHostnames=invalid" valid: true warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsInsecure is parsed correctly" uri: "mongodb://example.com/?tlsInsecure=true" @@ -70,14 +62,14 @@ tests: auth: ~ options: tlsInsecure: true - - + - description: "Invalid tlsInsecure causes a warning" uri: "mongodb://example.com/?tlsInsecure=invalid" valid: true warning: true hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsInsecure and tlsAllowInvalidCertificates both present (and true) raises an error" uri: "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidCertificates=true" @@ -85,7 +77,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsInsecure and tlsAllowInvalidCertificates both present (and false) raises an error" uri: "mongodb://example.com/?tlsInsecure=false&tlsAllowInvalidCertificates=false" @@ -93,7 +85,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsAllowInvalidCertificates and tlsInsecure both present (and true) raises an error" uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsInsecure=true" @@ -101,7 +93,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsAllowInvalidCertificates and tlsInsecure both present (and false) raises an error" uri: "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsInsecure=false" @@ -109,7 +101,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsInsecure and tlsAllowInvalidHostnames both present (and true) raises an error" uri: "mongodb://example.com/?tlsInsecure=true&tlsAllowInvalidHostnames=true" @@ -117,7 +109,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsInsecure and tlsAllowInvalidHostnames both present (and false) raises an error" uri: "mongodb://example.com/?tlsInsecure=false&tlsAllowInvalidHostnames=false" @@ -125,7 +117,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsAllowInvalidHostnames and tlsInsecure both present (and true) raises an error" uri: "mongodb://example.com/?tlsAllowInvalidHostnames=true&tlsInsecure=true" @@ -133,7 +125,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tlsAllowInvalidHostnames and tlsInsecure both present (and false) raises an error" uri: "mongodb://example.com/?tlsAllowInvalidHostnames=false&tlsInsecure=false" @@ -141,7 +133,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tls=true and ssl=true doesn't warn" uri: "mongodb://example.com/?tls=true&ssl=true" @@ -181,7 +173,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "tls=true and ssl=false raises error" uri: "mongodb://example.com/?tls=true&ssl=false" @@ -189,7 +181,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "ssl=false and tls=true raises error" uri: "mongodb://example.com/?ssl=false&tls=true" @@ -197,7 +189,7 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ - description: "ssl=true and tls=false raises error" uri: "mongodb://example.com/?ssl=true&tls=false" @@ -205,4 +197,374 @@ tests: warning: false hosts: ~ auth: ~ - options: {} + options: ~ + - + description: "tlsDisableCertificateRevocationCheck can be set to true" + uri: "mongodb://example.com/?tls=true&tlsDisableCertificateRevocationCheck=true" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + tls: true + tlsDisableCertificateRevocationCheck: true + - + description: "tlsDisableCertificateRevocationCheck can be set to false" + uri: "mongodb://example.com/?tls=true&tlsDisableCertificateRevocationCheck=false" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + tls: true + tlsDisableCertificateRevocationCheck: false + # 4 permutations of [tlsAllowInvalidCertificates=true/false, tlsDisableCertificateRevocationCheck=true/false] + - + description: "tlsAllowInvalidCertificates and tlsDisableCertificateRevocationCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates=true and tlsDisableCertificateRevocationCheck=false raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates=false and tlsDisableCertificateRevocationCheck=true raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates and tlsDisableCertificateRevocationCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableCertificateRevocationCheck=true/false, tlsAllowInvalidCertificates=true/false] + - + description: "tlsDisableCertificateRevocationCheck and tlsAllowInvalidCertificates both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsAllowInvalidCertificates=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=true and tlsAllowInvalidCertificates=false raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsAllowInvalidCertificates=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=false and tlsAllowInvalidCertificates=true raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsAllowInvalidCertificates=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck and tlsAllowInvalidCertificates both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsAllowInvalidCertificates=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsInsecure=true/false, tlsDisableCertificateRevocationCheck=true/false] + - + description: "tlsInsecure and tlsDisableCertificateRevocationCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsInsecure=true&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure=true and tlsDisableCertificateRevocationCheck=false raises an error" + uri: "mongodb://example.com/?tlsInsecure=true&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure=false and tlsDisableCertificateRevocationCheck=true raises an error" + uri: "mongodb://example.com/?tlsInsecure=false&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure and tlsDisableCertificateRevocationCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsInsecure=false&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableCertificateRevocationCheck=true/false, tlsInsecure=true/false] + - + description: "tlsDisableCertificateRevocationCheck and tlsInsecure both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsInsecure=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=true and tlsInsecure=false raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsInsecure=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=false and tlsInsecure=true raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsInsecure=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck and tlsInsecure both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsInsecure=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableCertificateRevocationCheck=true/false, tlsDisableOCSPEndpointCheck=true/false] + - + description: "tlsDisableCertificateRevocationCheck and tlsDisableOCSPEndpointCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=true and tlsDisableOCSPEndpointCheck=false raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=true&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck=false and tlsDisableOCSPEndpointCheck=true raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableCertificateRevocationCheck and tlsDisableOCSPEndpointCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableCertificateRevocationCheck=false&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableOCSPEndpointCheck=true/false, tlsDisableCertificateRevocationCheck=true/false] + - + description: "tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=true and tlsDisableCertificateRevocationCheck=false raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=false and tlsDisableCertificateRevocationCheck=true raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsDisableCertificateRevocationCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck and tlsDisableCertificateRevocationCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsDisableCertificateRevocationCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck can be set to true" + uri: "mongodb://example.com/?tls=true&tlsDisableOCSPEndpointCheck=true" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + tls: true + tlsDisableOCSPEndpointCheck: true + - + description: "tlsDisableOCSPEndpointCheck can be set to false" + uri: "mongodb://example.com/?tls=true&tlsDisableOCSPEndpointCheck=false" + valid: true + warning: false + hosts: ~ + auth: ~ + options: + tls: true + tlsDisableOCSPEndpointCheck: false + # 4 permutations of [tlsInsecure=true/false, tlsDisableOCSPEndpointCheck=true/false] + - + description: "tlsInsecure and tlsDisableOCSPEndpointCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsInsecure=true&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure=true and tlsDisableOCSPEndpointCheck=false raises an error" + uri: "mongodb://example.com/?tlsInsecure=true&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure=false and tlsDisableOCSPEndpointCheck=true raises an error" + uri: "mongodb://example.com/?tlsInsecure=false&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsInsecure and tlsDisableOCSPEndpointCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsInsecure=false&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableOCSPEndpointCheck=true/false, tlsInsecure=true/false] + - + description: "tlsDisableOCSPEndpointCheck and tlsInsecure both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsInsecure=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=true and tlsInsecure=false raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsInsecure=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=false and tlsInsecure=true raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsInsecure=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck and tlsInsecure both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsInsecure=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsAllowInvalidCertificates=true/false, tlsDisableOCSPEndpointCheck=true/false] + - + description: "tlsAllowInvalidCertificates and tlsDisableOCSPEndpointCheck both present (and true) raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates=true and tlsDisableOCSPEndpointCheck=false raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=true&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates=false and tlsDisableOCSPEndpointCheck=true raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableOCSPEndpointCheck=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsAllowInvalidCertificates and tlsDisableOCSPEndpointCheck both present (and false) raises an error" + uri: "mongodb://example.com/?tlsAllowInvalidCertificates=false&tlsDisableOCSPEndpointCheck=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + # 4 permutations of [tlsDisableOCSPEndpointCheck=true/false, tlsAllowInvalidCertificates=true/false] + - + description: "tlsDisableOCSPEndpointCheck and tlsAllowInvalidCertificates both present (and true) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsAllowInvalidCertificates=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=true and tlsAllowInvalidCertificates=false raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=true&tlsAllowInvalidCertificates=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck=false and tlsAllowInvalidCertificates=true raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsAllowInvalidCertificates=true" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ + - + description: "tlsDisableOCSPEndpointCheck and tlsAllowInvalidCertificates both present (and false) raises an error" + uri: "mongodb://example.com/?tlsDisableOCSPEndpointCheck=false&tlsAllowInvalidCertificates=false" + valid: false + warning: false + hosts: ~ + auth: ~ + options: ~ diff --git a/src/test/spec/json/versioned-api/README.rst b/src/test/spec/json/versioned-api/README.rst index d88c31e52..3716df43e 100644 --- a/src/test/spec/json/versioned-api/README.rst +++ b/src/test/spec/json/versioned-api/README.rst @@ -10,7 +10,7 @@ Notes ===== This directory contains tests for the Stable API specification. They are -implemented in the `Unified Test Format <../../unified-test-format/unified-test-format.rst>`__, +implemented in the `Unified Test Format <../../unified-test-format/unified-test-format.md>`__, and require schema version 1.1. Note that to run these tests, the server must be started with both ``enableTestCommands`` and ``acceptApiVersion2`` parameters set to true. diff --git a/src/test/spec/json/versioned-api/crud-api-version-1.json b/src/test/spec/json/versioned-api/crud-api-version-1.json index a387d0587..23ef59a6d 100644 --- a/src/test/spec/json/versioned-api/crud-api-version-1.json +++ b/src/test/spec/json/versioned-api/crud-api-version-1.json @@ -50,7 +50,8 @@ }, "apiDeprecationErrors": true } - ] + ], + "namespace": "versioned-api-tests.test" }, "initialData": [ { @@ -426,6 +427,86 @@ } ] }, + { + "description": "client bulkWrite appends declared API version", + "runOnRequirements": [ + { + "minServerVersion": "8.0", + "serverless": "forbid" + } + ], + "operations": [ + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "insertOne": { + "namespace": "versioned-api-tests.test", + "document": { + "_id": 6, + "x": 6 + } + } + } + ], + "verboseResults": true + }, + "expectResult": { + "insertedCount": 1, + "upsertedCount": 0, + "matchedCount": 0, + "modifiedCount": 0, + "deletedCount": 0, + "insertResults": { + "0": { + "insertedId": 6 + } + }, + "updateResults": {}, + "deleteResults": {} + } + } + ], + "expectEvents": [ + { + "client": "client", + "events": [ + { + "commandStartedEvent": { + "commandName": "bulkWrite", + "databaseName": "admin", + "command": { + "bulkWrite": 1, + "errorsOnly": false, + "ordered": true, + "ops": [ + { + "insert": 0, + "document": { + "_id": 6, + "x": 6 + } + } + ], + "nsInfo": [ + { + "ns": "versioned-api-tests.test" + } + ], + "apiVersion": "1", + "apiStrict": { + "$$unsetOrMatches": false + }, + "apiDeprecationErrors": true + } + } + } + ] + } + ] + }, { "description": "countDocuments appends declared API version", "operations": [ diff --git a/src/test/spec/json/versioned-api/crud-api-version-1.yml b/src/test/spec/json/versioned-api/crud-api-version-1.yml index 50135c145..01e032342 100644 --- a/src/test/spec/json/versioned-api/crud-api-version-1.yml +++ b/src/test/spec/json/versioned-api/crud-api-version-1.yml @@ -34,6 +34,7 @@ _yamlAnchors: apiVersion: "1" apiStrict: { $$unsetOrMatches: false } apiDeprecationErrors: true + namespace: &namespace "versioned-api-tests.test" initialData: - collectionName: *collectionName @@ -155,6 +156,47 @@ tests: multi: { $$unsetOrMatches: false } upsert: true <<: *expectedApiVersion + + - description: "client bulkWrite appends declared API version" + runOnRequirements: + - minServerVersion: "8.0" # `bulkWrite` added to server 8.0 + serverless: forbid + operations: + - name: clientBulkWrite + object: *client + arguments: + models: + - insertOne: + namespace: *namespace + document: { _id: 6, x: 6 } + verboseResults: true + expectResult: + insertedCount: 1 + upsertedCount: 0 + matchedCount: 0 + modifiedCount: 0 + deletedCount: 0 + insertResults: + 0: + insertedId: 6 + updateResults: {} + deleteResults: {} + expectEvents: + - client: *client + events: + - commandStartedEvent: + commandName: bulkWrite + databaseName: admin + command: + bulkWrite: 1 + errorsOnly: false + ordered: true + ops: + - insert: 0 + document: { _id: 6, x: 6 } + nsInfo: + - { ns: *namespace } + <<: *expectedApiVersion - description: "countDocuments appends declared API version" operations: diff --git a/src/test/spec/load_balancers.rs b/src/test/spec/load_balancers.rs index 6fdd070e8..ac1a77399 100644 --- a/src/test/spec/load_balancers.rs +++ b/src/test/spec/load_balancers.rs @@ -1,18 +1,10 @@ -use tokio::sync::RwLockWriteGuard; - -use crate::test::{ - spec::{ - unified_runner::{run_unified_tests, ExpectedCmapEvent, ExpectedEvent, TestFile}, - ExpectedEventType, - }, - LOCK, +use crate::test::spec::{ + unified_runner::{run_unified_tests, ExpectedCmapEvent, ExpectedEvent, TestFile}, + ExpectedEventType, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - // The Rust driver's asynchronous check-in of connections means that sometimes a new // connection will be created, rather than re-using an existing one; the connectionReady // events generated by this cause tests to fail, but does not break user-visible behavior. diff --git a/src/test/spec/mod.rs b/src/test/spec/mod.rs deleted file mode 100644 index 606a190ac..000000000 --- a/src/test/spec/mod.rs +++ /dev/null @@ -1,127 +0,0 @@ -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod auth; -mod change_streams; -#[cfg(feature = "in-use-encryption-unstable")] -mod client_side_encryption; -mod collection_management; -mod command_monitoring; -mod connection_stepdown; -mod crud; -mod crud_v1; -mod faas; -mod gridfs; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod initial_dns_seedlist_discovery; -mod load_balancers; -mod ocsp; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod read_write_concern; -mod retryable_reads; -mod retryable_writes; -mod sdam; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] -mod sessions; -#[cfg(feature = "tracing-unstable")] -mod trace; -mod transactions; -pub mod unified_runner; -mod v2_runner; -mod versioned_api; -mod write_error; - -use std::{ - any::type_name, - ffi::OsStr, - fs::{read_dir, File}, - future::Future, - path::PathBuf, -}; - -use serde::{de::DeserializeOwned, Deserialize}; - -pub(crate) use self::{ - unified_runner::{merge_uri_options, ExpectedEventType, Topology}, - v2_runner::{operation::Operation, test_file::RunOn}, -}; -use crate::{bson::Bson, test::SERVERLESS}; - -use super::log_uncaptured; - -pub(crate) fn deserialize_spec_tests( - spec: &[&str], - skipped_files: Option<&[&str]>, -) -> Vec<(T, PathBuf)> { - let dir_path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "src", "test", "spec", "json"] - .iter() - .chain(spec.iter()) - .collect(); - - let mut tests = vec![]; - for entry in read_dir(&dir_path) - .unwrap_or_else(|e| panic!("Failed to read directory at {:?}: {}", &dir_path, e)) - { - let path = entry.unwrap().path(); - let Some(filename) = path.file_name().and_then(OsStr::to_str).filter(|name| name.ends_with(".json")) else { - continue; - }; - - if let Some(skipped_files) = skipped_files { - if skipped_files.contains(&filename) { - log_uncaptured(format!("Skipping deserializing {:?}", &path)); - continue; - } - } - - let file = File::open(&path) - .unwrap_or_else(|e| panic!("Failed to open file at {:?}: {}", &path, e)); - - // Use BSON as an intermediary to deserialize extended JSON properly. - let test_bson: Bson = serde_json::from_reader(file).unwrap_or_else(|e| { - panic!( - "Failed to deserialize test JSON to BSON in {:?}: {}", - &path, e - ) - }); - let test: T = bson::from_bson(test_bson).unwrap_or_else(|e| { - panic!( - "Failed to deserialize test BSON to {} in {:?}: {}", - type_name::(), - &path, - e - ) - }); - - tests.push((test, path)); - } - - tests -} - -pub(crate) async fn run_spec_test(spec: &[&str], run_test_file: F) -where - F: Fn(T) -> G, - G: Future, - T: DeserializeOwned, -{ - for (test_file, _) in deserialize_spec_tests(spec, None) { - run_test_file(test_file).await; - } -} - -#[derive(Debug, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase", deny_unknown_fields)] -pub(crate) enum Serverless { - Require, - Forbid, - Allow, -} - -impl Serverless { - pub(crate) fn can_run(&self) -> bool { - match self { - Self::Forbid if *SERVERLESS => false, - Self::Require if !*SERVERLESS => false, - _ => true, - } - } -} diff --git a/src/test/spec/ocsp.rs b/src/test/spec/ocsp.rs deleted file mode 100644 index 93a13c867..000000000 --- a/src/test/spec/ocsp.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::time::Duration; - -use bson::doc; -use tokio::sync::RwLockWriteGuard; - -use crate::{ - test::{log_uncaptured, CLIENT_OPTIONS, LOCK}, - Client, -}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - if std::env::var_os("MONGO_OCSP_TESTS").is_none() { - log_uncaptured("skipping test due to missing environment variable MONGO_OCSP_TESTS"); - return; - } - - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let should_succeed = std::env::var("OCSP_TLS_SHOULD_SUCCEED") - .unwrap() - .to_lowercase(); - - let mut options = CLIENT_OPTIONS.get().await.clone(); - let mut tls_options = options.tls_options().unwrap(); - options.server_selection_timeout = Duration::from_millis(200).into(); - - let client = Client::with_options(options.clone()).unwrap(); - let response = client - .database("admin") - .run_command(doc! { "ping": 1 }, None) - .await; - - match response { - Ok(_) if should_succeed == "false" => { - panic!("OSCP: connection succeeded but should have failed") - } - Err(e) if should_succeed == "true" => { - panic!("OSCP: connection failed but should have succeded: {}", e) - } - _ => {} - } - - tls_options.allow_invalid_certificates = Some(true); - options.tls = Some(tls_options.into()); - let tls_insecure_client = Client::with_options(options).unwrap(); - tls_insecure_client - .database("admin") - .run_command(doc! { "ping" : 1 }, None) - .await - .expect("tls insecure ping should succeed"); -} diff --git a/src/test/spec/oidc.rs b/src/test/spec/oidc.rs new file mode 100644 index 000000000..a6914f52f --- /dev/null +++ b/src/test/spec/oidc.rs @@ -0,0 +1,1462 @@ +use std::path::PathBuf; + +use once_cell::sync::Lazy; +use tokio::sync::OnceCell; + +use crate::{ + bson::Bson, + test::spec::unified_runner::{TestFile, TestFileEntity}, +}; + +static MONGODB_URI: Lazy = Lazy::new(|| get_env_var("MONGODB_URI")); +static MONGODB_URI_SINGLE: Lazy = Lazy::new(|| get_env_var("MONGODB_URI_SINGLE")); +#[cfg(target_os = "linux")] +static MONGODB_URI_MULTI: Lazy = Lazy::new(|| get_env_var("MONGODB_URI_MULTI")); +static OIDC_DOMAIN: Lazy = Lazy::new(|| get_env_var("OIDC_DOMAIN")); +static OIDC_TOKEN_DIR: Lazy = Lazy::new(|| { + std::env::var("OIDC_TOKEN_DIR") + .unwrap_or_else(|_| "/tmp/tokens".to_string()) + .into() +}); +#[cfg(target_os = "linux")] +static OIDC_TOKEN_FILE: Lazy = Lazy::new(|| get_env_var("OIDC_TOKEN_FILE")); +static TEST_USER_1_USERNAME: Lazy = Lazy::new(|| format!("test_user1@{}", *OIDC_DOMAIN)); +#[cfg(target_os = "linux")] +static TEST_USER_2_USERNAME: Lazy = Lazy::new(|| format!("test_user2@{}", *OIDC_DOMAIN)); + +async fn get_access_token_test_user(once_cell: &'static OnceCell, user_n: u8) -> String { + once_cell + .get_or_init(|| async { + let mut path = OIDC_TOKEN_DIR.clone(); + let user = format!("test_user{}", user_n); + path.push(user); + tokio::fs::read_to_string(path).await.unwrap() + }) + .await + .to_string() +} +pub(crate) async fn get_access_token_test_user_1() -> String { + static ACCESS_TOKEN_TEST_USER_1: OnceCell = OnceCell::const_new(); + get_access_token_test_user(&ACCESS_TOKEN_TEST_USER_1, 1).await +} +#[cfg(target_os = "linux")] +async fn get_access_token_test_user_2() -> String { + static ACCESS_TOKEN_TEST_USER_2: OnceCell = OnceCell::const_new(); + get_access_token_test_user(&ACCESS_TOKEN_TEST_USER_2, 2).await +} + +fn get_env_var(var: &str) -> String { + std::env::var(var).expect(var) +} + +fn remove_mechanism_properties_placeholder(test_file: &mut TestFile) { + if let Some(ref mut create_entities) = test_file.create_entities { + for ref mut entity in create_entities { + if let TestFileEntity::Client(ref mut client) = entity { + if let Some(ref mut uri_options) = client.uri_options { + if let Some(mut mechanism_properties) = uri_options + .remove("authMechanismProperties") + .and_then(|bson| match bson { + Bson::Document(document) => Some(document), + _ => None, + }) + { + mechanism_properties.remove("$$placeholder"); + if !mechanism_properties.is_empty() { + uri_options.insert("authMechanismProperties", mechanism_properties); + } + } + } + } + } + } +} + +mod basic { + use crate::{ + bson::{doc, Document}, + client::auth::{oidc, AuthMechanism, Credential}, + options::ClientOptions, + test::{ + spec::unified_runner::run_unified_tests, + util::fail_point::{FailPoint, FailPointMode}, + }, + Client, + }; + use futures_util::FutureExt; + use std::{ + sync::Arc, + time::{Duration, Instant}, + }; + use tokio::sync::Mutex; + + use super::{ + get_access_token_test_user_1, + remove_mechanism_properties_placeholder, + MONGODB_URI, + MONGODB_URI_SINGLE, + TEST_USER_1_USERNAME, + }; + + #[cfg(target_os = "linux")] + use super::{ + get_access_token_test_user_2, + MONGODB_URI_MULTI, + OIDC_TOKEN_FILE, + TEST_USER_2_USERNAME, + }; + + #[tokio::test(flavor = "multi_thread")] + async fn run_unified() { + run_unified_tests(&["auth", "unified"]) + .transform_files(remove_mechanism_properties_placeholder) + .await; + } + + // Machine Callback tests + #[tokio::test] + async fn machine_1_1_callback_is_called() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential.as_mut().unwrap().source = None; + // test the new public API here. + opts.credential.as_mut().unwrap().oidc_callback = + crate::options::oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse::builder() + .access_token(get_access_token_test_user_1().await) + .build()) + } + .boxed() + }); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 10)] + async fn machine_1_2_callback_is_called_only_once_for_multiple_connections( + ) -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let mut handles = Vec::with_capacity(10); + for _ in 0..10 { + let client = client.clone(); + handles.push(tokio::spawn(async move { + for _ in 0..100 { + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await + .unwrap(); + } + })); + } + for handle in handles { + handle.await.unwrap(); + } + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn machine_2_1_valid_callback_inputs() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |c| { + let call_count = cb_call_count.clone(); + assert!(c.refresh_token.is_none()); + // timeout should be in the future + assert!(c.timeout.unwrap() >= Instant::now()); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + // 2.2 callback returns null, but that is impossible with the rust type system + + #[tokio::test] + async fn machine_2_3_oidc_callback_return_missing_data() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: "".to_string(), + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn machine_2_4_invalid_client_configuration_with_callback() -> anyhow::Result<()> { + use crate::client::auth::oidc::{ENVIRONMENT_PROP_STR, TOKEN_RESOURCE_PROP_STR}; + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .mechanism_properties( + doc! {ENVIRONMENT_PROP_STR: "test", TOKEN_RESOURCE_PROP_STR: "test"}, + ) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::InvalidArgument { .. }, + )); + Ok(()) + } + + #[tokio::test] + async fn machine_3_1_failure_with_cached_tokens_fetch_a_new_token_and_retry_auth( + ) -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + // poison the cache with a bad token, authentication should still work. + opts.credential + .as_mut() + .unwrap() + .oidc_callback + .set_access_token(Some("random happy sunshine token".to_string())) + .await; + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn machine_3_2_auth_failures_without_cached_tokens_returns_an_error() -> anyhow::Result<()> + { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: "bad token".to_string(), + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn machine_4_1_reauthentication() -> anyhow::Result<()> { + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // Now set a failpoint for find with 391 error code + let fail_point = + FailPoint::fail_command(&["find"], FailPointMode::Times(1)).error_code(391); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(2, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn machine_4_2_read_command_fails_if_reauth_fails() -> anyhow::Result<()> { + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut options = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + let access_token = if *call_count.lock().await == 1 { + get_access_token_test_user_1().await + } else { + "bad token".to_string() + }; + Ok(oidc::IdpServerResponse::builder() + .access_token(access_token) + .build()) + } + .boxed() + })) + .build(); + options.credential = Some(credential); + let client = Client::with_options(options)?; + let collection = client.database("test").collection::("test"); + + collection.find_one(doc! {}).await?; + + let fail_point = + FailPoint::fail_command(&["find"], FailPointMode::Times(1)).error_code(391); + let _guard = client.enable_fail_point(fail_point).await?; + + collection.find_one(doc! {}).await.unwrap_err(); + + assert_eq!(*call_count.lock().await, 2); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn machine_4_3_write_command_fails_if_reauth_fails() -> anyhow::Result<()> { + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut options = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + let access_token = if *call_count.lock().await == 1 { + get_access_token_test_user_1().await + } else { + "bad token".to_string() + }; + Ok(oidc::IdpServerResponse::builder() + .access_token(access_token) + .build()) + } + .boxed() + })) + .build(); + options.credential = Some(credential); + let client = Client::with_options(options)?; + let collection = client.database("test").collection::("test"); + + collection.insert_one(doc! { "x": 1 }).await?; + + let fail_point = + FailPoint::fail_command(&["insert"], FailPointMode::Times(1)).error_code(391); + let _guard = client.enable_fail_point(fail_point).await?; + + collection.insert_one(doc! { "y": 2 }).await.unwrap_err(); + + assert_eq!(*call_count.lock().await, 2); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn machine_4_4_speculative_auth_ignored_on_reauth() -> anyhow::Result<()> { + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut options = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + let credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::machine(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse::builder() + .access_token(get_access_token_test_user_1().await) + .build()) + } + .boxed() + })) + .build(); + credential + .oidc_callback + .set_access_token(Some(get_access_token_test_user_1().await)) + .await; + options.credential = Some(credential); + let client = Client::for_test().options(options).monitor_events().await; + let event_buffer = &client.events; + let collection = client.database("test").collection::("test"); + + collection.insert_one(doc! { "x": 1 }).await?; + + assert_eq!(*call_count.lock().await, 0); + let sasl_start_events = event_buffer.get_command_started_events(&["saslStart"]); + assert!(sasl_start_events.is_empty()); + + let fail_point = + FailPoint::fail_command(&["insert"], FailPointMode::Times(1)).error_code(391); + let _guard = client.enable_fail_point(fail_point).await?; + + collection.insert_one(doc! { "y": 2 }).await?; + + assert_eq!(*call_count.lock().await, 1); + let _sasl_start_events = event_buffer.get_command_started_events(&["saslStart"]); + // TODO RUST-2176: unskip this assertion when saslStart events are emitted + // assert!(!sasl_start_events.is_empty()); + + Ok(()) + } + + // Human Callback tests + #[tokio::test] + async fn human_1_1_single_principal_implicit_username() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let database = client.database("test"); + let collection = database.collection::("test"); + collection.find_one(doc! {}).await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn human_1_2_single_principal_explicit_username() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .username(TEST_USER_1_USERNAME.clone()) + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn human_1_3_multiple_principal_user_1() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .username(TEST_USER_1_USERNAME.clone()) + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + #[cfg(target_os = "linux")] // MONGODB_URI_MULTI is only set when running on linux + async fn human_1_4_multiple_principal_user_2() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_MULTI).await?; + opts.credential = Credential::builder() + .username(TEST_USER_2_USERNAME.clone()) + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_2().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + #[cfg(target_os = "linux")] // MONGODB_URI_MULTI is only set when running on linux + async fn human_1_5_multiple_principal_no_user() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_MULTI).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: tokio::fs::read_to_string(&*OIDC_TOKEN_FILE).await?, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + assert_eq!(0, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn human_1_6_allowed_hosts_blocked() -> anyhow::Result<()> { + use crate::client::auth::oidc::ALLOWED_HOSTS_PROP_STR; + { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + // Use empty list for ALLOWED_HOSTS + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .mechanism_properties(crate::bson::doc! { + ALLOWED_HOSTS_PROP_STR: [], + }) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + // asserting 0 shows that this is a client side error + assert_eq!(0, *(*call_count).lock().await); + } + + { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .mechanism_properties(crate::bson::doc! { + ALLOWED_HOSTS_PROP_STR: ["example.com"], + }) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + // asserting 0 shows that this is a client side error + assert_eq!(0, *(*call_count).lock().await); + } + + Ok(()) + } + + #[tokio::test] + async fn human_2_1_valid_callback_inputs() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |c| { + let call_count = cb_call_count.clone(); + let idp_info = c.idp_info.unwrap(); + assert!(idp_info.issuer.as_str() != ""); + assert!(idp_info.client_id.is_some()); + assert!(c.timeout.unwrap() <= Instant::now() + Duration::from_secs(60 * 5)); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test] + async fn human_2_2_callback_returns_missing_data() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: "".to_string(), + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_3_1_uses_speculative_authentication_if_there_is_a_cached_token( + ) -> anyhow::Result<()> { + // get an admin_client for setting failpoints + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + // since this test will use the cached token, this callback shouldn't matter + access_token: "".to_string(), + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + + // put the test_user1 token in the cache + opts.credential + .as_mut() + .unwrap() + .oidc_callback + .set_access_token(Some(get_access_token_test_user_1().await)) + .await; + + let client = Client::with_options(opts)?; + + // Now set a failpoint for saslStart + // we use 5 times just because AlwaysOn is dangerous if for some reason we don't run + // the cleanup, since we will not be able to auth a new connection to turn + // off the failpoint. + let fail_point = + FailPoint::fail_command(&["saslStart"], FailPointMode::Times(5)).error_code(20); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + // Now find should succeed even though we have a fail point on saslStart because the spec + // auth should succeed. + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + // the callback should not have been called at all + assert_eq!(0, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_3_2_does_not_use_speculative_authentication_if_there_is_no_cached_token( + ) -> anyhow::Result<()> { + // get an admin_client for setting failpoints + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // Now set a failpoint for find + let fail_point = + FailPoint::fail_command(&["saslStart"], FailPointMode::Times(5)).error_code(20); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + + assert_eq!(0, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_4_1_succeeds() -> anyhow::Result<()> { + use crate::{ + event::command::CommandEvent, + test::{util::event_buffer::EventBuffer, Event}, + }; + + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + + let buffer = EventBuffer::new(); + opts.command_event_handler = Some(buffer.handler()); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + // Now set a failpoint for find with 391 error code + let fail_point = + FailPoint::fail_command(&["find"], FailPointMode::Times(1)).error_code(391); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + assert_eq!(2, *(*call_count).lock().await); + let find_events = buffer.filter_map(|e: &Event| match e.as_command_event() { + Some(command_event) if command_event.command_name() == "find" => { + Some(command_event.clone()) + } + _ => None, + }); + // assert the first find started + assert!(matches!( + find_events.first().unwrap(), + CommandEvent::Started(_) + )); + // assert the first find succeeded + assert!(matches!( + find_events.get(1).unwrap(), + CommandEvent::Succeeded(_) + )); + // assert the second find started + assert!(matches!( + find_events.get(2).unwrap(), + CommandEvent::Started(_) + )); + // assert the second find failed + assert!(matches!( + find_events.get(3).unwrap(), + CommandEvent::Failed(_) + )); + // assert the first find started + assert!(matches!( + find_events.get(4).unwrap(), + CommandEvent::Started(_) + )); + // assert the third find succeeded + assert!(matches!( + find_events.get(5).unwrap(), + CommandEvent::Succeeded(_) + )); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_4_2_succeeds_no_refresh() -> anyhow::Result<()> { + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + // Now set a failpoint for find with 391 error code + let fail_point = + FailPoint::fail_command(&["find"], FailPointMode::Times(1)).error_code(391); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + assert_eq!(2, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_4_3_succeeds_after_refresh_fails() -> anyhow::Result<()> { + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: Some("fake refresh token".to_string()), + }) + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + assert_eq!(1, *(*call_count).lock().await); + + // Now set a failpoint for find with 391 error code + let fail_point = FailPoint::fail_command(&["find", "saslStart"], FailPointMode::Times(2)) + .error_code(391); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + assert_eq!(3, *(*call_count).lock().await); + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn human_4_4_fails() -> anyhow::Result<()> { + let admin_client = Client::with_uri_str(&*MONGODB_URI).await?; + + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |_| { + let call_count = cb_call_count.clone(); + async move { + let mut cc = call_count.lock().await; + *cc += 1; + if *cc == 1 { + Ok(oidc::IdpServerResponse { + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: Some("fake refresh token".to_string()), + }) + } else { + Ok(oidc::IdpServerResponse { + access_token: "bad token".to_string(), + expires: None, + refresh_token: Some("fake refresh token".to_string()), + }) + } + } + .boxed() + })) + .build() + .into(); + let client = Client::with_options(opts)?; + + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + assert_eq!(1, *(*call_count).lock().await); + + // Now set a failpoint for find with 391 error code + let fail_point = FailPoint::fail_command(&["find", "saslStart"], FailPointMode::Times(3)) + .error_code(391); + let _guard = admin_client.enable_fail_point(fail_point).await.unwrap(); + + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + + assert_eq!(3, *(*call_count).lock().await); + Ok(()) + } + + // This is not in the spec, but the spec has no test that actually tests refresh flow + #[tokio::test] + async fn human_4_5_refresh_token_flow() -> anyhow::Result<()> { + // we need to assert the callback count + let call_count = Arc::new(Mutex::new(0)); + let cb_call_count = call_count.clone(); + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential = Credential::builder() + .mechanism(AuthMechanism::MongoDbOidc) + .oidc_callback(oidc::Callback::human(move |c| { + let call_count = cb_call_count.clone(); + // assert that the cached refresh token is passed to the callback + assert_eq!(c.refresh_token.as_deref(), Some("some fake refresh token")); + async move { + *call_count.lock().await += 1; + Ok(oidc::IdpServerResponse { + // since this test will use the cached token, this callback shouldn't matter + access_token: get_access_token_test_user_1().await, + expires: None, + refresh_token: None, + }) + } + .boxed() + })) + .build() + .into(); + + // put a fake refresh token in the cache + opts.credential + .as_mut() + .unwrap() + .oidc_callback + .set_refresh_token(Some("some fake refresh token".to_string())) + .await; + + let client = Client::with_options(opts)?; + + // Now find should succeed even though we have a fail point on saslStart because the spec + // auth should succeed. + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + + // the callback should have been called once + assert_eq!(1, *(*call_count).lock().await); + Ok(()) + } +} + +mod azure { + use crate::{ + bson::{doc, Document}, + client::{ + auth::oidc::{AZURE_ENVIRONMENT_VALUE_STR, ENVIRONMENT_PROP_STR}, + options::ClientOptions, + Client, + }, + test::spec::unified_runner::run_unified_tests, + }; + + use super::{remove_mechanism_properties_placeholder, MONGODB_URI_SINGLE}; + + #[tokio::test(flavor = "multi_thread")] + async fn run_unified() { + run_unified_tests(&["test_files"]) + .transform_files(remove_mechanism_properties_placeholder) + .use_exact_path() + .await; + } + + #[tokio::test] + async fn machine_5_1_azure_with_no_username() -> anyhow::Result<()> { + let opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + Ok(()) + } + + #[tokio::test] + async fn machine_5_2_azure_with_bad_username() -> anyhow::Result<()> { + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential.as_mut().unwrap().username = Some("bad".to_string()); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::Authentication { .. }, + )); + Ok(()) + } + + #[tokio::test] + async fn machine_5_3_token_resource_must_be_set_for_azure() -> anyhow::Result<()> { + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential.as_mut().unwrap().mechanism_properties = Some(doc! { + ENVIRONMENT_PROP_STR: AZURE_ENVIRONMENT_VALUE_STR, + }); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::InvalidArgument { .. }, + )); + Ok(()) + } +} + +mod gcp { + use crate::{ + bson::{doc, Document}, + client::{options::ClientOptions, Client}, + test::spec::unified_runner::run_unified_tests, + }; + + use super::{remove_mechanism_properties_placeholder, MONGODB_URI_SINGLE}; + + #[tokio::test(flavor = "multi_thread")] + async fn run_unified() { + run_unified_tests(&["test_files"]) + .transform_files(remove_mechanism_properties_placeholder) + .use_exact_path() + .await; + } + + #[tokio::test] + async fn machine_5_4_gcp_with_no_username() -> anyhow::Result<()> { + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential.as_mut().unwrap().source = None; + let client = Client::with_options(opts)?; + client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await?; + Ok(()) + } + + #[tokio::test] + async fn machine_5_5_token_resource_must_be_set_for_gcp() -> anyhow::Result<()> { + use crate::client::auth::oidc::{ENVIRONMENT_PROP_STR, GCP_ENVIRONMENT_VALUE_STR}; + + let mut opts = ClientOptions::parse(&*MONGODB_URI_SINGLE).await?; + opts.credential.as_mut().unwrap().source = None; + opts.credential.as_mut().unwrap().mechanism_properties = Some(doc! { + ENVIRONMENT_PROP_STR: GCP_ENVIRONMENT_VALUE_STR, + }); + let client = Client::with_options(opts)?; + let res = client + .database("test") + .collection::("test") + .find_one(doc! {}) + .await; + + assert!(res.is_err()); + assert!(matches!( + *res.unwrap_err().kind, + crate::error::ErrorKind::InvalidArgument { .. }, + )); + Ok(()) + } +} + +mod k8s { + use crate::test::spec::unified_runner::run_unified_tests; + + use super::remove_mechanism_properties_placeholder; + + #[tokio::test(flavor = "multi_thread")] + async fn run_unified() { + run_unified_tests(&["test_files"]) + .transform_files(remove_mechanism_properties_placeholder) + .use_exact_path() + .await; + } +} diff --git a/src/test/spec/read_write_concern.rs b/src/test/spec/read_write_concern.rs new file mode 100644 index 000000000..7d8b6595a --- /dev/null +++ b/src/test/spec/read_write_concern.rs @@ -0,0 +1,9 @@ +mod connection_string; +mod document; + +use crate::test::spec::unified_runner::run_unified_tests; + +#[tokio::test] +async fn operation() { + run_unified_tests(&["read-write-concern", "operation"]).await; +} diff --git a/src/test/spec/read_write_concern/connection_string.rs b/src/test/spec/read_write_concern/connection_string.rs index a4b1572e9..83e3d00ff 100644 --- a/src/test/spec/read_write_concern/connection_string.rs +++ b/src/test/spec/read_write_concern/connection_string.rs @@ -54,7 +54,7 @@ async fn run_connection_string_test(test_file: TestFile) { &normalize_write_concern_doc( options .write_concern - .map(|w| super::write_concern_to_document(&w) + .map(|w| crate::bson_compat::serialize_to_document(&w) .expect(&test_case.description)) .unwrap_or_default() ), @@ -71,8 +71,7 @@ async fn run_connection_string_test(test_file: TestFile) { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run() { run_spec_test( &["read-write-concern", "connection-string"], diff --git a/src/test/spec/read_write_concern/document.rs b/src/test/spec/read_write_concern/document.rs index 714558890..7b53c5144 100644 --- a/src/test/spec/read_write_concern/document.rs +++ b/src/test/spec/read_write_concern/document.rs @@ -30,29 +30,29 @@ async fn run_document_test(test_file: TestFile) { let description = test_case.description.as_str(); if let Some(specified_write_concern_document) = test_case.write_concern { - let specified_write_concern = - match bson::from_document::(specified_write_concern_document) - .map_err(Error::from) - .and_then(|wc| wc.validate().map(|_| wc)) - { - Ok(write_concern) => { - assert!( - test_case.valid, - "Write concern deserialization/validation should fail: {}", - description - ); - write_concern - } - Err(err) => { - assert!( - !test_case.valid, - "Write concern deserialization/validation should succeed but got \ - {:?}: {}", - err, description, - ); - continue; - } - }; + let specified_write_concern = match crate::bson_compat::deserialize_from_document::< + WriteConcern, + >(specified_write_concern_document) + .map_err(Error::from) + .and_then(|wc| wc.validate().map(|_| wc)) + { + Ok(write_concern) => { + assert!( + test_case.valid, + "Write concern deserialization/validation should fail: {}", + description + ); + write_concern + } + Err(err) => { + assert!( + !test_case.valid, + "Write concern deserialization/validation should succeed but got {:?}: {}", + err, description, + ); + continue; + } + }; if let Some(is_server_default) = test_case.is_server_default { assert_eq!( @@ -76,13 +76,15 @@ async fn run_document_test(test_file: TestFile) { ); } - let actual_write_concern_document = bson::to_document(&specified_write_concern) - .unwrap_or_else(|err| { - panic!( - "Write concern serialization should succeed but got {:?}: {}", - err, description - ) - }); + let actual_write_concern_document = crate::bson_compat::serialize_to_document( + &specified_write_concern, + ) + .unwrap_or_else(|err| { + panic!( + "Write concern serialization should succeed but got {:?}: {}", + err, description + ) + }); if let Some(expected_write_concern_document) = test_case.write_concern_document { assert_eq!( @@ -101,20 +103,23 @@ async fn run_document_test(test_file: TestFile) { } let specified_read_concern: ReadConcern = - bson::from_document(specified_read_concern_document).unwrap_or_else(|err| { - panic!( - "Read concern deserialization should succeed but got {:?}: {}", - err, description, - ) - }); + crate::bson_compat::deserialize_from_document(specified_read_concern_document) + .unwrap_or_else(|err| { + panic!( + "Read concern deserialization should succeed but got {:?}: {}", + err, description, + ) + }); - let actual_read_concern_document = bson::to_document(&specified_read_concern) - .unwrap_or_else(|err| { - panic!( - "Read concern serialization should succeed but got: {:?}: {}", - err, description - ) - }); + let actual_read_concern_document = crate::bson_compat::serialize_to_document( + &specified_read_concern, + ) + .unwrap_or_else(|err| { + panic!( + "Read concern serialization should succeed but got: {:?}: {}", + err, description + ) + }); if let Some(expected_read_concern_document) = test_case.read_concern_document { assert_eq!( @@ -127,8 +132,7 @@ async fn run_document_test(test_file: TestFile) { } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run() { run_spec_test(&["read-write-concern", "document"], run_document_test).await; } diff --git a/src/test/spec/read_write_concern/mod.rs b/src/test/spec/read_write_concern/mod.rs deleted file mode 100644 index c13f52922..000000000 --- a/src/test/spec/read_write_concern/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod connection_string; -mod document; -mod operation; - -use crate::{ - bson::{Bson, Document}, - error::Result, - options::WriteConcern, -}; - -fn write_concern_to_document(write_concern: &WriteConcern) -> Result { - match bson::to_bson(&write_concern)? { - Bson::Document(doc) => Ok(doc), - _ => unreachable!(), - } -} diff --git a/src/test/spec/read_write_concern/operation.rs b/src/test/spec/read_write_concern/operation.rs deleted file mode 100644 index 1323e3e6c..000000000 --- a/src/test/spec/read_write_concern/operation.rs +++ /dev/null @@ -1,10 +0,0 @@ -use tokio::sync::RwLockWriteGuard; - -use crate::test::{spec::v2_runner::run_v2_tests, LOCK}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run() { - let _lock: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - run_v2_tests(&["read-write-concern", "operation"]).await; -} diff --git a/src/test/spec/retryable_reads.rs b/src/test/spec/retryable_reads.rs index 058302542..131a61455 100644 --- a/src/test/spec/retryable_reads.rs +++ b/src/test/spec/retryable_reads.rs @@ -1,127 +1,117 @@ -use std::{sync::Arc, time::Duration}; +use std::{future::IntoFuture, sync::Arc, time::Duration}; -use bson::doc; -use tokio::sync::RwLockWriteGuard; +use crate::bson::doc; use crate::{ error::Result, event::{ - cmap::{CmapEvent, CmapEventHandler, ConnectionCheckoutFailedReason}, - command::CommandEventHandler, + cmap::{CmapEvent, ConnectionCheckoutFailedReason}, + command::CommandEvent, }, - runtime, - runtime::AsyncJoinHandle, + options::SelectionCriteria, + runtime::{self, AsyncJoinHandle}, test::{ + block_connection_supported, + fail_command_supported, + get_client_options, log_uncaptured, - spec::{unified_runner::run_unified_tests, v2_runner::run_v2_tests}, + spec::unified_runner::run_unified_tests, + topology_is_load_balanced, + topology_is_sharded, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, Event, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, }, + Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_legacy() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - run_v2_tests(&["retryable-reads", "legacy"]).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; run_unified_tests(&["retryable-reads", "unified"]).await; } /// Test ensures that the connection used in the first attempt of a retry is released back into the /// pool before the second attempt. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn retry_releases_connection() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; + if !fail_command_supported().await { + log_uncaptured("skipping retry_releases_connection due to failCommand not being supported"); + return; + } - let mut client_options = CLIENT_OPTIONS.get().await.clone(); + let mut client_options = get_client_options().await.clone(); client_options.hosts.drain(1..); client_options.retry_reads = Some(true); client_options.max_pool_size = Some(1); - let client = TestClient::with_options(Some(client_options)).await; - if !client.supports_fail_command() { - log_uncaptured("skipping retry_releases_connection due to failCommand not being supported"); - return; - } + let client = Client::for_test().options(client_options).await; let collection = client .database("retry_releases_connection") .collection("retry_releases_connection"); - collection.insert_one(doc! { "x": 1 }, None).await.unwrap(); + collection.insert_one(doc! { "x": 1 }).await.unwrap(); // Use a connection error to ensure streaming monitor checks get cancelled. Otherwise, we'd have // to wait for the entire heartbeatFrequencyMS before the find succeeds. - let options = FailCommandOptions::builder().close_connection(true).build(); - let failpoint = FailPoint::fail_command(&["find"], FailPointMode::Times(1), Some(options)); - let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap(); + let fail_point = + FailPoint::fail_command(&["find"], FailPointMode::Times(1)).close_connection(true); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); - runtime::timeout(Duration::from_secs(1), collection.find_one(doc! {}, None)) - .await - .expect("operation should not time out") - .expect("find should succeed"); + runtime::timeout( + Duration::from_secs(1), + collection.find_one(doc! {}).into_future(), + ) + .await + .expect("operation should not time out") + .expect("find should succeed"); } /// Prose test from retryable reads spec verifying that PoolClearedErrors are retried. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn retry_read_pool_cleared() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; + if !block_connection_supported().await { + log_uncaptured( + "skipping retry_read_pool_cleared due to blockConnection not being supported", + ); + return; + } + if topology_is_load_balanced().await { + log_uncaptured("skipping retry_read_pool_cleared due to load-balanced topology"); + return; + } - let handler = Arc::new(EventHandler::new()); + let buffer = EventBuffer::new(); - let mut client_options = CLIENT_OPTIONS.get().await.clone(); + let mut client_options = get_client_options().await.clone(); client_options.retry_reads = Some(true); client_options.max_pool_size = Some(1); - client_options.cmap_event_handler = Some(handler.clone() as Arc); - client_options.command_event_handler = Some(handler.clone() as Arc); + client_options.cmap_event_handler = Some(buffer.handler()); + client_options.command_event_handler = Some(buffer.handler()); // on sharded clusters, ensure only a single mongos is used if client_options.repl_set_name.is_none() { client_options.hosts.drain(1..); } - let client = TestClient::with_options(Some(client_options.clone())).await; - if !client.supports_block_connection() { - log_uncaptured( - "skipping retry_read_pool_cleared due to blockConnection not being supported", - ); - return; - } - if client.is_load_balanced() { - log_uncaptured("skipping retry_read_pool_cleared due to load-balanced topology"); - return; - } + let client = Client::for_test().options(client_options.clone()).await; let collection = client .database("retry_read_pool_cleared") .collection("retry_read_pool_cleared"); - collection.insert_one(doc! { "x": 1 }, None).await.unwrap(); + collection.insert_one(doc! { "x": 1 }).await.unwrap(); - let options = FailCommandOptions::builder() + let fail_point = FailPoint::fail_command(&["find"], FailPointMode::Times(1)) .error_code(91) - .block_connection(Duration::from_secs(1)) - .build(); - let failpoint = FailPoint::fail_command(&["find"], FailPointMode::Times(1), Some(options)); - let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap(); + .block_connection(Duration::from_secs(1)); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); - let mut subscriber = handler.subscribe(); + let mut event_stream = buffer.stream(); let mut tasks: Vec> = Vec::new(); for _ in 0..2 { let coll = collection.clone(); - let task = runtime::spawn(async move { coll.find_one(doc! {}, None).await }); + let task = runtime::spawn(async move { coll.find_one(doc! {}).await }); tasks.push(task); } @@ -131,24 +121,23 @@ async fn retry_read_pool_cleared() { .collect::>>() .expect("all should succeed"); - let _ = subscriber - .wait_for_event(Duration::from_millis(500), |event| { + let _ = event_stream + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Cmap(CmapEvent::ConnectionCheckedOut(_))) }) .await .expect("first checkout should succeed"); - let _ = subscriber - .wait_for_event(Duration::from_millis(500), |event| { + let _ = event_stream + .next_match(Duration::from_millis(500), |event| { matches!(event, Event::Cmap(CmapEvent::PoolCleared(_))) }) .await .expect("pool clear should occur"); - let next_cmap_events = subscriber - .collect_events(Duration::from_millis(1000), |event| match event { - Event::Cmap(_) => true, - _ => false, + let next_cmap_events = event_stream + .collect(Duration::from_millis(1000), |event| { + matches!(event, Event::Cmap(_)) }) .await; @@ -165,5 +154,150 @@ async fn retry_read_pool_cleared() { ); } - assert_eq!(handler.get_command_started_events(&["find"]).len(), 3); + assert_eq!(buffer.get_command_started_events(&["find"]).len(), 3); +} + +// Retryable Reads Are Retried on a Different mongos if One is Available +#[tokio::test(flavor = "multi_thread")] +async fn retry_read_different_mongos() { + if !fail_command_supported().await { + log_uncaptured("skipping retry_read_different_mongos: requires failCommand"); + return; + } + let mut client_options = get_client_options().await.clone(); + if !(topology_is_sharded().await && client_options.hosts.len() >= 2) { + log_uncaptured( + "skipping retry_read_different_mongos: requires sharded cluster with at least two \ + hosts", + ); + return; + } + client_options.hosts.drain(2..); + client_options.retry_reads = Some(true); + + let hosts = client_options.hosts.clone(); + let client = Client::for_test() + .options(client_options) + .monitor_events() + .await; + + // NOTE: This test uses a single client to set failpoints on each mongos and run the find + // operation. This avoids flakiness caused by a race between server discovery and server + // selection. + + // When a client is first created, it initializes its view of the topology with all configured + // mongos addresses, but marks each as Unknown until it completes the server discovery process + // by sending and receiving "hello" messages Unknown servers are not eligible for server + // selection. + + // Previously, we created a new client for each call to `enable_fail_point` and for the find + // operation. Each new client restarted the discovery process, and sometimes had not yet marked + // both mongos servers as usable, leading to test failures when the retry logic couldn't find a + // second eligible server. + + // By reusing a single client, each `enable_fail_point` call forces discovery to complete for + // the corresponding mongos. As a result, when the find operation runs, the client has a + // fully discovered topology and can reliably select between both servers. + let mut guards = Vec::new(); + for address in hosts { + let address = address.clone(); + let fail_point = FailPoint::fail_command(&["find"], FailPointMode::Times(1)) + .error_code(6) + .selection_criteria(SelectionCriteria::Predicate(Arc::new(move |info| { + info.description.address == address + }))); + guards.push(client.enable_fail_point(fail_point).await.unwrap()); + } + + let result = client + .database("test") + .collection::("retry_read_different_mongos") + .find(doc! {}) + .await; + assert!(result.is_err()); + let events = client.events.get_command_events(&["find"]); + assert!( + matches!( + &events[..], + &[ + CommandEvent::Started(_), + CommandEvent::Failed(_), + CommandEvent::Started(_), + CommandEvent::Failed(_), + ] + ), + "unexpected events: {:#?}", + events, + ); + let first_failed = events[1].as_command_failed().unwrap(); + let first_address = &first_failed.connection.address; + let second_failed = events[3].as_command_failed().unwrap(); + let second_address = &second_failed.connection.address; + assert_ne!( + first_address, second_address, + "Failed commands did not occur on two different mongos instances" + ); + + drop(guards); // enforce lifetime +} + +// Retryable Reads Are Retried on the Same mongos if No Others are Available +#[tokio::test(flavor = "multi_thread")] +async fn retry_read_same_mongos() { + if !fail_command_supported().await { + log_uncaptured("skipping retry_read_same_mongos: requires failCommand"); + return; + } + if !topology_is_sharded().await { + log_uncaptured("skipping retry_read_same_mongos: requires sharded cluster"); + return; + } + + let mut client_options = get_client_options().await.clone(); + client_options.hosts.drain(1..); + client_options.retry_reads = Some(true); + let fp_guard = { + let mut client_options = client_options.clone(); + client_options.direct_connection = Some(true); + let client = Client::for_test().options(client_options).await; + + let fail_point = FailPoint::fail_command(&["find"], FailPointMode::Times(1)).error_code(6); + client.enable_fail_point(fail_point).await.unwrap() + }; + + client_options.direct_connection = Some(false); + let client = Client::for_test() + .options(client_options) + .monitor_events() + .await; + let result = client + .database("test") + .collection::("retry_read_same_mongos") + .find(doc! {}) + .await; + assert!(result.is_ok(), "{:?}", result); + let events = client.events.get_command_events(&["find"]); + assert!( + matches!( + &events[..], + &[ + CommandEvent::Started(_), + CommandEvent::Failed(_), + CommandEvent::Started(_), + CommandEvent::Succeeded(_), + ] + ), + "unexpected events: {:#?}", + events, + ); + let first_failed = events[1].as_command_failed().unwrap(); + let first_address = &first_failed.connection.address; + let second_failed = events[3].as_command_succeeded().unwrap(); + let second_address = &second_failed.connection.address; + assert_eq!( + first_address, second_address, + "Failed command and retry did not occur on the same mongos instance", + ); + + drop(fp_guard); // enforce lifetime } diff --git a/src/test/spec/retryable_writes.rs b/src/test/spec/retryable_writes.rs new file mode 100644 index 000000000..0d0f14b11 --- /dev/null +++ b/src/test/spec/retryable_writes.rs @@ -0,0 +1,451 @@ +use std::{sync::Arc, time::Duration}; + +use crate::{bson::Bson, options::SelectionCriteria}; +use tokio::sync::Mutex; + +use crate::{ + bson::{doc, Document}, + error::{ErrorKind, Result, RETRYABLE_WRITE_ERROR}, + event::{ + cmap::{CmapEvent, ConnectionCheckoutFailedReason}, + command::CommandEvent, + }, + runtime::{self, spawn, AcknowledgedMessage, AsyncJoinHandle}, + test::{ + block_connection_supported, + fail_command_supported, + get_client_options, + log_uncaptured, + server_version_gt, + server_version_lt, + spec::unified_runner::run_unified_tests, + topology_is_load_balanced, + topology_is_replica_set, + topology_is_sharded, + topology_is_standalone, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, + Event, + TestClient, + }, + Client, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn run_unified() { + run_unified_tests(&["retryable-writes", "unified"]) + // The driver does not support unacknowledged writes + .skip_files(&["unacknowledged-write-concern.json"]) + .await; +} + +#[tokio::test] +#[function_name::named] +async fn mmapv1_error_raised() { + if server_version_gt(4, 0).await || !topology_is_replica_set().await { + log_uncaptured("skipping mmapv1_error_raised due to test topology"); + return; + } + + let client = Client::for_test().await; + let coll = client.init_db_and_coll(function_name!(), "coll").await; + + let server_status = client + .database(function_name!()) + .run_command(doc! { "serverStatus": 1 }) + .await + .unwrap(); + let name = server_status + .get_document("storageEngine") + .unwrap() + .get_str("name") + .unwrap(); + if name != "mmapv1" { + log_uncaptured("skipping mmapv1_error_raised due to unsupported storage engine"); + return; + } + + let err = coll.insert_one(doc! { "x": 1 }).await.unwrap_err(); + match *err.kind { + ErrorKind::Command(err) => { + assert_eq!( + err.message, + "This MongoDB deployment does not support retryable writes. Please add \ + retryWrites=false to your connection string." + ); + } + e => panic!("expected command error, got: {:?}", e), + } +} + +#[tokio::test] +async fn label_not_added_first_read_error() { + label_not_added(false).await; +} + +#[tokio::test] +async fn label_not_added_second_read_error() { + label_not_added(true).await; +} + +#[function_name::named] +async fn label_not_added(retry_reads: bool) { + if !fail_command_supported().await { + log_uncaptured("skipping label_not_added due to fail command unsupported"); + return; + } + + let mut options = get_client_options().await.clone(); + options.retry_reads = Some(retry_reads); + let client = Client::for_test() + .options(options) + .use_single_mongos() + .await; + + let coll = client + .init_db_and_coll(&format!("{}{}", function_name!(), retry_reads), "coll") + .await; + + let failpoint = doc! { + "configureFailPoint": "failCommand", + "mode": { "times": if retry_reads { 2 } else { 1 } }, + "data": { + "failCommands": ["find"], + "errorCode": 11600 + } + }; + client + .database("admin") + .run_command(failpoint) + .await + .unwrap(); + + let err = coll.find(doc! {}).await.unwrap_err(); + + assert!(!err.contains_label("RetryableWriteError")); +} + +/// Prose test from retryable writes spec verifying that PoolClearedErrors are retried. +#[tokio::test(flavor = "multi_thread")] +async fn retry_write_pool_cleared() { + if topology_is_standalone().await { + log_uncaptured("skipping retry_write_pool_cleared due standalone topology"); + return; + } + if topology_is_load_balanced().await { + log_uncaptured("skipping retry_write_pool_cleared due to load-balanced topology"); + return; + } + if !block_connection_supported().await { + log_uncaptured( + "skipping retry_write_pool_cleared due to blockConnection not being supported", + ); + return; + } + + let buffer = EventBuffer::new(); + + let mut client_options = get_client_options().await.clone(); + client_options.retry_writes = Some(true); + client_options.max_pool_size = Some(1); + client_options.cmap_event_handler = Some(buffer.handler()); + client_options.command_event_handler = Some(buffer.handler()); + // on sharded clusters, ensure only a single mongos is used + if client_options.repl_set_name.is_none() { + client_options.hosts.drain(1..); + } + + let client = Client::for_test().options(client_options.clone()).await; + + let collection = client + .database("retry_write_pool_cleared") + .collection("retry_write_pool_cleared"); + + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)) + .error_code(91) + .block_connection(Duration::from_secs(1)) + .error_labels(vec![RETRYABLE_WRITE_ERROR]); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); + + let mut event_stream = buffer.stream(); + + let mut tasks: Vec> = Vec::new(); + for _ in 0..2 { + let coll = collection.clone(); + let task = runtime::spawn(async move { coll.insert_one(doc! {}).await }); + tasks.push(task); + } + + futures::future::join_all(tasks) + .await + .into_iter() + .collect::>>() + .expect("all should succeeed"); + + let _ = event_stream + .next_match(Duration::from_millis(500), |event| { + matches!(event, Event::Cmap(CmapEvent::ConnectionCheckedOut(_))) + }) + .await + .expect("first checkout should succeed"); + + let _ = event_stream + .next_match(Duration::from_millis(500), |event| { + matches!(event, Event::Cmap(CmapEvent::PoolCleared(_))) + }) + .await + .expect("pool clear should occur"); + + let next_cmap_events = event_stream + .collect(Duration::from_millis(1000), |event| { + matches!(event, Event::Cmap(_)) + }) + .await; + + if !next_cmap_events.iter().any(|event| match event { + Event::Cmap(CmapEvent::ConnectionCheckoutFailed(e)) => { + matches!(e.reason, ConnectionCheckoutFailedReason::ConnectionError) + } + _ => false, + }) { + panic!( + "Expected second checkout to fail, but no ConnectionCheckoutFailed event observed. \ + CMAP events:\n{:?}", + next_cmap_events + ); + } + + assert_eq!(buffer.get_command_started_events(&["insert"]).len(), 3); +} + +/// Prose test from retryable writes spec verifying that the original error is returned after +/// encountering a WriteConcernError with a RetryableWriteError label. +#[tokio::test(flavor = "multi_thread")] +async fn retry_write_retryable_write_error() { + if !topology_is_replica_set().await || server_version_lt(6, 0).await { + log_uncaptured("skipping retry_write_retryable_write_error: invalid topology"); + return; + } + + let mut client_options = get_client_options().await.clone(); + client_options.retry_writes = Some(true); + let (event_tx, event_rx) = tokio::sync::mpsc::channel::>(1); + // The listener needs to be active on client startup, but also needs a handle to the client + // itself for the trigger action. + let listener_client: Arc>> = Arc::new(Mutex::new(None)); + // Set up event listener + let (fp_tx, mut fp_rx) = tokio::sync::mpsc::unbounded_channel(); + { + let client = listener_client.clone(); + let mut event_rx = event_rx; + let fp_tx = fp_tx.clone(); + // Spawn a task to watch the event channel + spawn(async move { + while let Some(msg) = event_rx.recv().await { + if let CommandEvent::Succeeded(ev) = &*msg { + if let Some(Bson::Document(wc_err)) = ev.reply.get("writeConcernError") { + if ev.command_name == "insert" + && wc_err.get_i32("code").is_ok_and(|c| c == 91) + { + // Spawn a new task so events continue to process + let client = client.clone(); + let fp_tx = fp_tx.clone(); + spawn(async move { + // Enable the failpoint. + let fp_guard = { + let client = client.lock().await; + let fail_point = FailPoint::fail_command( + &["insert"], + FailPointMode::Times(1), + ) + .error_code(10107) + .error_labels(vec!["RetryableWriteError", "NoWritesPerformed"]); + client + .as_ref() + .unwrap() + .enable_fail_point(fail_point) + .await + .unwrap() + }; + fp_tx.send(fp_guard).unwrap(); + // Defer acknowledging the message until the failpoint has been set + // up so the retry hits it. + msg.acknowledge(()); + }); + } + } + } + } + }); + } + client_options.test_options_mut().async_event_listener = Some(event_tx); + let client = Client::for_test().options(client_options).await; + *listener_client.lock().await = Some(client.clone()); + + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)) + .write_concern_error(doc! { + "code": 91, + "errorLabels": ["RetryableWriteError"], + }); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); + + let result = client + .database("test") + .collection::("test") + .insert_one(doc! { "hello": "there" }) + .await; + assert_eq!(result.unwrap_err().code(), Some(91)); + + // Consume failpoint guard. + let _ = fp_rx.recv().await; +} + +// Test that in a sharded cluster writes are retried on a different mongos if one available +#[tokio::test(flavor = "multi_thread")] +async fn retry_write_different_mongos() { + if !fail_command_supported().await { + log_uncaptured("skipping retry_write_different_mongos: requires failCommand"); + return; + } + let mut client_options = get_client_options().await.clone(); + if !(topology_is_sharded().await && client_options.hosts.len() >= 2) { + log_uncaptured( + "skipping retry_write_different_mongos: requires sharded cluster with at least two \ + hosts", + ); + return; + } + + // NOTE: This test uses a single client to set failpoints on each mongos and run the insert + // operation. This avoids flakiness caused by a race between server discovery and server + // selection. + + // When a client is first created, it initializes its view of the topology with all configured + // mongos addresses, but marks each as Unknown until it completes the server discovery process + // by sending and receiving "hello" messages Unknown servers are not eligible for server + // selection. + + // Previously, we created a new client for each call to `enable_fail_point` and for the insert + // operation. Each new client restarted the discovery process, and sometimes had not yet marked + // both mongos servers as usable, leading to test failures when the retry logic couldn't insert + // a second eligible server. + + // By reusing a single client, each `enable_fail_point` call forces discovery to complete for + // the corresponding mongos. As a result, when the insert operation runs, the client has a + // fully discovered topology and can reliably select between both servers. + client_options.hosts.drain(2..); + client_options.retry_writes = Some(true); + let hosts = client_options.hosts.clone(); + let client = Client::for_test() + .options(client_options) + .monitor_events() + .await; + + let mut guards = Vec::new(); + for address in hosts { + let address = address.clone(); + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)) + .error_code(6) + .error_labels([RETRYABLE_WRITE_ERROR]) + .selection_criteria(SelectionCriteria::Predicate(Arc::new(move |info| { + info.description.address == address + }))); + guards.push(client.enable_fail_point(fail_point).await.unwrap()); + } + + let result = client + .database("test") + .collection::("retry_write_different_mongos") + .insert_one(doc! {}) + .await; + assert!(result.is_err()); + let events = client.events.get_command_events(&["insert"]); + assert!( + matches!( + &events[..], + &[ + CommandEvent::Started(_), + CommandEvent::Failed(_), + CommandEvent::Started(_), + CommandEvent::Failed(_), + ] + ), + "unexpected events: {:#?}", + events, + ); + let first_failed = events[1].as_command_failed().unwrap(); + let first_address = &first_failed.connection.address; + let second_failed = events[3].as_command_failed().unwrap(); + let second_address = &second_failed.connection.address; + assert_ne!( + first_address, second_address, + "Failed commands did not occur on two different mongos instances" + ); + + drop(guards); // enforce lifetime +} + +// Retryable Reads Are Retried on the Same mongos if No Others are Available +#[tokio::test(flavor = "multi_thread")] +async fn retry_write_same_mongos() { + if !fail_command_supported().await { + log_uncaptured("skipping retry_write_same_mongos: requires failCommand"); + return; + } + if !topology_is_sharded().await { + log_uncaptured("skipping retry_write_same_mongos: requires sharded cluster"); + return; + } + + let mut client_options = get_client_options().await.clone(); + client_options.hosts.drain(1..); + client_options.retry_writes = Some(true); + let fp_guard = { + let mut client_options = client_options.clone(); + client_options.direct_connection = Some(true); + let client = Client::for_test().options(client_options).await; + + let fail_point = FailPoint::fail_command(&["insert"], FailPointMode::Times(1)) + .error_code(6) + .error_labels(vec![RETRYABLE_WRITE_ERROR]); + client.enable_fail_point(fail_point).await.unwrap() + }; + + client_options.direct_connection = Some(false); + let client = Client::for_test() + .options(client_options) + .monitor_events() + .await; + let result = client + .database("test") + .collection::("retry_write_same_mongos") + .insert_one(doc! {}) + .await; + assert!(result.is_ok(), "{:?}", result); + let events = client.events.get_command_events(&["insert"]); + assert!( + matches!( + &events[..], + &[ + CommandEvent::Started(_), + CommandEvent::Failed(_), + CommandEvent::Started(_), + CommandEvent::Succeeded(_), + ] + ), + "unexpected events: {:#?}", + events, + ); + let first_failed = events[1].as_command_failed().unwrap(); + let first_address = &first_failed.connection.address; + let second_failed = events[3].as_command_succeeded().unwrap(); + let second_address = &second_failed.connection.address; + assert_eq!( + first_address, second_address, + "Failed commands did not occur on the same mongos instance", + ); + + drop(fp_guard); // enforce lifetime +} diff --git a/src/test/spec/retryable_writes/mod.rs b/src/test/spec/retryable_writes/mod.rs deleted file mode 100644 index a39b0a678..000000000 --- a/src/test/spec/retryable_writes/mod.rs +++ /dev/null @@ -1,501 +0,0 @@ -mod test_file; - -use std::{sync::Arc, time::Duration}; - -use futures::stream::TryStreamExt; -use semver::VersionReq; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; - -use test_file::{TestFile, TestResult}; - -use crate::{ - bson::{doc, Document}, - error::{ErrorKind, Result, RETRYABLE_WRITE_ERROR}, - event::{ - cmap::{CmapEvent, CmapEventHandler, ConnectionCheckoutFailedReason}, - command::CommandEventHandler, - }, - options::{ClientOptions, FindOptions, InsertManyOptions}, - runtime, - runtime::AsyncJoinHandle, - sdam::MIN_HEARTBEAT_FREQUENCY, - test::{ - assert_matches, - log_uncaptured, - run_spec_test, - spec::unified_runner::run_unified_tests, - util::get_default_name, - Event, - EventClient, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, - }, -}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - run_unified_tests(&["retryable-writes", "unified"]).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_legacy() { - async fn run_test(test_file: TestFile) { - for mut test_case in test_file.tests { - if test_case.operation.name == "bulkWrite" { - continue; - } - let mut options = test_case.client_options.unwrap_or_default(); - options.hosts = CLIENT_OPTIONS.get().await.hosts.clone(); - if options.heartbeat_freq.is_none() { - options.heartbeat_freq = Some(MIN_HEARTBEAT_FREQUENCY); - } - - let client = EventClient::with_additional_options( - options, - Some(Duration::from_millis(50)), - test_case.use_multiple_mongoses, - None, - ) - .await; - - if let Some(ref run_on) = test_file.run_on { - let can_run_on = run_on.iter().any(|run_on| run_on.can_run_on(&client)); - if !can_run_on { - log_uncaptured(format!("Skipping {}", test_case.description)); - continue; - } - } - - let db_name = get_default_name(&test_case.description); - let coll_name = "coll"; - - let coll = client.init_db_and_coll(&db_name, coll_name).await; - - if !test_file.data.is_empty() { - coll.insert_many(test_file.data.clone(), None) - .await - .expect(&test_case.description); - } - - let _fp_guard = if let Some(ref mut fail_point) = test_case.fail_point { - Some(fail_point.enable(&client, None).await.unwrap_or_else(|e| { - panic!( - "{}: error enabling failpoint: {:#?}", - test_case.description, e - ) - })) - } else { - None - }; - - let coll = client.database(&db_name).collection(coll_name); - let result = test_case.operation.execute_on_collection(&coll, None).await; - - // Disable the failpoint, if any. - drop(_fp_guard); - - if let Some(error) = test_case.outcome.error { - assert_eq!( - error, - result.is_err(), - "{}: expected error: {}, got {:?}", - &test_case.description, - error, - result - ); - } - - if let Some(expected_result) = test_case.outcome.result { - match expected_result { - TestResult::Value(value) => { - let description = &test_case.description; - let result = result - .unwrap_or_else(|e| { - panic!( - "{:?}: operation should succeed, got error: {}", - description, e - ) - }) - .unwrap(); - assert_matches(&result, &value, Some(description)); - } - TestResult::Labels(expected_labels) => { - let error = result.expect_err(&format!( - "{:?}: operation should fail", - &test_case.description - )); - - let description = &test_case.description; - if let Some(contain) = expected_labels.error_labels_contain { - contain.iter().for_each(|label| { - assert!( - error.contains_label(label), - "{}: error labels should include {}", - description, - label, - ); - }); - } - - if let Some(omit) = expected_labels.error_labels_omit { - omit.iter().for_each(|label| { - assert!( - !error.contains_label(label), - "{}: error labels should not include {}", - description, - label, - ); - }); - } - } - }; - } - - let coll_name = match test_case.outcome.collection.name { - Some(name) => name, - None => coll_name.to_string(), - }; - - let coll = client.get_coll(&db_name, &coll_name); - let options = FindOptions::builder().sort(doc! { "_id": 1 }).build(); - let actual_data: Vec = coll - .find(None, options) - .await - .unwrap() - .try_collect() - .await - .unwrap(); - assert_eq!(test_case.outcome.collection.data, actual_data); - - client.database(&db_name).drop(None).await.unwrap(); - } - } - - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - run_spec_test(&["retryable-writes", "legacy"], run_test).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn transaction_ids_excluded() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if !(client.is_replica_set() || client.is_sharded()) { - log_uncaptured("skipping transaction_ids_excluded due to test topology"); - return; - } - - let coll = client.init_db_and_coll(function_name!(), "coll").await; - - let excludes_txn_number = |command_name: &str| -> bool { - let (started, _) = client.get_successful_command_execution(command_name); - !started.command.contains_key("txnNumber") - }; - - coll.update_many(doc! {}, doc! { "$set": doc! { "x": 1 } }, None) - .await - .unwrap(); - assert!(excludes_txn_number("update")); - - coll.delete_many(doc! {}, None).await.unwrap(); - assert!(excludes_txn_number("delete")); - - coll.aggregate( - vec![ - doc! { "$match": doc! { "x": 1 } }, - doc! { "$out": "other_coll" }, - ], - None, - ) - .await - .unwrap(); - assert!(excludes_txn_number("aggregate")); - - let req = semver::VersionReq::parse(">=4.2").unwrap(); - if req.matches(&client.server_version) { - coll.aggregate( - vec![ - doc! { "$match": doc! { "x": 1 } }, - doc! { "$merge": "other_coll" }, - ], - None, - ) - .await - .unwrap(); - assert!(excludes_txn_number("aggregate")); - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn transaction_ids_included() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; - - if !(client.is_replica_set() || client.is_sharded()) { - log_uncaptured("skipping transaction_ids_included due to test topology"); - return; - } - - let coll = client.init_db_and_coll(function_name!(), "coll").await; - - let includes_txn_number = |command_name: &str| -> bool { - let (started, _) = client.get_successful_command_execution(command_name); - started.command.contains_key("txnNumber") - }; - - coll.insert_one(doc! { "x": 1 }, None).await.unwrap(); - assert!(includes_txn_number("insert")); - - coll.update_one(doc! {}, doc! { "$set": doc! { "x": 1 } }, None) - .await - .unwrap(); - assert!(includes_txn_number("update")); - - coll.replace_one(doc! {}, doc! { "x": 1 }, None) - .await - .unwrap(); - assert!(includes_txn_number("update")); - - coll.delete_one(doc! {}, None).await.unwrap(); - assert!(includes_txn_number("delete")); - - coll.find_one_and_delete(doc! {}, None).await.unwrap(); - assert!(includes_txn_number("findAndModify")); - - coll.find_one_and_replace(doc! {}, doc! { "x": 1 }, None) - .await - .unwrap(); - assert!(includes_txn_number("findAndModify")); - - coll.find_one_and_update(doc! {}, doc! { "$set": doc! { "x": 1 } }, None) - .await - .unwrap(); - assert!(includes_txn_number("findAndModify")); - - let options = InsertManyOptions::builder().ordered(true).build(); - coll.insert_many(vec![doc! { "x": 1 }], options) - .await - .unwrap(); - assert!(includes_txn_number("insert")); - - let options = InsertManyOptions::builder().ordered(false).build(); - coll.insert_many(vec![doc! { "x": 1 }], options) - .await - .unwrap(); - assert!(includes_txn_number("insert")); -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -#[function_name::named] -async fn mmapv1_error_raised() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = TestClient::new().await; - - let req = semver::VersionReq::parse("<=4.0").unwrap(); - if !req.matches(&client.server_version) || !client.is_replica_set() { - log_uncaptured("skipping mmapv1_error_raised due to test topology"); - return; - } - - let coll = client.init_db_and_coll(function_name!(), "coll").await; - - let server_status = client - .database(function_name!()) - .run_command(doc! { "serverStatus": 1 }, None) - .await - .unwrap(); - let name = server_status - .get_document("storageEngine") - .unwrap() - .get_str("name") - .unwrap(); - if name != "mmapv1" { - log_uncaptured("skipping mmapv1_error_raised due to unsupported storage engine"); - return; - } - - let err = coll.insert_one(doc! { "x": 1 }, None).await.unwrap_err(); - match *err.kind { - ErrorKind::Command(err) => { - assert_eq!( - err.message, - "This MongoDB deployment does not support retryable writes. Please add \ - retryWrites=false to your connection string." - ); - } - e => panic!("expected command error, got: {:?}", e), - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn label_not_added_first_read_error() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - label_not_added(false).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn label_not_added_second_read_error() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - label_not_added(true).await; -} - -#[function_name::named] -async fn label_not_added(retry_reads: bool) { - let options = ClientOptions::builder() - .hosts(vec![]) - .retry_reads(retry_reads) - .build(); - let client = TestClient::with_additional_options(Some(options)).await; - - // Configuring a failpoint is only supported on 4.0+ replica sets and 4.1.5+ sharded clusters. - let req = VersionReq::parse(">=4.0").unwrap(); - let sharded_req = VersionReq::parse(">=4.1.5").unwrap(); - if client.is_sharded() && !sharded_req.matches(&client.server_version) - || !req.matches(&client.server_version) - { - log_uncaptured( - "skipping label_not_added due to unsupported replica set or sharded cluster version", - ); - return; - } - - let coll = client - .init_db_and_coll(&format!("{}{}", function_name!(), retry_reads), "coll") - .await; - - let failpoint = doc! { - "configureFailPoint": "failCommand", - "mode": { "times": if retry_reads { 2 } else { 1 } }, - "data": { - "failCommands": ["find"], - "errorCode": 11600 - } - }; - client - .database("admin") - .run_command(failpoint, None) - .await - .unwrap(); - - let err = coll.find(doc! {}, None).await.unwrap_err(); - - assert!(!err.contains_label("RetryableWriteError")); -} - -/// Prose test from retryable writes spec verifying that PoolClearedErrors are retried. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn retry_write_pool_cleared() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - let handler = Arc::new(EventHandler::new()); - - let mut client_options = CLIENT_OPTIONS.get().await.clone(); - client_options.retry_writes = Some(true); - client_options.max_pool_size = Some(1); - client_options.cmap_event_handler = Some(handler.clone() as Arc); - client_options.command_event_handler = Some(handler.clone() as Arc); - // on sharded clusters, ensure only a single mongos is used - if client_options.repl_set_name.is_none() { - client_options.hosts.drain(1..); - } - - let client = TestClient::with_options(Some(client_options.clone())).await; - if !client.supports_block_connection() { - log_uncaptured( - "skipping retry_write_pool_cleared due to blockConnection not being supported", - ); - return; - } - - if client.is_standalone() { - log_uncaptured("skipping retry_write_pool_cleared due standalone topology"); - return; - } - - if client.is_load_balanced() { - log_uncaptured("skipping retry_write_pool_cleared due to load-balanced topology"); - return; - } - - let collection = client - .database("retry_write_pool_cleared") - .collection("retry_write_pool_cleared"); - - let options = FailCommandOptions::builder() - .error_code(91) - .block_connection(Duration::from_secs(1)) - .error_labels(vec![RETRYABLE_WRITE_ERROR.to_string()]) - .build(); - let failpoint = FailPoint::fail_command(&["insert"], FailPointMode::Times(1), Some(options)); - let _fp_guard = client.enable_failpoint(failpoint, None).await.unwrap(); - - let mut subscriber = handler.subscribe(); - - let mut tasks: Vec> = Vec::new(); - for _ in 0..2 { - let coll = collection.clone(); - let task = runtime::spawn(async move { coll.insert_one(doc! {}, None).await }); - tasks.push(task); - } - - futures::future::join_all(tasks) - .await - .into_iter() - .collect::>>() - .expect("all should succeeed"); - - let _ = subscriber - .wait_for_event(Duration::from_millis(500), |event| { - matches!(event, Event::Cmap(CmapEvent::ConnectionCheckedOut(_))) - }) - .await - .expect("first checkout should succeed"); - - let _ = subscriber - .wait_for_event(Duration::from_millis(500), |event| { - matches!(event, Event::Cmap(CmapEvent::PoolCleared(_))) - }) - .await - .expect("pool clear should occur"); - - let next_cmap_events = subscriber - .collect_events(Duration::from_millis(1000), |event| match event { - Event::Cmap(_) => true, - _ => false, - }) - .await; - - if !next_cmap_events.iter().any(|event| match event { - Event::Cmap(CmapEvent::ConnectionCheckoutFailed(e)) => { - matches!(e.reason, ConnectionCheckoutFailedReason::ConnectionError) - } - _ => false, - }) { - panic!( - "Expected second checkout to fail, but no ConnectionCheckoutFailed event observed. \ - CMAP events:\n{:?}", - next_cmap_events - ); - } - - assert_eq!(handler.get_command_started_events(&["insert"]).len(), 3); -} diff --git a/src/test/spec/retryable_writes/test_file.rs b/src/test/spec/retryable_writes/test_file.rs deleted file mode 100644 index b3c72b520..000000000 --- a/src/test/spec/retryable_writes/test_file.rs +++ /dev/null @@ -1,54 +0,0 @@ -use serde::Deserialize; - -use super::super::{Operation, RunOn}; -use crate::{ - bson::{Bson, Document}, - options::ClientOptions, - test::FailPoint, -}; - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct TestFile { - pub(crate) run_on: Option>, - pub(crate) data: Vec, - pub(crate) tests: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub(crate) struct TestCase { - pub(crate) description: String, - pub(crate) client_options: Option, - pub(crate) use_multiple_mongoses: Option, - pub(crate) fail_point: Option, - pub(crate) operation: Operation, - pub(crate) outcome: Outcome, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct Outcome { - pub(crate) error: Option, - pub(crate) result: Option, - pub(crate) collection: CollectionOutcome, -} - -#[derive(Debug, Deserialize)] -#[serde(untagged)] -pub(crate) enum TestResult { - Labels(Labels), - Value(Bson), -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(crate) struct Labels { - pub(crate) error_labels_contain: Option>, - pub(crate) error_labels_omit: Option>, -} - -#[derive(Debug, Deserialize)] -pub(crate) struct CollectionOutcome { - pub(crate) name: Option, - pub(crate) data: Vec, -} diff --git a/src/test/spec/run_command.rs b/src/test/spec/run_command.rs new file mode 100644 index 000000000..f7f6a335b --- /dev/null +++ b/src/test/spec/run_command.rs @@ -0,0 +1,11 @@ +use crate::test::spec::unified_runner::run_unified_tests; + +#[tokio::test(flavor = "multi_thread")] // multi_thread required for FailPoint +async fn run_unified() { + run_unified_tests(&["run-command", "unified"]) + .skip_tests(&[ + // TODO RUST-1649: unskip when withTransaction is implemented + "attaches transaction fields to given command", + ]) + .await; +} diff --git a/src/test/spec/sdam.rs b/src/test/spec/sdam.rs index 7e2be2089..99977ea1d 100644 --- a/src/test/spec/sdam.rs +++ b/src/test/spec/sdam.rs @@ -1,56 +1,65 @@ -use std::{sync::Arc, time::Duration}; +use std::time::Duration; -use bson::{doc, Document}; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; +use crate::bson::{doc, Document}; use crate::{ + event::sdam::SdamEvent, hello::LEGACY_HELLO_COMMAND_NAME, runtime, test::{ + block_connection_supported, + get_client_options, log_uncaptured, spec::unified_runner::run_unified_tests, + streaming_monitor_protocol_supported, + topology_is_load_balanced, + util::{ + event_buffer::EventBuffer, + fail_point::{FailPoint, FailPointMode}, + }, Event, - EventHandler, - FailCommandOptions, - FailPoint, - FailPointMode, - SdamEvent, - TestClient, - CLIENT_OPTIONS, - LOCK, }, Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; + // TODO RUST-1222: Unskip this file + let mut skipped_files = vec!["interruptInUse-pool-clear.json"]; + if cfg!(not(feature = "tracing-unstable")) { + skipped_files.extend_from_slice(&[ + "logging-standalone.json", + "logging-replicaset.json", + "logging-sharded.json", + "logging-loadbalanced.json", + ]); + } + run_unified_tests(&["server-discovery-and-monitoring", "unified"]) + .skip_files(&skipped_files) .skip_tests(&[ // The driver does not support socketTimeoutMS. "Reset server and pool after network timeout error during authentication", "Ignore network timeout error on find", + // TODO RUST-2068: unskip these tests + "Pool is cleared on handshake error during minPoolSize population", + "Pool is cleared on authentication error during minPoolSize population", ]) .await; } /// Streaming protocol prose test 1 from SDAM spec tests. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn streaming_min_heartbeat_frequency() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let test_client = TestClient::new().await; - if test_client.is_load_balanced() { + if topology_is_load_balanced().await { log_uncaptured("skipping streaming_min_heartbeat_frequency due to load balanced topology"); return; } - let handler = Arc::new(EventHandler::new()); - let mut options = CLIENT_OPTIONS.get().await.clone(); + let buffer = EventBuffer::new(); + let mut options = get_client_options().await.clone(); options.heartbeat_freq = Some(Duration::from_millis(500)); - options.sdam_event_handler = Some(handler.clone()); + options.sdam_event_handler = Some(buffer.handler()); let hosts = options.hosts.clone(); @@ -58,7 +67,7 @@ async fn streaming_min_heartbeat_frequency() { // discover a server client .database("admin") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await .unwrap(); @@ -66,12 +75,13 @@ async fn streaming_min_heartbeat_frequency() { // 500ms for 5 heartbeats. let mut tasks = Vec::new(); for address in hosts { - let h = handler.clone(); + let h = buffer.clone(); tasks.push(runtime::spawn(async move { - let mut subscriber = h.subscribe(); + + let mut event_stream = h.stream(); for _ in 0..5 { - let event = subscriber - .wait_for_event(Duration::from_millis(750), |e| { + let event = event_stream + .next_match(Duration::from_millis(750), |e| { matches!(e, Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(e)) if e.server_address == address) }) .await; @@ -89,21 +99,17 @@ async fn streaming_min_heartbeat_frequency() { } /// Variant of the previous prose test that checks for a non-minHeartbeatFrequencyMS value. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn heartbeat_frequency_is_respected() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - - let test_client = TestClient::new().await; - if test_client.is_load_balanced() { + if topology_is_load_balanced().await { log_uncaptured("skipping streaming_min_heartbeat_frequency due to load balanced topology"); return; } - let handler = Arc::new(EventHandler::new()); - let mut options = CLIENT_OPTIONS.get().await.clone(); + let buffer = EventBuffer::new(); + let mut options = get_client_options().await.clone(); options.heartbeat_freq = Some(Duration::from_millis(1000)); - options.sdam_event_handler = Some(handler.clone()); + options.sdam_event_handler = Some(buffer.handler()); let hosts = options.hosts.clone(); @@ -111,7 +117,7 @@ async fn heartbeat_frequency_is_respected() { // discover a server client .database("admin") - .run_command(doc! { "ping": 1 }, None) + .run_command(doc! { "ping": 1 }) .await .unwrap(); @@ -119,12 +125,13 @@ async fn heartbeat_frequency_is_respected() { // 1s for 3s. let mut tasks = Vec::new(); for address in hosts { - let h = handler.clone(); + let h = buffer.clone(); tasks.push(runtime::spawn(async move { - let mut subscriber = h.subscribe(); + + let mut event_stream = h.stream(); // collect events for 2 seconds, should see between 2 and 3 heartbeats. - let events = subscriber.collect_events(Duration::from_secs(3), |e| { + let events = event_stream.collect(Duration::from_secs(3), |e| { matches!(e, Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(e)) if e.server_address == address) }).await; @@ -142,55 +149,50 @@ async fn heartbeat_frequency_is_respected() { } /// RTT prose test 1 from SDAM spec tests. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn rtt_is_updated() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let test_client = TestClient::new().await; - if !test_client.supports_streaming_monitoring_protocol() { + if !streaming_monitor_protocol_supported().await { log_uncaptured( "skipping rtt_is_updated due to not supporting streaming monitoring protocol", ); return; } - - if test_client.is_load_balanced() { + if topology_is_load_balanced().await { log_uncaptured("skipping rtt_is_updated due to load balanced topology"); return; } - - if test_client.supports_block_connection() { + if !block_connection_supported().await { log_uncaptured("skipping rtt_is_updated due to not supporting block_connection"); return; } let app_name = "streamingRttTest"; - let handler = Arc::new(EventHandler::new()); - let mut options = CLIENT_OPTIONS.get().await.clone(); + let buffer = EventBuffer::new(); + let mut options = get_client_options().await.clone(); options.heartbeat_freq = Some(Duration::from_millis(500)); options.app_name = Some(app_name.to_string()); - options.sdam_event_handler = Some(handler.clone()); + options.sdam_event_handler = Some(buffer.handler()); options.hosts.drain(1..); options.direct_connection = Some(true); let host = options.hosts[0].clone(); let client = Client::with_options(options).unwrap(); - let mut subscriber = handler.subscribe(); + + let mut event_stream = buffer.stream(); // run a find to wait for the primary to be discovered client .database("foo") .collection::("bar") - .find(None, None) + .find(doc! {}) .await .unwrap(); // wait for multiple heartbeats, assert their RTT is > 0 - let events = subscriber - .collect_events(Duration::from_secs(2), |e| { + let events = event_stream + .collect(Duration::from_secs(2), |e| { if let Event::Sdam(SdamEvent::ServerDescriptionChanged(e)) = e { assert!( e.new_description.average_round_trip_time().unwrap() > Duration::from_millis(0) @@ -202,15 +204,13 @@ async fn rtt_is_updated() { assert!(events.len() > 2); // configure a failpoint that blocks hello commands - let fp = FailPoint::fail_command( + let fail_point = FailPoint::fail_command( &["hello", LEGACY_HELLO_COMMAND_NAME], FailPointMode::Times(1000), - FailCommandOptions::builder() - .block_connection(Duration::from_millis(500)) - .app_name(app_name.to_string()) - .build(), - ); - let _gp_guard = fp.enable(&client, None).await.unwrap(); + ) + .block_connection(Duration::from_millis(500)) + .app_name(app_name); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); let mut watcher = client.topology().watch(); runtime::timeout(Duration::from_secs(10), async move { @@ -230,3 +230,67 @@ async fn rtt_is_updated() { .await .unwrap(); } + +/* TODO RUST-1895 enable this +#[tokio::test(flavor = "multi_thread")] +async fn heartbeat_started_before_socket() { + use std::sync::{Arc, Mutex}; + use tokio::{io::AsyncReadExt, net::TcpListener}; + + #[derive(Debug, PartialEq)] + enum Event { + ClientConnected, + ClientHelloReceived, + HeartbeatStarted, + HeartbeatFailed, + } + let events: Arc>> = Arc::new(Mutex::new(vec![])); + + // Mock server + { + let listener = TcpListener::bind("127.0.0.1:9999").await.unwrap(); + let events = Arc::clone(&events); + tokio::spawn(async move { + loop { + let (mut socket, _) = listener.accept().await.unwrap(); + events.lock().unwrap().push(Event::ClientConnected); + let mut buf = [0; 1024]; + let _ = socket.read(&mut buf).await.unwrap(); + events.lock().unwrap().push(Event::ClientHelloReceived); + } + }); + } + + // Client setup + let mut options = ClientOptions::parse("mongodb://127.0.0.1:9999/") + .await + .unwrap(); + options.server_selection_timeout = Some(Duration::from_millis(500)); + { + let events = Arc::clone(&events); + options.sdam_event_handler = + Some(crate::event::EventHandler::callback(move |ev| match ev { + SdamEvent::ServerHeartbeatStarted(_) => { + events.lock().unwrap().push(Event::HeartbeatStarted) + } + SdamEvent::ServerHeartbeatFailed(_) => { + events.lock().unwrap().push(Event::HeartbeatFailed) + } + _ => (), + })); + } + let client = Client::with_options(options).unwrap(); + + // Test event order + let _ = client.list_database_names().await; + assert_eq!( + &[ + Event::HeartbeatStarted, + Event::ClientConnected, + Event::ClientHelloReceived, + Event::HeartbeatFailed + ], + &events.lock().unwrap()[0..4], + ); +} +*/ diff --git a/src/test/spec/sessions.rs b/src/test/spec/sessions.rs index cd939d8c1..5e2c8d3ed 100644 --- a/src/test/spec/sessions.rs +++ b/src/test/spec/sessions.rs @@ -1,40 +1,38 @@ +#[path = "sessions/sessions_not_supported.rs"] +mod sessions_not_supported_skip_local; // requires mongocryptd + use std::{ + future::IntoFuture, sync::{Arc, Mutex}, time::Duration, }; use futures::TryStreamExt; use futures_util::{future::try_join_all, FutureExt}; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use crate::{ bson::{doc, Document}, - client::options::ClientOptions, error::{ErrorKind, Result}, - event::command::{CommandEvent, CommandEventHandler, CommandStartedEvent}, - options::SessionOptions, - runtime::process::Process, + event::{ + command::{CommandEvent, CommandStartedEvent}, + sdam::SdamEvent, + }, test::{ + get_client_options, log_uncaptured, + server_version_gte, spec::unified_runner::run_unified_tests, - util::Event, - EventClient, - TestClient, - CLIENT_OPTIONS, - LOCK, + topology_is_load_balanced, + topology_is_sharded, + Event, }, Client, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - - // TODO RUST-1414: unskip this file - let mut skipped_files = vec!["implicit-sessions-default-causal-consistency.json"]; - let client = TestClient::new().await; - if client.is_sharded() && client.server_version_gte(7, 0) { + let mut skipped_files = vec![]; + if topology_is_sharded().await && server_version_gte(7, 0).await { // TODO RUST-1666: unskip this file skipped_files.push("snapshot-sessions.json"); } @@ -45,32 +43,30 @@ async fn run_unified() { } // Sessions prose test 1 -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn snapshot_and_causal_consistency_are_mutually_exclusive() { - let options = SessionOptions::builder() + let client = Client::for_test().await; + assert!(client + .start_session() .snapshot(true) .causal_consistency(true) - .build(); - let client = TestClient::new().await; - assert!(client.start_session(options).await.is_err()); + .await + .is_err()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] #[function_name::named] async fn explicit_session_created_on_same_client() { - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; + let client0 = Client::for_test().await; + let client1 = Client::for_test().await; - let client0 = TestClient::new().await; - let client1 = TestClient::new().await; - - let mut session0 = client0.start_session(None).await.unwrap(); - let mut session1 = client1.start_session(None).await.unwrap(); + let mut session0 = client0.start_session().await.unwrap(); + let mut session1 = client1.start_session().await.unwrap(); let db = client0.database(function_name!()); let err = db - .list_collections_with_session(None, None, &mut session1) + .list_collections() + .session(&mut session1) .await .unwrap_err(); match *err.kind { @@ -82,7 +78,8 @@ async fn explicit_session_created_on_same_client() { .database(function_name!()) .collection(function_name!()); let err = coll - .insert_one_with_session(doc! {}, None, &mut session0) + .insert_one(doc! {}) + .session(&mut session0) .await .unwrap_err(); match *err.kind { @@ -92,32 +89,26 @@ async fn explicit_session_created_on_same_client() { } // Sessions prose test 14 -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn implicit_session_after_connection() { - struct EventHandler { - lsids: Mutex>, - } - - impl CommandEventHandler for EventHandler { - fn handle_command_started_event(&self, event: CommandStartedEvent) { - self.lsids - .lock() - .unwrap() - .push(event.command.get_document("lsid").unwrap().clone()); - } - } - - let _guard: RwLockReadGuard<_> = LOCK.run_concurrently().await; - let event_handler = Arc::new(EventHandler { - lsids: Mutex::new(vec![]), - }); + let lsids = Arc::new(Mutex::new(vec![])); + let event_handler = { + let lsids = Arc::clone(&lsids); + crate::event::EventHandler::callback(move |ev: CommandEvent| { + if let CommandEvent::Started(CommandStartedEvent { command, .. }) = ev { + lsids + .lock() + .unwrap() + .push(command.get_document("lsid").unwrap().clone()); + } + }) + }; let mut min_lsids = usize::MAX; let mut max_lsids = 0usize; for _ in 0..5 { let client = { - let mut options = CLIENT_OPTIONS.get().await.clone(); + let mut options = get_client_options().await.clone(); options.max_pool_size = Some(1); options.retry_writes = Some(true); options.hosts.drain(1..); @@ -133,31 +124,45 @@ async fn implicit_session_after_connection() { fn ignore_val(r: Result) -> Result<()> { r.map(|_| ()) } - ops.push(coll.insert_one(doc! {}, None).map(ignore_val).boxed()); - ops.push(coll.delete_one(doc! {}, None).map(ignore_val).boxed()); ops.push( - coll.update_one(doc! {}, doc! { "$set": { "a": 1 } }, None) + coll.insert_one(doc! {}) + .into_future() + .map(ignore_val) + .boxed(), + ); + ops.push( + coll.delete_one(doc! {}) + .into_future() + .map(ignore_val) + .boxed(), + ); + ops.push( + coll.update_one(doc! {}, doc! { "$set": { "a": 1 } }) + .into_future() .map(ignore_val) .boxed(), ); ops.push( - coll.find_one_and_delete(doc! {}, None) + coll.find_one_and_delete(doc! {}) + .into_future() .map(ignore_val) .boxed(), ); ops.push( - coll.find_one_and_update(doc! {}, doc! { "$set": { "a": 1 } }, None) + coll.find_one_and_update(doc! {}, doc! { "$set": { "a": 1 } }) + .into_future() .map(ignore_val) .boxed(), ); ops.push( - coll.find_one_and_replace(doc! {}, doc! { "a": 1 }, None) + coll.find_one_and_replace(doc! {}, doc! { "a": 1 }) + .into_future() .map(ignore_val) .boxed(), ); ops.push( async { - let cursor = coll.find(doc! {}, None).await.unwrap(); + let cursor = coll.find(doc! {}).await.unwrap(); let r: Result> = cursor.try_collect().await; r.map(|_| ()) } @@ -166,7 +171,7 @@ async fn implicit_session_after_connection() { let _ = try_join_all(ops).await.unwrap(); - let mut lsids = event_handler.lsids.lock().unwrap(); + let mut lsids = lsids.lock().unwrap(); let mut unique = vec![]; 'outer: for lsid in lsids.iter() { for u in &unique { @@ -198,112 +203,85 @@ async fn implicit_session_after_connection() { ); } -async fn spawn_mongocryptd(name: &str) -> Option<(EventClient, Process)> { - let util_client = TestClient::new().await; - if util_client.server_version_lt(4, 2) { - log_uncaptured(format!( - "Skipping {name}: cannot spawn mongocryptd due to server version < 4.2" - )); - return None; - } - - let pid_file_path = format!("--pidfilepath={name}.pid"); - let args = vec!["--port=47017", &pid_file_path]; - let Ok(process) = Process::spawn("mongocryptd", args) else { - if std::env::var("SESSION_TEST_REQUIRE_MONGOCRYPTD").is_ok() { - panic!("Failed to spawn mongocryptd"); - } - log_uncaptured(format!("Skipping {name}: failed to spawn mongocryptd")); - return None; - }; - - let options = ClientOptions::parse("mongodb://localhost:47017") - .await - .unwrap(); - let client = EventClient::with_options(options).await; - assert!(client.server_info.logical_session_timeout_minutes.is_none()); - - Some((client, process)) -} +// Prose tests 18 and 19 in sessions_not_supported_skip_local module -async fn clean_up_mongocryptd(mut process: Process, name: &str) { - let _ = std::fs::remove_file(format!("{name}.pid")); - let _ = process.kill(); - let _ = process.wait().await; -} - -// Sessions prose test 18 -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn sessions_not_supported_implicit_session_ignored() { - let _guard = LOCK.run_exclusively().await; - - let name = "sessions_not_supported_implicit_session_ignored"; - - let Some((client, process)) = spawn_mongocryptd(name).await else { +// Sessions prose test 20 +#[tokio::test] +async fn no_cluster_time_in_sdam() { + if topology_is_load_balanced().await { + log_uncaptured("Skipping no_cluster_time_in_sdam: load-balanced topology"); return; - }; - - let mut subscriber = client.handler.subscribe(); - let coll = client.database(name).collection(name); + } + let mut options = get_client_options().await.clone(); + options.direct_connection = Some(true); + options.hosts.drain(1..); + let heartbeat_freq = Duration::from_millis(10); + options.heartbeat_freq = Some(heartbeat_freq); + let c1 = Client::for_test() + .options(options) + .min_heartbeat_freq(heartbeat_freq) + .monitor_events() + .await; - let _ = coll.find(doc! {}, None).await; - let event = subscriber - .filter_map_event(Duration::from_millis(500), |event| match event { - Event::Command(CommandEvent::Started(command_started_event)) - if command_started_event.command_name == "find" => - { - Some(command_started_event) - } - _ => None, - }) + // Send a ping on c1 + let cluster_time = c1 + .database("admin") + .run_command(doc! { "ping": 1 }) .await - .expect("Did not observe a command started event for find operation"); - assert!(!event.command.contains_key("lsid")); - - let _ = coll.insert_one(doc! { "x": 1 }, None).await; - let event = subscriber - .filter_map_event(Duration::from_millis(500), |event| match event { - Event::Command(CommandEvent::Started(command_started_event)) - if command_started_event.command_name == "insert" => - { - Some(command_started_event) - } - _ => None, - }) + .unwrap() + .get("$clusterTime") + .cloned(); + + // Send a write on c2 + let c2 = Client::for_test().await; + c2.database("test") + .collection::("test") + .insert_one(doc! {"advance": "$clusterTime"}) .await - .expect("Did not observe a command started event for insert operation"); - assert!(!event.command.contains_key("lsid")); - - clean_up_mongocryptd(process, name).await; -} - -// Sessions prose test 19 -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn sessions_not_supported_explicit_session_error() { - let _guard = LOCK.run_exclusively().await; - - let name = "sessions_not_supported_explicit_session_error"; - - let Some((client, process)) = spawn_mongocryptd(name).await else { - return; - }; - - let mut session = client.start_session(None).await.unwrap(); - let coll = client.database(name).collection(name); + .unwrap(); - let error = coll - .find_one_with_session(doc! {}, None, &mut session) + // Wait for the next (heartbeat started, heartbeat succeeded) event pair on c1 + let mut events = c1.events.stream(); + const TIMEOUT: Duration = Duration::from_secs(1); + crate::runtime::timeout(TIMEOUT, async { + loop { + // Find a started event... + let _started = events + .next_match(TIMEOUT, |ev| { + matches!(ev, Event::Sdam(SdamEvent::ServerHeartbeatStarted(_))) + }) + .await + .unwrap(); + // ... and the next heartbeat event after that ... + let next_hb = events + .next_map(TIMEOUT, |ev| match ev { + Event::Sdam(hb @ SdamEvent::ServerHeartbeatStarted(_)) => Some(hb), + Event::Sdam(hb @ SdamEvent::ServerHeartbeatFailed(_)) => Some(hb), + Event::Sdam(hb @ SdamEvent::ServerHeartbeatSucceeded(_)) => Some(hb), + _ => None, + }) + .await + .unwrap(); + // ... and see if it was a succeeded event. + if matches!(next_hb, SdamEvent::ServerHeartbeatSucceeded(_)) { + break; + } + } + }) + .await + .unwrap(); + + // Send another ping + let mut events = c1.events.stream(); + c1.database("admin") + .run_command(doc! { "ping": 1 }) .await - .unwrap_err(); - assert!(matches!(*error.kind, ErrorKind::SessionsNotSupported)); - - let error = coll - .insert_one_with_session(doc! { "x": 1 }, None, &mut session) + .unwrap(); + let (start, _succeded) = events + .next_successful_command_execution(TIMEOUT, "ping") .await - .unwrap_err(); - assert!(matches!(*error.kind, ErrorKind::SessionsNotSupported)); + .unwrap(); - clean_up_mongocryptd(process, name).await; + // Assert that the cluster time hasn't changed + assert_eq!(cluster_time.as_ref(), start.command.get("$clusterTime")); } diff --git a/src/test/spec/sessions/sessions_not_supported.rs b/src/test/spec/sessions/sessions_not_supported.rs new file mode 100644 index 000000000..387503fb1 --- /dev/null +++ b/src/test/spec/sessions/sessions_not_supported.rs @@ -0,0 +1,111 @@ +use std::time::Duration; + +use crate::{ + bson::doc, + client::options::ClientOptions, + error::ErrorKind, + event::command::CommandEvent, + runtime::process::Process, + test::{log_uncaptured, server_version_lt, util::Event, EventClient}, + Client, +}; + +async fn spawn_mongocryptd(name: &str) -> Option<(EventClient, Process)> { + if server_version_lt(4, 2).await { + log_uncaptured(format!( + "Skipping {name}: cannot spawn mongocryptd due to server version < 4.2" + )); + return None; + } + + let pid_file_path = format!("--pidfilepath={name}.pid"); + let args = vec!["--port=47017", &pid_file_path]; + let process = Process::spawn("mongocryptd", args).expect("failed to spawn mongocryptd"); + + let options = ClientOptions::parse("mongodb://localhost:47017") + .await + .unwrap(); + let client = Client::for_test().options(options).monitor_events().await; + let hello_response = client.hello().await.unwrap(); + assert!(hello_response.logical_session_timeout_minutes.is_none()); + + Some((client, process)) +} + +async fn clean_up_mongocryptd(mut process: Process, name: &str) { + let _ = std::fs::remove_file(format!("{name}.pid")); + let _ = process.kill(); + let _ = process.wait().await; +} + +// Sessions prose test 18 +#[tokio::test] +async fn sessions_not_supported_implicit_session_ignored() { + let name = "sessions_not_supported_implicit_session_ignored"; + + let Some((client, process)) = spawn_mongocryptd(name).await else { + return; + }; + + let mut event_stream = client.events.stream(); + let coll = client.database(name).collection(name); + + let _ = coll.find(doc! {}).await; + let event = event_stream + .next_map(Duration::from_millis(500), |event| match event { + Event::Command(CommandEvent::Started(command_started_event)) + if command_started_event.command_name == "find" => + { + Some(command_started_event) + } + _ => None, + }) + .await + .expect("Did not observe a command started event for find operation"); + assert!(!event.command.contains_key("lsid")); + + let _ = coll.insert_one(doc! { "x": 1 }).await; + let event = event_stream + .next_map(Duration::from_millis(500), |event| match event { + Event::Command(CommandEvent::Started(command_started_event)) + if command_started_event.command_name == "insert" => + { + Some(command_started_event) + } + _ => None, + }) + .await + .expect("Did not observe a command started event for insert operation"); + assert!(!event.command.contains_key("lsid")); + + clean_up_mongocryptd(process, name).await; +} + +// Sessions prose test 19 +#[tokio::test] +async fn sessions_not_supported_explicit_session_error() { + let name = "sessions_not_supported_explicit_session_error"; + + let Some((client, process)) = spawn_mongocryptd(name).await else { + return; + }; + + let mut session = client.start_session().await.unwrap(); + let coll = client.database(name).collection(name); + + let error = coll + .find_one(doc! {}) + .session(&mut session) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::SessionsNotSupported)); + + let error = coll + .insert_one(doc! { "x": 1 }) + .session(&mut session) + .await + .unwrap_err(); + assert!(matches!(*error.kind, ErrorKind::SessionsNotSupported)); + + clean_up_mongocryptd(process, name).await; +} diff --git a/src/test/spec/trace.rs b/src/test/spec/trace.rs index 938863ecb..0d7b38c3a 100644 --- a/src/test/spec/trace.rs +++ b/src/test/spec/trace.rs @@ -3,13 +3,12 @@ use std::{collections::HashMap, iter, sync::Arc, time::Duration}; use crate::{ bson::{doc, Document}, client::options::ServerAddress, - coll::options::FindOptions, error::{ - BulkWriteError, - BulkWriteFailure, CommandError, Error, ErrorKind, + IndexedWriteError, + InsertManyError, WriteConcernError, WriteError, WriteFailure, @@ -22,19 +21,20 @@ use crate::{ SelectionCriteria, }, test::{ + get_client_options, log_uncaptured, spec::unified_runner::run_unified_tests, - TestClient, - CLIENT_OPTIONS, + topology_is_standalone, DEFAULT_GLOBAL_TRACING_HANDLER, - LOCK, SERVER_API, }, trace::{ - command::{truncate_on_char_boundary, DEFAULT_MAX_DOCUMENT_LENGTH_BYTES}, + truncate_on_char_boundary, TracingRepresentation, COMMAND_TRACING_EVENT_TARGET, + DEFAULT_MAX_DOCUMENT_LENGTH_BYTES, }, + Client, TopologyType, }; @@ -50,52 +50,50 @@ fn tracing_truncation() { assert_eq!(s, String::from("...")); // we should "round up" to the end of the first emoji - s = two_emoji.clone(); + s.clone_from(&two_emoji); truncate_on_char_boundary(&mut s, 1); assert_eq!(s, String::from("🤔...")); // 4 is a boundary, so we should truncate there - s = two_emoji.clone(); + s.clone_from(&two_emoji); truncate_on_char_boundary(&mut s, 4); assert_eq!(s, String::from("🤔...")); // we should round up to the full string - s = two_emoji.clone(); + s.clone_from(&two_emoji); truncate_on_char_boundary(&mut s, 5); assert_eq!(s, two_emoji); // end of string is a boundary, so we should truncate there - s = two_emoji.clone(); + s.clone_from(&two_emoji); truncate_on_char_boundary(&mut s, 8); assert_eq!(s, two_emoji); // we should get the full string back if the new length is longer than the original - s = two_emoji.clone(); + s.clone_from(&two_emoji); truncate_on_char_boundary(&mut s, 10); assert_eq!(s, two_emoji); } /// Prose test 1: Default truncation limit -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn command_logging_truncation_default_limit() { - let _guard = LOCK.run_exclusively().await; - let client = TestClient::new().await; + let client = Client::for_test().await; let coll = client.init_db_and_coll("tracing_test", "truncation").await; let _levels_guard = DEFAULT_GLOBAL_TRACING_HANDLER.set_levels(HashMap::from([( COMMAND_TRACING_EVENT_TARGET.to_string(), tracing::Level::DEBUG, )])); - let mut tracing_subscriber = DEFAULT_GLOBAL_TRACING_HANDLER.subscribe(); + let mut tracing_stream = DEFAULT_GLOBAL_TRACING_HANDLER.event_stream(); let docs = iter::repeat(doc! { "x": "y" }).take(100); - coll.insert_many(docs, None) + coll.insert_many(docs) .await .expect("insert many should succeed"); - let events = tracing_subscriber - .collect_events(Duration::from_millis(500), |_| true) + let events = tracing_stream + .collect(Duration::from_millis(500), |_| true) .await; assert_eq!(events.len(), 2); @@ -107,9 +105,9 @@ async fn command_logging_truncation_default_limit() { let reply = succeeded.get_value_as_string("reply"); assert!(reply.len() <= DEFAULT_MAX_DOCUMENT_LENGTH_BYTES + 3); // +3 for trailing "..." - coll.find(None, None).await.expect("find should succeed"); - let succeeded = tracing_subscriber - .wait_for_event(Duration::from_millis(500), |e| { + coll.find(doc! {}).await.expect("find should succeed"); + let succeeded = tracing_stream + .next_match(Duration::from_millis(500), |e| { e.get_value_as_string("message") == "Command succeeded" }) .await @@ -119,29 +117,26 @@ async fn command_logging_truncation_default_limit() { } /// Prose test 2: explicitly configured truncation limit -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn command_logging_truncation_explicit_limit() { - let _guard = LOCK.run_exclusively().await; - - let mut client_opts = CLIENT_OPTIONS.get().await.clone(); + let mut client_opts = get_client_options().await.clone(); client_opts.tracing_max_document_length_bytes = Some(5); - let client = TestClient::with_options(Some(client_opts)).await; + let client = Client::for_test().options(client_opts).await; let _levels_guard = DEFAULT_GLOBAL_TRACING_HANDLER.set_levels(HashMap::from([( COMMAND_TRACING_EVENT_TARGET.to_string(), tracing::Level::DEBUG, )])); - let mut tracing_subscriber = DEFAULT_GLOBAL_TRACING_HANDLER.subscribe(); + let mut tracing_stream = DEFAULT_GLOBAL_TRACING_HANDLER.event_stream(); client .database("tracing_test") - .run_command(doc! { "hello" : "true" }, None) + .run_command(doc! { "hello" : "true" }) .await .expect("hello command should succeed"); - let events = tracing_subscriber - .collect_events(Duration::from_millis(500), |_| true) + let events = tracing_stream + .collect(Duration::from_millis(500), |_| true) .await; assert_eq!(events.len(), 2); @@ -158,17 +153,11 @@ async fn command_logging_truncation_explicit_limit() { } /// Prose test 3: mid-codepoint truncation -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn command_logging_truncation_mid_codepoint() { - let _guard = LOCK.run_exclusively().await; - - let mut client_opts = CLIENT_OPTIONS.get().await.clone(); - client_opts.tracing_max_document_length_bytes = Some(215); - let client = TestClient::with_options(Some(client_opts)).await; // On non-standalone topologies the command includes a clusterTime and so gets truncated // differently. - if !client.is_standalone() { + if !topology_is_standalone().await { log_uncaptured("Skipping test due to incompatible topology type"); return; } @@ -178,21 +167,25 @@ async fn command_logging_truncation_mid_codepoint() { return; } + let mut client_opts = get_client_options().await.clone(); + client_opts.tracing_max_document_length_bytes = Some(215); + let client = Client::for_test().options(client_opts).await; + let coll = client.init_db_and_coll("tracing_test", "truncation").await; let _levels_guard = DEFAULT_GLOBAL_TRACING_HANDLER.set_levels(HashMap::from([( COMMAND_TRACING_EVENT_TARGET.to_string(), tracing::Level::DEBUG, )])); - let mut tracing_subscriber = DEFAULT_GLOBAL_TRACING_HANDLER.subscribe(); + let mut tracing_stream = DEFAULT_GLOBAL_TRACING_HANDLER.event_stream(); let docs = iter::repeat(doc! { "🤔": "🤔🤔🤔🤔🤔🤔" }).take(10); - coll.insert_many(docs, None) + coll.insert_many(docs) .await .expect("insert many should succeed"); - let started = tracing_subscriber - .wait_for_event(Duration::from_millis(500), |e| { + let started = tracing_stream + .next_match(Duration::from_millis(500), |e| { e.get_value_as_string("message") == "Command started" }) .await @@ -204,14 +197,12 @@ async fn command_logging_truncation_mid_codepoint() { // trailing "..." assert_eq!(command.len(), 221); - let find_options = FindOptions::builder() + coll.find(doc! {}) .projection(doc! { "_id": 0, "🤔": 1 }) - .build(); - coll.find(None, find_options) .await .expect("find should succeed"); - let succeeded = tracing_subscriber - .wait_for_event(Duration::from_millis(500), |e| { + let succeeded = tracing_stream + .next_match(Duration::from_millis(500), |e| { e.get_value_as_string("message") == "Command succeeded" && e.get_value_as_string("commandName") == "find" }) @@ -270,13 +261,13 @@ fn error_redaction() { assert_on_properties(code, code_name.unwrap(), message, details); } }, - ErrorKind::BulkWrite(BulkWriteFailure { + ErrorKind::InsertMany(InsertManyError { write_errors, write_concern_error, .. }) => { if let Some(write_errors) = write_errors { - for BulkWriteError { + for IndexedWriteError { code, code_name, message, @@ -345,8 +336,8 @@ fn error_redaction() { assert_is_redacted(write_error); let mut bulk_write_error = Error::new( - ErrorKind::BulkWrite(BulkWriteFailure { - write_errors: Some(vec![BulkWriteError { + ErrorKind::InsertMany(InsertManyError { + write_errors: Some(vec![IndexedWriteError { index: 0, code: 123, code_name: Some("CodeName".to_string()), @@ -371,7 +362,7 @@ fn selection_criteria_tracing_representation() { // non-primary read preferences with empty options - options should be omitted from // representation. - let empty_opts = ReadPreferenceOptions::builder().build(); + let empty_opts = Some(ReadPreferenceOptions::builder().build()); assert_eq!( SelectionCriteria::ReadPreference(ReadPreference::PrimaryPreferred { @@ -404,9 +395,11 @@ fn selection_criteria_tracing_representation() { let mut tag_set = HashMap::new(); tag_set.insert("a".to_string(), "b".to_string()); - let opts_with_tag_sets = ReadPreferenceOptions::builder() - .tag_sets(vec![tag_set.clone()]) - .build(); + let opts_with_tag_sets = Some( + ReadPreferenceOptions::builder() + .tag_sets(vec![tag_set.clone()]) + .build(), + ); assert_eq!( SelectionCriteria::ReadPreference(ReadPreference::PrimaryPreferred { @@ -416,9 +409,11 @@ fn selection_criteria_tracing_representation() { "ReadPreference { Mode: PrimaryPreferred, Tag Sets: [{\"a\": \"b\"}] }" ); - let opts_with_max_staleness = ReadPreferenceOptions::builder() - .max_staleness(Duration::from_millis(200)) - .build(); + let opts_with_max_staleness = Some( + ReadPreferenceOptions::builder() + .max_staleness(Duration::from_millis(200)) + .build(), + ); assert_eq!( SelectionCriteria::ReadPreference(ReadPreference::PrimaryPreferred { options: opts_with_max_staleness @@ -427,9 +422,12 @@ fn selection_criteria_tracing_representation() { "ReadPreference { Mode: PrimaryPreferred, Max Staleness: 200ms }" ); - let opts_with_hedge = ReadPreferenceOptions::builder() - .hedge(HedgedReadOptions::with_enabled(true)) - .build(); + #[allow(deprecated)] + let opts_with_hedge = Some( + ReadPreferenceOptions::builder() + .hedge(HedgedReadOptions::builder().enabled(true).build()) + .build(), + ); assert_eq!( SelectionCriteria::ReadPreference(ReadPreference::PrimaryPreferred { options: opts_with_hedge @@ -438,10 +436,12 @@ fn selection_criteria_tracing_representation() { "ReadPreference { Mode: PrimaryPreferred, Hedge: true }" ); - let opts_with_multiple_options = ReadPreferenceOptions::builder() - .max_staleness(Duration::from_millis(200)) - .tag_sets(vec![tag_set]) - .build(); + let opts_with_multiple_options = Some( + ReadPreferenceOptions::builder() + .max_staleness(Duration::from_millis(200)) + .tag_sets(vec![tag_set]) + .build(), + ); assert_eq!( SelectionCriteria::ReadPreference(ReadPreference::PrimaryPreferred { options: opts_with_multiple_options @@ -462,10 +462,10 @@ fn topology_description_tracing_representation() { let mut servers = HashMap::new(); servers.insert( ServerAddress::default(), - ServerDescription::new(ServerAddress::default()), + ServerDescription::new(&ServerAddress::default()), ); - let oid = bson::oid::ObjectId::new(); + let oid = crate::bson::oid::ObjectId::new(); let description = TopologyDescription { single_seed: false, set_name: Some("myReplicaSet".to_string()), @@ -479,6 +479,7 @@ fn topology_description_tracing_representation() { local_threshold: None, heartbeat_freq: None, servers, + srv_max_hosts: None, }; assert_eq!( @@ -492,11 +493,11 @@ fn topology_description_tracing_representation() { ) } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn command_logging_unified() { - let _guard = LOCK.run_exclusively().await; run_unified_tests(&["command-logging-and-monitoring", "logging"]) + // TODO RUST-1599: Unskip this file + .skip_files(&["command.json"]) // Rust does not (and does not plan to) support unacknowledged writes; see RUST-9. .skip_tests(&[ "An unacknowledged write generates a succeeded log message with ok: 1 reply", @@ -504,10 +505,8 @@ async fn command_logging_unified() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn connection_logging_unified() { - let _guard = LOCK.run_exclusively().await; run_unified_tests(&["connection-monitoring-and-pooling", "logging"]) .skip_tests(&[ // TODO: RUST-1096 Unskip when configurable maxConnecting is added. @@ -522,16 +521,10 @@ async fn connection_logging_unified() { .await; } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn server_selection_logging_unified() { - let _guard = LOCK.run_exclusively().await; run_unified_tests(&["server-selection", "logging"]) - .skip_tests(&[ - // TODO: RUST-583 Unskip these if/when we add operation IDs as part of bulkWrite - // support. - "Successful bulkWrite operation: log messages have operationIds", - "Failed bulkWrite operation: log messages have operationIds", - ]) + // Operation IDs are not currently supported. + .skip_files(&["operation-id.json"]) .await; } diff --git a/src/test/spec/transactions.rs b/src/test/spec/transactions.rs index 594fd2cb8..29349d97d 100644 --- a/src/test/spec/transactions.rs +++ b/src/test/spec/transactions.rs @@ -1,60 +1,48 @@ use std::time::Duration; -use futures_util::FutureExt; use serde::{Deserialize, Serialize}; -use tokio::sync::{RwLockReadGuard, RwLockWriteGuard}; use crate::{ bson::{doc, Document}, error::{Error, Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}, + options::{CollectionOptions, WriteConcern}, test::{ + get_client_options, log_uncaptured, - spec::{unified_runner::run_unified_tests, v2_runner::run_v2_tests}, - FailCommandOptions, - FailPoint, - FailPointMode, - TestClient, - CLIENT_OPTIONS, - LOCK, + server_version_lt, + spec::unified_runner::run_unified_tests, + topology_is_sharded, + transactions_supported, + util::fail_point::{FailPoint, FailPointMode}, }, Client, Collection, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_legacy() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - run_v2_tests(&["transactions", "legacy"]) - // TODO RUST-582: unskip this file - .skip_files(&["error-labels-blockConnection.json"]) - .await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_legacy_convenient_api() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; - run_v2_tests(&["transactions-convenient-api"]).await; -} - // TODO RUST-902: Reduce transactionLifetimeLimitSeconds. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn run_unified() { - let _guard: RwLockWriteGuard<()> = LOCK.run_exclusively().await; +#[tokio::test(flavor = "multi_thread")] +async fn run_unified_base_api() { run_unified_tests(&["transactions", "unified"]) // TODO RUST-1656: unskip these files .skip_files(&["retryable-abort-handshake.json", "retryable-commit-handshake.json"]) + // The driver doesn't support socketTimeoutMS + .skip_tests(&["add RetryableWriteError and UnknownTransactionCommitResult labels to connection errors"]) .await; } +#[tokio::test(flavor = "multi_thread")] +async fn run_unified_convenient_api() { + run_unified_tests(&["transactions-convenient-api", "unified"]).await; +} + // This test checks that deserializing an operation correctly still retrieves the recovery token. -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] #[function_name::named] async fn deserialize_recovery_token() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; + if !topology_is_sharded().await || server_version_lt(4, 2).await { + log_uncaptured("skipping deserialize_recovery_token due to test topology"); + return; + } #[derive(Debug, Serialize)] struct A { @@ -66,141 +54,117 @@ async fn deserialize_recovery_token() { _str: String, } - let client = TestClient::new().await; - if !client.is_sharded() || client.server_version_lt(4, 2) { - log_uncaptured("skipping deserialize_recovery_token due to test topology"); - return; - } + let client = Client::for_test().await; - let mut session = client.start_session(None).await.unwrap(); + let mut session = client.start_session().await.unwrap(); // Insert a document with schema A. client .database(function_name!()) .collection::(function_name!()) - .drop(None) + .drop() .await .unwrap(); client .database(function_name!()) - .create_collection(function_name!(), None) + .create_collection(function_name!()) .await .unwrap(); let coll = client .database(function_name!()) .collection(function_name!()); - coll.insert_one(A { num: 4 }, None).await.unwrap(); + coll.insert_one(A { num: 4 }).await.unwrap(); // Attempt to execute Find on a document with schema B. let coll: Collection = client .database(function_name!()) .collection(function_name!()); - session.start_transaction(None).await.unwrap(); + session.start_transaction().await.unwrap(); assert!(session.transaction.recovery_token.is_none()); - let result = coll.find_one_with_session(None, None, &mut session).await; + let result = coll.find_one(doc! {}).session(&mut session).await; assert!(result.is_err()); // Assert that the deserialization failed. // Nevertheless, the recovery token should have been retrieved from the ok: 1 response. assert!(session.transaction.recovery_token.is_some()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn convenient_api_custom_error() { - let _guard: _ = LOCK.run_concurrently().await; - let client = Client::test_builder().event_client().build().await; - if !client.supports_transactions() { + if !transactions_supported().await { log_uncaptured("Skipping convenient_api_custom_error: no transaction support."); return; } - let mut session = client.start_session(None).await.unwrap(); + + let client = Client::for_test().monitor_events().await; + let mut session = client.start_session().await.unwrap(); let coll = client .database("test_convenient") .collection::("test_convenient"); struct MyErr; let result: Result<()> = session - .with_transaction( - coll, - |session, coll| { - async move { - coll.find_one_with_session(None, None, session).await?; - Err(Error::custom(MyErr)) - } - .boxed() - }, - None, - ) + .start_transaction() + .and_run2(async move |session| { + coll.find_one(doc! {}).session(session).await?; + Err(Error::custom(MyErr)) + }) .await; assert!(result.is_err()); assert!(result.unwrap_err().get_custom::().is_some()); - let events = client.get_all_command_started_events(); + + let events = client.events.get_all_command_started_events(); let commands: Vec<_> = events.iter().map(|ev| &ev.command_name).collect(); assert_eq!(&["find", "abortTransaction"], &commands[..]); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn convenient_api_returned_value() { - let _guard: _ = LOCK.run_concurrently().await; - let client = Client::test_builder().event_client().build().await; - if !client.supports_transactions() { + if !transactions_supported().await { log_uncaptured("Skipping convenient_api_returned_value: no transaction support."); return; } - let mut session = client.start_session(None).await.unwrap(); + + let client = Client::for_test().monitor_events().await; + let mut session = client.start_session().await.unwrap(); let coll = client .database("test_convenient") .collection::("test_convenient"); let value = session - .with_transaction( - coll, - |session, coll| { - async move { - coll.find_one_with_session(None, None, session).await?; - Ok(42) - } - .boxed() - }, - None, - ) + .start_transaction() + .and_run2(async move |session| { + coll.find_one(doc! {}).session(session).await?; + Ok(42) + }) .await .unwrap(); assert_eq!(42, value); } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn convenient_api_retry_timeout_callback() { - let _guard: _ = LOCK.run_concurrently().await; - let client = Client::test_builder().event_client().build().await; - if !client.supports_transactions() { + if !transactions_supported().await { log_uncaptured("Skipping convenient_api_retry_timeout_callback: no transaction support."); return; } - let mut session = client.start_session(None).await.unwrap(); + + let client = Client::for_test().monitor_events().await; + let mut session = client.start_session().await.unwrap(); session.convenient_transaction_timeout = Some(Duration::ZERO); let coll = client .database("test_convenient") .collection::("test_convenient"); let result: Result<()> = session - .with_transaction( - coll, - |session, coll| { - async move { - coll.find_one_with_session(None, None, session).await?; - let mut err = Error::custom(42); - err.add_label(TRANSIENT_TRANSACTION_ERROR); - Err(err) - } - .boxed() - }, - None, - ) + .start_transaction() + .and_run2(async move |session| { + coll.find_one(doc! {}).session(session).await?; + let mut err = Error::custom(42); + err.add_label(TRANSIENT_TRANSACTION_ERROR); + Err(err) + }) .await; let err = result.unwrap_err(); @@ -208,114 +172,109 @@ async fn convenient_api_retry_timeout_callback() { assert!(err.contains_label(TRANSIENT_TRANSACTION_ERROR)); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn convenient_api_retry_timeout_commit_unknown() { - let _guard: _ = LOCK.run_exclusively().await; - let mut options = CLIENT_OPTIONS.get().await.clone(); - if Client::test_builder().build().await.is_sharded() { - options.direct_connection = Some(true); - options.hosts.drain(1..); - } - let client = Client::test_builder() - .options(options) - .event_client() - .build() - .await; - if !client.supports_transactions() { + if !transactions_supported().await { log_uncaptured( "Skipping convenient_api_retry_timeout_commit_unknown: no transaction support.", ); return; } - let mut session = client.start_session(None).await.unwrap(); + + let mut options = get_client_options().await.clone(); + if topology_is_sharded().await { + options.direct_connection = Some(true); + options.hosts.drain(1..); + } + + let client = Client::for_test().options(options).monitor_events().await; + let mut session = client.start_session().await.unwrap(); session.convenient_transaction_timeout = Some(Duration::ZERO); let coll = client .database("test_convenient") .collection::("test_convenient"); - let _fp = FailPoint::fail_command( - &["commitTransaction"], - FailPointMode::Times(1), - FailCommandOptions::builder() - .error_code(251) - .error_labels(vec![UNKNOWN_TRANSACTION_COMMIT_RESULT.to_string()]) - .build(), - ) - .enable(&client, None) - .await - .unwrap(); + let fail_point = FailPoint::fail_command(&["commitTransaction"], FailPointMode::Times(1)) + .error_code(251) + .error_labels(vec![UNKNOWN_TRANSACTION_COMMIT_RESULT]); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); let result = session - .with_transaction( - coll, - |session, coll| { - async move { - coll.find_one_with_session(None, None, session).await?; - Ok(()) - } - .boxed() - }, - None, - ) + .start_transaction() + .and_run2(async move |session| { + coll.find_one(doc! {}).session(session).await?; + Ok(()) + }) .await; let err = result.unwrap_err(); assert_eq!(Some(251), err.sdam_code()); } -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test(flavor = "multi_thread")] async fn convenient_api_retry_timeout_commit_transient() { - let _guard: _ = LOCK.run_exclusively().await; - let mut options = CLIENT_OPTIONS.get().await.clone(); - if Client::test_builder().build().await.is_sharded() { - options.direct_connection = Some(true); - options.hosts.drain(1..); - } - let client = Client::test_builder() - .options(options) - .event_client() - .build() - .await; - if !client.supports_transactions() { + if !transactions_supported().await { log_uncaptured( "Skipping convenient_api_retry_timeout_commit_transient: no transaction support.", ); return; } - let mut session = client.start_session(None).await.unwrap(); + + let mut options = get_client_options().await.clone(); + if topology_is_sharded().await { + options.direct_connection = Some(true); + options.hosts.drain(1..); + } + + let client = Client::for_test().options(options).monitor_events().await; + let mut session = client.start_session().await.unwrap(); session.convenient_transaction_timeout = Some(Duration::ZERO); let coll = client .database("test_convenient") .collection::("test_convenient"); - let _fp = FailPoint::fail_command( - &["commitTransaction"], - FailPointMode::Times(1), - FailCommandOptions::builder() - .error_code(251) - .error_labels(vec![TRANSIENT_TRANSACTION_ERROR.to_string()]) - .build(), - ) - .enable(&client, None) - .await - .unwrap(); + let fail_point = FailPoint::fail_command(&["commitTransaction"], FailPointMode::Times(1)) + .error_code(251) + .error_labels(vec![TRANSIENT_TRANSACTION_ERROR]); + let _guard = client.enable_fail_point(fail_point).await.unwrap(); let result = session - .with_transaction( - coll, - |session, coll| { - async move { - coll.find_one_with_session(None, None, session).await?; - Ok(()) - } - .boxed() - }, - None, - ) + .start_transaction() + .and_run2(async move |session| { + coll.find_one(doc! {}).session(session).await?; + Ok(()) + }) .await; let err = result.unwrap_err(); assert!(err.contains_label(TRANSIENT_TRANSACTION_ERROR)); } + +#[tokio::test] +async fn write_concern_not_inherited() { + if !transactions_supported().await { + log_uncaptured("Skipping write_concern_not_inherited: no transaction support."); + return; + } + + let client = Client::for_test().await; + let db = client.database("write_concern_not_inherited"); + let coll: Collection = db.collection_with_options( + "test", + CollectionOptions::builder() + .write_concern(WriteConcern::nodes(0)) + .build(), + ); + let _ = coll.drop().write_concern(WriteConcern::majority()).await; + db.create_collection(coll.name()).await.unwrap(); + + let mut session = client.start_session().await.unwrap(); + session.start_transaction().await.unwrap(); + coll.insert_one(doc! { "n": 1 }) + .session(&mut session) + .await + .unwrap(); + session.commit_transaction().await.unwrap(); + + assert!(coll.find_one(doc! { "n": 1 }).await.unwrap().is_some()); +} diff --git a/src/test/spec/unified_runner.rs b/src/test/spec/unified_runner.rs new file mode 100644 index 000000000..357232f51 --- /dev/null +++ b/src/test/spec/unified_runner.rs @@ -0,0 +1,223 @@ +pub(crate) mod entity; +pub(crate) mod matcher; +pub(crate) mod operation; +pub(crate) mod test_event; +pub(crate) mod test_file; +pub(crate) mod test_runner; + +use std::future::IntoFuture; + +use futures::future::{BoxFuture, FutureExt}; +use serde::Deserialize; + +use crate::test::{ + file_level_log, + log_uncaptured, + spec::{deserialize_spec_tests, deserialize_spec_tests_from_exact_path}, +}; + +pub(crate) use self::{ + entity::{ClientEntity, Entity, SessionEntity, TestCursor}, + matcher::{events_match, results_match}, + operation::Operation, + test_event::{ExpectedCmapEvent, ExpectedCommandEvent, ExpectedEvent, ObserveEvent}, + test_file::{ + merge_uri_options, + CollectionData, + ExpectError, + ExpectedEventType, + TestFile, + TestFileEntity, + Topology, + }, + test_runner::{EntityMap, TestRunner}, +}; + +pub(crate) fn run_unified_tests(spec: &'static [&'static str]) -> RunUnifiedTestsAction { + RunUnifiedTestsAction { + spec, + skipped_files: None, + skipped_tests: None, + file_transformation: None, + use_exact_path: false, + } +} + +type FileTransformation = Box; +pub(crate) struct RunUnifiedTestsAction { + spec: &'static [&'static str], + skipped_files: Option>, + skipped_tests: Option>, + file_transformation: Option, + use_exact_path: bool, +} + +impl RunUnifiedTestsAction { + /// The files to skip deserializing. The provided filenames should only contain the filename and + /// extension, e.g. "unacknowledged-writes.json". Filenames are matched case-sensitively. + pub(crate) fn skip_files(self, skipped_files: &[&'static str]) -> Self { + Self { + skipped_files: Some(skipped_files.to_vec()), + ..self + } + } + + /// The descriptions of the tests to skip. Test descriptions are matched case-sensitively. + pub(crate) fn skip_tests(self, skipped_tests: &[&'static str]) -> Self { + Self { + skipped_tests: Some(skipped_tests.to_vec()), + ..self + } + } + + /// A transformation to apply to each test file prior to running the tests. + pub(crate) fn transform_files( + self, + file_transformation: impl Fn(&mut TestFile) + Send + Sync + 'static, + ) -> Self { + Self { + file_transformation: Some(Box::new(file_transformation)), + ..self + } + } + + /// Use the exact path provided to run_unified_tests when deserializing the spec tests. This is + /// useful when running the tests in an environment in which the test files have been uploaded + /// separately and are not being read from the driver directory. + pub(crate) fn use_exact_path(self) -> Self { + Self { + use_exact_path: true, + ..self + } + } +} + +impl IntoFuture for RunUnifiedTestsAction { + type Output = (); + type IntoFuture = BoxFuture<'static, Self::Output>; + + fn into_future(self) -> Self::IntoFuture { + async move { + let files = if self.use_exact_path { + deserialize_spec_tests_from_exact_path::( + self.spec, + self.skipped_files.as_deref(), + ) + } else { + deserialize_spec_tests::(self.spec, self.skipped_files.as_deref()) + }; + for (mut test_file, path) in files { + if let Some(ref file_transformation) = self.file_transformation { + file_transformation(&mut test_file); + } + + let test_runner = TestRunner::new().await; + test_runner + .run_test(test_file, path, self.skipped_tests.as_ref()) + .await; + } + } + .boxed() + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn valid_pass() { + let mut skipped_files = vec![ + // TODO RUST-1570: unskip this file + "collectionData-createOptions.json", + // TODO RUST-1405: unskip this file + "expectedError-errorResponse.json", + // TODO RUST-582: unskip these files + "entity-cursor-iterateOnce.json", + "matches-lte-operator.json", + // TODO: unskip this file when the convenient transactions API tests are converted to the + // unified format + "poc-transactions-convenient-api.json", + // TODO RUST-2077: unskip this file + "poc-queryable-encryption.json", + ]; + // These tests need the in-use-encryption feature flag to be deserialized and run. + if cfg!(not(feature = "in-use-encryption")) { + skipped_files.extend(&[ + "kmsProviders-placeholder_kms_credentials.json", + "kmsProviders-unconfigured_kms.json", + "kmsProviders-explicit_kms_credentials.json", + "kmsProviders-mixed_kms_credential_fields.json", + ]); + } + + run_unified_tests(&["unified-test-format", "valid-pass"]) + .skip_files(&skipped_files) + // This test relies on old OP_QUERY behavior that many drivers still use for < 4.4, but + // we do not use, due to never implementing OP_QUERY. + .skip_tests(&["A successful find event with a getmore and the server kills the cursor (<= 4.4)"]) + .await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn valid_fail() { + expect_failures(&["unified-test-format", "valid-fail"], None).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn invalid() { + expect_failures( + &["unified-test-format", "invalid"], + // We don't do the schema validation required by these tests to avoid lengthy + // deserialization implementations. + Some(&[ + "runOnRequirement-minProperties.json", + "tests-minItems.json", + "expectedError-isError-const.json", + "expectedError-minProperties.json", + "expectedLogMessage-component-enum.json", + "entity-client-observeLogMessages-minProperties.json", + "test-expectLogMessages-minItems.json", + ]), + ) + .await; +} + +#[derive(Debug)] +enum TestFileResult { + Ok(Box), + Err, +} + +impl<'de> Deserialize<'de> for TestFileResult { + // This implementation should always return Ok to avoid panicking during deserialization in + // deserialize_spec_tests. + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + // Test files should always be valid JSON. + let value = serde_json::Value::deserialize(deserializer).unwrap(); + match serde_json::from_value(value) { + Ok(test_file) => Ok(TestFileResult::Ok(test_file)), + Err(_) => Ok(TestFileResult::Err), + } + } +} + +// The test runner enforces the unified test format schema during both deserialization and +// execution. Note that these tests may not fail as expected if the TEST_DESCRIPTION environment +// variable for skipping tests is set. +async fn expect_failures(spec: &[&str], skipped_files: Option<&'static [&'static str]>) { + for (test_file_result, path) in deserialize_spec_tests::(spec, skipped_files) { + log_uncaptured(format!("Expecting failure for {:?}", path)); + // If the test deserialized properly, then expect an error to occur during execution. + if let TestFileResult::Ok(test_file) = test_file_result { + std::panic::AssertUnwindSafe(async { + TestRunner::new() + .await + .run_test(*test_file, path.clone(), None) + .await; + }) + .catch_unwind() + .await + .expect_err(&format!("Tests from {:?} should have failed", &path)); + } + } +} diff --git a/src/test/spec/unified_runner/entity.rs b/src/test/spec/unified_runner/entity.rs index 84086bebb..4dca9ae10 100644 --- a/src/test/spec/unified_runner/entity.rs +++ b/src/test/spec/unified_runner/entity.rs @@ -1,12 +1,10 @@ use std::{ - fs::File, - io::BufWriter, ops::{Deref, DerefMut}, sync::Arc, time::Duration, }; -use tokio::sync::{mpsc, oneshot, Mutex}; +use tokio::sync::{mpsc, oneshot, Mutex, RwLock}; use crate::{ bson::{Bson, Document}, @@ -19,8 +17,8 @@ use crate::{ sdam::TopologyDescription, test::{ spec::unified_runner::{ExpectedEventType, ObserveEvent}, + util::event_buffer::EventBuffer, Event, - EventHandler, }, Client, ClientSession, @@ -30,7 +28,7 @@ use crate::{ SessionCursor, }; -use super::{observer::EventObserver, test_file::ThreadMessage, Operation}; +use super::{events_match, test_file::ThreadMessage, EntityMap, ExpectedEvent, Operation}; #[derive(Debug)] #[allow(clippy::large_enum_variant)] @@ -39,18 +37,24 @@ pub(crate) enum Entity { Database(Database), Collection(Collection), Session(SessionEntity), + SessionPtr(SessionPtr), Bucket(GridFsBucket), Cursor(TestCursor), Bson(Bson), - EventList(EventList), Thread(ThreadEntity), TopologyDescription(TopologyDescription), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] ClientEncryption(Arc), None, } -#[cfg(feature = "in-use-encryption-unstable")] +#[derive(Debug)] +pub(crate) struct SessionPtr(pub(crate) *mut ClientSession); + +unsafe impl Send for SessionPtr {} +unsafe impl Sync for SessionPtr {} + +#[cfg(feature = "in-use-encryption")] impl std::fmt::Debug for crate::client_encryption::ClientEncryption { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ClientEncryption").finish() @@ -61,9 +65,8 @@ impl std::fmt::Debug for crate::client_encryption::ClientEncryption { pub(crate) struct ClientEntity { /// This is None if a `close` operation has been executed for this entity. pub(crate) client: Option, - pub(crate) topology_id: bson::oid::ObjectId, - handler: Arc, - pub(crate) observer: Arc>, + pub(crate) topology_id: crate::bson::oid::ObjectId, + events: EventBuffer, observe_events: Option>, ignore_command_names: Option>, observe_sensitive_commands: bool, @@ -91,18 +94,6 @@ pub(crate) enum TestCursor { Closed, } -#[derive(Debug)] -pub struct EventList { - pub client_id: String, - pub event_names: Vec, -} - -impl From for Entity { - fn from(event_list: EventList) -> Self { - Self::EventList(event_list) - } -} - impl TestCursor { pub(crate) async fn make_kill_watcher(&mut self) -> oneshot::Receiver<()> { match self { @@ -128,22 +119,19 @@ impl TestCursor { impl ClientEntity { pub(crate) fn new( - client_options: ClientOptions, - handler: Arc, + mut client_options: ClientOptions, observe_events: Option>, ignore_command_names: Option>, observe_sensitive_commands: bool, ) -> Self { - // ensure the observer is already listening before the client is created to avoid any races - // around collecting initial events when the topology opens. - let observer = EventObserver::new(handler.broadcaster().subscribe()); + let events = EventBuffer::new(); + events.register(&mut client_options); let client = Client::with_options(client_options).unwrap(); let topology_id = client.topology().id; Self { client: Some(client), topology_id, - handler, - observer: Arc::new(Mutex::new(observer)), + events, observe_events, ignore_command_names, observe_sensitive_commands, @@ -154,19 +142,64 @@ impl ClientEntity { /// Ignores any event with a name in the ignore list. Also ignores all configureFailPoint /// events. pub(crate) fn get_filtered_events(&self, expected_type: ExpectedEventType) -> Vec { - self.handler.get_filtered_events(expected_type, |event| { - if let Event::Command(cev) = event { - if !self.allow_command_event(cev) { + self.events + .all() + .into_iter() + .filter(|event| { + if !expected_type.matches(event) { return false; } - } - if let Some(observe_events) = self.observe_events.as_ref() { - if !observe_events.iter().any(|observe| observe.matches(event)) { - return false; + if let Event::Command(cev) = event { + if !self.allow_command_event(cev) { + return false; + } + } + if let Some(observe_events) = self.observe_events.as_ref() { + if !observe_events.iter().any(|observe| observe.matches(event)) { + return false; + } + } + true + }) + .collect() + } + + pub(crate) fn matching_events( + &self, + expected: &ExpectedEvent, + entities: &EntityMap, + ) -> Vec { + self.events + .all() + .into_iter() + .filter(|e| events_match(e, expected, Some(entities)).is_ok()) + .collect() + } + + pub(crate) async fn wait_for_matching_events( + &self, + expected: &ExpectedEvent, + count: usize, + entities: Arc>, + ) -> Result<()> { + const TIMEOUT: Duration = Duration::from_secs(10); + crate::runtime::timeout(TIMEOUT, async { + let mut stream = self.events.stream_all(); + let mut matched = 0; + loop { + let Some(ev) = stream.next(TIMEOUT).await else { + continue; + }; + let entities = &*entities.read().await; + if events_match(&ev, expected, Some(entities)).is_ok() { + matched += 1; + if matched >= count { + return Ok(()); + } } } - true }) + .await? } /// Returns `true` if a given `CommandEvent` is allowed to be observed. @@ -201,17 +234,23 @@ impl ClientEntity { /// Gets all events of type commandStartedEvent, excluding configureFailPoint events. pub(crate) fn get_all_command_started_events(&self) -> Vec { - self.handler.get_all_command_started_events() - } - - /// Writes all events with the given name to the given BufWriter. - pub fn write_events_list_to_file(&self, names: &[&str], writer: &mut BufWriter) { - self.handler.write_events_list_to_file(names, writer); + self.events + .all() + .into_iter() + .filter_map(|ev| match ev { + Event::Command(CommandEvent::Started(ev)) + if ev.command_name != "configureFailPoint" => + { + Some(ev) + } + _ => None, + }) + .collect() } /// Gets the count of connections currently checked out. pub(crate) fn connections_checked_out(&self) -> u32 { - self.handler.connections_checked_out() + self.events.connections_checked_out() } /// Synchronize all connection pool worker threads. @@ -221,7 +260,7 @@ impl ClientEntity { } } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) fn client(&self) -> Option<&Client> { self.client.as_ref() } @@ -293,7 +332,7 @@ impl Deref for ClientEntity { match &self.client { Some(c) => c, None => panic!( - "Attempted to deference a client entity which was closed via a `close` test \ + "Attempted to dereference a client entity which was closed via a `close` test \ operation" ), } @@ -370,9 +409,10 @@ impl Entity { } } - pub fn as_mut_session_entity(&mut self) -> &mut SessionEntity { + pub fn as_mut_session(&mut self) -> &mut ClientSession { match self { - Self::Session(client_session) => client_session, + Self::Session(client_session) => &mut *client_session, + Self::SessionPtr(ptr) => unsafe { &mut *ptr.0 }, _ => panic!("Expected mutable client session entity, got {:?}", &self), } } @@ -412,15 +452,8 @@ impl Entity { } } - pub fn as_event_list(&self) -> &EventList { - match self { - Self::EventList(event_list) => event_list, - _ => panic!("Expected event list, got {:?}", &self), - } - } - /// If this entity is descended from a client entity, returns the topology ID for that client. - pub(crate) async fn client_topology_id(&self) -> Option { + pub(crate) async fn client_topology_id(&self) -> Option { match self { Entity::Client(client_entity) => Some(client_entity.topology_id), Entity::Database(database) => Some(database.client().topology().id), @@ -437,7 +470,7 @@ impl Entity { } } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub fn as_client_encryption(&self) -> &Arc { match self { Self::ClientEncryption(ce) => ce, diff --git a/src/test/spec/unified_runner/matcher.rs b/src/test/spec/unified_runner/matcher.rs index 747950507..c5f670780 100644 --- a/src/test/spec/unified_runner/matcher.rs +++ b/src/test/spec/unified_runner/matcher.rs @@ -1,10 +1,14 @@ -use bson::Document; +use std::fmt::Debug; use crate::{ - bson::{doc, spec::ElementType, Bson}, - bson_util::get_int, - event::{cmap::CmapEvent, command::CommandEvent, sdam::ServerDescription}, - test::{Event, SdamEvent}, + bson::{doc, spec::ElementType, Bson, Document}, + bson_util::{get_double, get_int}, + event::{ + cmap::CmapEvent, + command::CommandEvent, + sdam::{SdamEvent, ServerDescription}, + }, + test::Event, }; use super::{ @@ -20,6 +24,14 @@ use super::test_file::ExpectedMessage; #[cfg(feature = "tracing-unstable")] use crate::test::util::{TracingEvent, TracingEventValue}; +#[cfg(feature = "tracing-unstable")] +fn mismatch_message(kind: &str, actual: impl Debug, expected: impl Debug) -> String { + format!( + "{} do not match. Actual:\n{:?}\nExpected:\n{:?}", + kind, actual, expected + ) +} + use std::convert::TryInto; pub(crate) fn results_match( @@ -51,20 +63,26 @@ pub(crate) fn tracing_events_match( actual: &TracingEvent, expected: &ExpectedMessage, ) -> Result<(), String> { - if actual.target != expected.target { - return Err(format!( - "Expected and actual tracing event components do not match: expected {}, got {}", - expected.target, actual.target - )); + if let Some(ref target) = expected.target { + if &actual.target != target { + return Err(mismatch_message( + "tracing event components", + &actual.target, + target, + )); + } } - if actual.level != expected.level { - return Err(format!( - "Expected and actual tracing event levels do not match: expected {}. got {}", - expected.level, actual.level - )); + if let Some(level) = expected.level { + if actual.level != level { + return Err(mismatch_message( + "tracing event levels", + actual.level, + level, + )); + } } - use lazy_static::lazy_static; + use once_cell::sync::Lazy; use regex::Regex; if let Some(failure_should_be_redacted) = expected.failure_is_redacted { @@ -72,17 +90,21 @@ pub(crate) fn tracing_events_match( Some(failure) => { match failure { TracingEventValue::String(failure_str) => { - // lazy_static saves us having to recompile this regex every time this + // `Lazy` saves us having to recompile this regex every time this // function is called. - lazy_static! { - static ref COMMAND_FAILED_REGEX: Regex = Regex::new( + + static COMMAND_FAILED_REGEX: Lazy = Lazy::new(|| { + Regex::new( r"^Kind: Command failed: Error code (?P\d+) \((?P.+)\): (?P.+)+, labels: (?P.+)$" - ).unwrap(); + ).unwrap() + }); - static ref IO_ERROR_REGEX: Regex = Regex::new( - r"^Kind: I/O error: (?P.+), labels: (?P.+)$" - ).unwrap(); - } + static IO_ERROR_REGEX: Lazy = Lazy::new(|| { + Regex::new( + r"^Kind: I/O error: (?P.+), labels: (?P.+)$", + ) + .unwrap() + }); // We redact all server-returned errors, however at this time the only types // of errors that show up in tracing redaction tests @@ -171,7 +193,7 @@ pub(crate) fn tracing_events_match( }; } - let serialized_fields = bson::to_document(&actual.fields) + let serialized_fields = crate::bson_compat::serialize_to_document(&actual.fields) .map_err(|e| format!("Failed to serialize tracing fields to document: {}", e))?; results_match( @@ -329,10 +351,50 @@ fn sdam_events_match(actual: &SdamEvent, expected: &ExpectedSdamEvent) -> Result } Ok(()) } + (SdamEvent::TopologyOpening(_), ExpectedSdamEvent::TopologyOpening {}) => Ok(()), + (SdamEvent::TopologyClosed(_), ExpectedSdamEvent::TopologyClosed {}) => Ok(()), ( - SdamEvent::TopologyDescriptionChanged(_), - ExpectedSdamEvent::TopologyDescriptionChanged {}, - ) => Ok(()), + SdamEvent::TopologyDescriptionChanged(actual), + ExpectedSdamEvent::TopologyDescriptionChanged { + previous_description, + new_description, + }, + ) => { + if let Some(expected_prev) = previous_description { + match_opt( + &actual.previous_description.topology_type(), + &expected_prev.topology_type, + )?; + } + if let Some(expected_new) = new_description { + match_opt( + &actual.new_description.topology_type(), + &expected_new.topology_type, + )?; + } + Ok(()) + } + ( + SdamEvent::ServerHeartbeatStarted(actual), + ExpectedSdamEvent::ServerHeartbeatStarted { awaited }, + ) => { + match_opt(&actual.awaited, awaited)?; + Ok(()) + } + ( + SdamEvent::ServerHeartbeatSucceeded(actual), + ExpectedSdamEvent::ServerHeartbeatSucceeded { awaited }, + ) => { + match_opt(&actual.awaited, awaited)?; + Ok(()) + } + ( + SdamEvent::ServerHeartbeatFailed(actual), + ExpectedSdamEvent::ServerHeartbeatFailed { awaited }, + ) => { + match_opt(&actual.awaited, awaited)?; + Ok(()) + } _ => expected_err(actual, expected), } } @@ -417,10 +479,7 @@ fn results_match_inner( } } -fn expected_err( - actual: &A, - expected: &B, -) -> Result<(), String> { +fn expected_err(actual: &A, expected: &B) -> Result<(), String> { Err(format!("expected {:?}, got {:?}", expected, actual)) } @@ -449,8 +508,17 @@ fn special_operator_matches( entities: Option<&EntityMap>, ) -> Result<(), String> { match key.as_ref() { - "$$exists" => match_eq(&value.as_bool().unwrap(), &actual.is_some()), - "$$type" => type_matches(value, actual.unwrap()), + "$$exists" => match_eq(&actual.is_some(), &value.as_bool().unwrap()), + "$$type" => { + if let Some(actual) = actual { + type_matches(value, actual) + } else { + Err(format!( + "Expected value to have type {:?} but got None", + value + )) + } + } "$$unsetOrMatches" => { if actual.is_some() { results_match_inner(actual, value, false, false, entities) @@ -506,6 +574,21 @@ fn special_operator_matches( results_match_inner(Some(&doc), value, false, false, entities) } "$$matchAsRoot" => results_match_inner(actual, value, false, true, entities), + "$$lte" => { + let Some(expected) = get_double(value) else { + return Err(format!("expected number for comparison, got {}", value)); + }; + let Some(actual) = actual.and_then(get_double) else { + return Err(format!("expected actual to be a number, got {:?}", actual)); + }; + if actual > expected { + return Err(format!( + "expected actual to be <= {}, got {}", + expected, actual + )); + } + Ok(()) + } other => panic!("unknown special operator: {}", other), } } @@ -525,6 +608,10 @@ fn type_matches(types: &Bson, actual: &Bson) -> Result<(), String> { } } Bson::String(str) => { + if str == "number" { + let number_types: Bson = vec!["int", "long", "double", "decimal"].into(); + return type_matches(&number_types, actual); + } let expected = match str.as_ref() { "double" => ElementType::Double, "string" => ElementType::String, diff --git a/src/test/spec/unified_runner/mod.rs b/src/test/spec/unified_runner/mod.rs deleted file mode 100644 index 533d08424..000000000 --- a/src/test/spec/unified_runner/mod.rs +++ /dev/null @@ -1,210 +0,0 @@ -pub(crate) mod entity; -pub(crate) mod matcher; -pub(crate) mod observer; -pub(crate) mod operation; -pub(crate) mod test_event; -pub(crate) mod test_file; -pub(crate) mod test_runner; - -use std::future::IntoFuture; - -use futures::future::{BoxFuture, FutureExt}; -use serde::Deserialize; -use tokio::sync::RwLockWriteGuard; - -use crate::test::{file_level_log, log_uncaptured, spec::deserialize_spec_tests, LOCK}; - -pub(crate) use self::{ - entity::{ClientEntity, Entity, SessionEntity, TestCursor}, - matcher::{events_match, results_match}, - operation::Operation, - test_event::{ExpectedCmapEvent, ExpectedCommandEvent, ExpectedEvent, ObserveEvent}, - test_file::{ - merge_uri_options, - CollectionData, - ExpectError, - ExpectedEventType, - TestFile, - TestFileEntity, - Topology, - }, - test_runner::{EntityMap, TestRunner}, -}; - -pub(crate) fn run_unified_tests(spec: &'static [&'static str]) -> RunUnifiedTestsAction { - RunUnifiedTestsAction { - spec, - skipped_files: None, - skipped_tests: None, - file_transformation: None, - } -} - -type FileTransformation = Box; -pub(crate) struct RunUnifiedTestsAction { - spec: &'static [&'static str], - skipped_files: Option>, - skipped_tests: Option>, - file_transformation: Option, -} - -impl RunUnifiedTestsAction { - /// The files to skip deserializing. The provided filenames should only contain the filename and - /// extension, e.g. "unacknowledged-writes.json". Filenames are matched case-sensitively. - pub(crate) fn skip_files(self, skipped_files: &[&'static str]) -> Self { - Self { - skipped_files: Some(skipped_files.to_vec()), - ..self - } - } - - /// The descriptions of the tests to skip. Test descriptions are matched case-sensitively. - pub(crate) fn skip_tests(self, skipped_tests: &[&'static str]) -> Self { - Self { - skipped_tests: Some(skipped_tests.to_vec()), - ..self - } - } - - /// A transformation to apply to each test file prior to running the tests. - pub(crate) fn transform_files( - self, - file_transformation: impl Fn(&mut TestFile) + Send + Sync + 'static, - ) -> Self { - Self { - file_transformation: Some(Box::new(file_transformation)), - ..self - } - } -} - -impl IntoFuture for RunUnifiedTestsAction { - type Output = (); - type IntoFuture = BoxFuture<'static, Self::Output>; - - fn into_future(self) -> Self::IntoFuture { - async move { - for (mut test_file, path) in - deserialize_spec_tests::(self.spec, self.skipped_files.as_deref()) - { - if let Some(ref file_transformation) = self.file_transformation { - file_transformation(&mut test_file); - } - - let test_runner = TestRunner::new().await; - test_runner - .run_test(test_file, path, self.skipped_tests.as_ref()) - .await; - } - } - .boxed() - } -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn valid_pass() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - - let mut skipped_files = vec![ - // TODO RUST-1570: unskip this file - "collectionData-createOptions.json", - // TODO RUST-1405: unskip this file - "expectedError-errorResponse.json", - // TODO RUST-582: unskip these files - "entity-cursor-iterateOnce.json", - "matches-lte-operator.json", - // TODO: unskip this file when the convenient transactions API tests are converted to the - // unified format - "poc-transactions-convenient-api.json", - ]; - // These tests need the in-use-encryption-unstable feature flag to be deserialized and run. - if cfg!(not(feature = "in-use-encryption-unstable")) { - skipped_files.extend(&[ - "kmsProviders-placeholder_kms_credentials.json", - "kmsProviders-unconfigured_kms.json", - "kmsProviders-explicit_kms_credentials.json", - "kmsProviders-mixed_kms_credential_fields.json", - ]); - } - - run_unified_tests(&["unified-test-format", "valid-pass"]) - .skip_files(&skipped_files) - // This test relies on old OP_QUERY behavior that many drivers still use for < 4.4, but - // we do not use, due to never implementing OP_QUERY. - .skip_tests(&["A successful find event with a getmore and the server kills the cursor (<= 4.4)"]) - .await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn valid_fail() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - expect_failures(&["unified-test-format", "valid-fail"], None).await; -} - -#[cfg_attr(feature = "tokio-runtime", tokio::test(flavor = "multi_thread"))] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] -async fn invalid() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; - expect_failures( - &["unified-test-format", "invalid"], - // We don't do the schema validation required by these tests to avoid lengthy - // deserialization implementations. - Some(&[ - "runOnRequirement-minProperties.json", - "storeEventsAsEntity-events-enum.json", - "tests-minItems.json", - "expectedError-isError-const.json", - "expectedError-minProperties.json", - "storeEventsAsEntity-events-minItems.json", - "expectedLogMessage-component-enum.json", - "entity-client-observeLogMessages-minProperties.json", - "test-expectLogMessages-minItems.json", - ]), - ) - .await; -} - -#[derive(Debug)] -enum TestFileResult { - Ok(TestFile), - Err, -} - -impl<'de> Deserialize<'de> for TestFileResult { - // This implementation should always return Ok to avoid panicking during deserialization in - // deserialize_spec_tests. - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - // Test files should always be valid JSON. - let value = serde_json::Value::deserialize(deserializer).unwrap(); - match serde_json::from_value(value) { - Ok(test_file) => Ok(TestFileResult::Ok(test_file)), - Err(_) => Ok(TestFileResult::Err), - } - } -} - -// The test runner enforces the unified test format schema during both deserialization and -// execution. Note that these tests may not fail as expected if the TEST_DESCRIPTION environment -// variable for skipping tests is set. -async fn expect_failures(spec: &[&str], skipped_files: Option<&'static [&'static str]>) { - for (test_file_result, path) in deserialize_spec_tests::(spec, skipped_files) { - log_uncaptured(format!("Expecting failure for {:?}", path)); - // If the test deserialized properly, then expect an error to occur during execution. - if let TestFileResult::Ok(test_file) = test_file_result { - std::panic::AssertUnwindSafe(async { - TestRunner::new() - .await - .run_test(test_file, path.clone(), None) - .await; - }) - .catch_unwind() - .await - .expect_err(&format!("Tests from {:?} should have failed", &path)); - } - } -} diff --git a/src/test/spec/unified_runner/observer.rs b/src/test/spec/unified_runner/observer.rs deleted file mode 100644 index 36e255b2c..000000000 --- a/src/test/spec/unified_runner/observer.rs +++ /dev/null @@ -1,105 +0,0 @@ -use tokio::sync::{ - broadcast::{ - self, - error::{RecvError, TryRecvError}, - }, - RwLock, -}; - -use std::{sync::Arc, time::Duration}; - -use crate::{ - error::{Error, Result}, - runtime, - test::Event, -}; - -use super::{events_match, EntityMap, ExpectedEvent}; - -// TODO: RUST-1424: consolidate this with `EventHandler` -/// Observer used to cache all the seen events for a given client in a unified test. -/// Used to implement assertEventCount and waitForEvent operations. -#[derive(Debug)] -pub(crate) struct EventObserver { - seen_events: Vec, - receiver: broadcast::Receiver, -} - -impl EventObserver { - pub fn new(receiver: broadcast::Receiver) -> Self { - Self { - seen_events: Vec::new(), - receiver, - } - } - - pub(crate) async fn recv(&mut self) -> Option { - match self.receiver.recv().await { - Ok(e) => { - self.seen_events.push(e.clone()); - Some(e) - } - Err(RecvError::Lagged(_)) => panic!("event receiver lagged"), - Err(RecvError::Closed) => None, - } - } - - fn try_recv(&mut self) -> Option { - match self.receiver.try_recv() { - Ok(e) => { - self.seen_events.push(e.clone()); - Some(e) - } - Err(TryRecvError::Lagged(_)) => panic!("event receiver lagged"), - Err(TryRecvError::Closed | TryRecvError::Empty) => None, - } - } - - pub(crate) async fn matching_events( - &mut self, - event: &ExpectedEvent, - entities: Arc>, - ) -> Vec { - // first retrieve all the events buffered in the channel - while self.try_recv().is_some() {} - let es = entities.read().await; - // Then collect all matching events. - self.seen_events - .iter() - .filter(|e| events_match(e, event, Some(&es)).is_ok()) - .cloned() - .collect() - } - - pub async fn wait_for_matching_events( - &mut self, - event: &ExpectedEvent, - count: usize, - entities: Arc>, - ) -> Result<()> { - let mut seen = self.matching_events(event, entities.clone()).await.len(); - - if seen >= count { - return Ok(()); - } - - runtime::timeout(Duration::from_secs(10), async { - while let Some(e) = self.recv().await { - let es = entities.read().await; - if events_match(&e, event, Some(&es)).is_ok() { - seen += 1; - if seen == count { - return Ok(()); - } - } - } - Err(Error::internal(format!( - "ran out of events before, only saw {} of {}", - seen, count - ))) - }) - .await??; - - Ok(()) - } -} diff --git a/src/test/spec/unified_runner/operation.rs b/src/test/spec/unified_runner/operation.rs index 427ec579a..b2ef60bf9 100644 --- a/src/test/spec/unified_runner/operation.rs +++ b/src/test/spec/unified_runner/operation.rs @@ -1,88 +1,97 @@ -#[cfg(feature = "in-use-encryption-unstable")] +mod bulk_write; +mod collection; +mod command; +mod connection; +mod count; +#[cfg(feature = "in-use-encryption")] mod csfle; -#[cfg(feature = "in-use-encryption-unstable")] -use self::csfle::*; - -use std::{ - collections::HashMap, - convert::TryInto, - fmt::Debug, - ops::Deref, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Duration, +mod delete; +mod failpoint; +mod find; +mod gridfs; +mod index; +mod insert; +mod iteration; +mod list; +mod rename; +mod search_index; +mod session; +mod thread; +mod topology; +mod transaction; +mod update; +mod wait; + +use std::{fmt::Debug, ops::Deref}; + +use collection::{ + Aggregate, + AssertCollectionExists, + AssertCollectionNotExists, + CreateCollection, + DropCollection, }; - -use futures::{ - future::BoxFuture, - io::AsyncReadExt, - stream::{StreamExt, TryStreamExt}, - FutureExt, +use command::{CreateCommandCursor, RunCommand, RunCursorCommand}; +use connection::{AssertNumberConnectionsCheckedOut, Close}; +use count::{AssertEventCount, CountDocuments, Distinct, EstimatedDocumentCount}; +use delete::{DeleteMany, DeleteOne}; +use failpoint::{FailPointCommand, TargetedFailPoint}; +use find::{ + CreateFindCursor, + Find, + FindOne, + FindOneAndDelete, + FindOneAndReplace, + FindOneAndUpdate, +}; +use futures::{future::BoxFuture, FutureExt}; +use gridfs::{Delete, DeleteByName, Download, DownloadByName, RenameByName, Upload}; +use index::{ + AssertIndexExists, + AssertIndexNotExists, + CreateIndex, + DropIndex, + ListIndexNames, + ListIndexes, }; +use insert::{InsertMany, InsertOne}; +use iteration::{IterateOnce, IterateUntilDocumentOrError}; +use list::{ListCollectionNames, ListCollections, ListDatabaseNames, ListDatabases}; +use rename::Rename; use serde::{ de::{DeserializeOwned, Deserializer}, Deserialize, }; -use time::OffsetDateTime; +use session::{ + AssertDifferentLsidOnLastTwoCommands, + AssertSameLsidOnLastTwoCommands, + AssertSessionDirty, + AssertSessionNotDirty, + AssertSessionPinned, + AssertSessionTransactionState, + AssertSessionUnpinned, + EndSession, +}; +use thread::{RunOnThread, WaitForThread}; use tokio::sync::Mutex; +use topology::{AssertTopologyType, RecordTopologyDescription}; +use transaction::{AbortTransaction, CommitTransaction, StartTransaction, WithTransaction}; +use update::{ReplaceOne, UpdateMany, UpdateOne}; +use wait::{Wait, WaitForEvent, WaitForPrimaryChange}; -use super::{ - results_match, - Entity, - EntityMap, - ExpectError, - ExpectedEvent, - TestCursor, - TestFileEntity, - TestRunner, -}; +use super::{results_match, Entity, ExpectError, TestCursor, TestFileEntity, TestRunner}; use crate::{ - bson::{doc, to_bson, Bson, Document}, - bson_util, - change_stream::options::ChangeStreamOptions, - client::session::TransactionState, - coll::options::Hint, - collation::Collation, + bson::{doc, Bson, Document}, error::{ErrorKind, Result}, - gridfs::options::{GridFsDownloadByNameOptions, GridFsUploadOptions}, - options::{ - AggregateOptions, - CountOptions, - CreateCollectionOptions, - DeleteOptions, - DistinctOptions, - DropCollectionOptions, - EstimatedDocumentCountOptions, - FindOneAndDeleteOptions, - FindOneAndReplaceOptions, - FindOneAndUpdateOptions, - FindOneOptions, - FindOptions, - IndexOptions, - InsertManyOptions, - InsertOneOptions, - ListCollectionsOptions, - ListDatabasesOptions, - ListIndexesOptions, - ReadConcern, - ReplaceOptions, - SelectionCriteria, - UpdateModifications, - UpdateOptions, - }, - runtime, - selection_criteria::ReadPreference, - test::FailPoint, - Collection, - Database, - IndexModel, - ServerType, - TopologyType, + options::ChangeStreamOptions, }; +use bulk_write::*; +#[cfg(feature = "in-use-encryption")] +use csfle::*; +use search_index::*; + pub(crate) trait TestOperation: Debug + Send + Sync { fn execute_test_runner_operation<'a>( &'a self, @@ -120,6 +129,7 @@ pub(crate) trait TestOperation: Debug + Send + Sync { /// If this operation specifies entities to create, returns those entities. Otherwise, /// returns None. + #[cfg(feature = "tracing-unstable")] fn test_file_entities(&self) -> Option<&Vec> { None } @@ -133,24 +143,52 @@ macro_rules! with_mut_session { ($test_runner:ident, $id:expr, |$session:ident| $body:expr) => { async { let id = $id; - let mut session_owned = match $test_runner.entities.write().await.remove(id).unwrap() { - Entity::Session(session_owned) => session_owned, + let entity = $test_runner.entities.write().await.remove(id).unwrap(); + match entity { + Entity::Session(mut session_owned) => { + let $session: &mut crate::ClientSession = &mut session_owned; + let out = $body.await; + $test_runner + .entities + .write() + .await + .insert(id.to_string(), Entity::Session(session_owned)); + out + } + Entity::SessionPtr(ptr) => { + let $session = unsafe { &mut *ptr.0 }; + let out = $body.await; + $test_runner + .entities + .write() + .await + .insert(id.to_string(), Entity::SessionPtr(ptr)); + out + } o => panic!( "expected {} to be a session entity, instead was {:?}", $id, o ), - }; - let $session = &mut session_owned; - let out = $body.await; - $test_runner - .entities - .write() - .await - .insert(id.to_string(), Entity::Session(session_owned)); - out + } + } + }; +} +use with_mut_session; + +macro_rules! with_opt_session { + ($test_runner:ident, $id:expr, $act:expr $(,)?) => { + async { + let act = $act; + match $id { + Some(id) => { + with_mut_session!($test_runner, id, |session| act.session(session)).await + } + None => act.await, + } } }; } +use with_opt_session; #[derive(Debug)] pub(crate) struct Operation { @@ -161,13 +199,19 @@ pub(crate) struct Operation { } impl Operation { - pub(crate) async fn execute<'a>(&self, test_runner: &TestRunner, description: &str) { + pub(crate) async fn execute(&self, test_runner: &TestRunner, description: &str) { + let _ = self.execute_fallible(test_runner, description).await; + } + + async fn execute_fallible(&self, test_runner: &TestRunner, description: &str) -> Result<()> { match self.object { OperationObject::TestRunner => { self.execute_test_runner_operation(test_runner).await; + Ok(()) } OperationObject::Entity(ref id) => { let result = self.execute_entity_operation(id, test_runner).await; + let error = result.as_ref().map_or_else(|e| Err(e.clone()), |_| Ok(())); match &self.expectation { Expectation::Result { @@ -217,10 +261,11 @@ impl Operation { "{}: {} should return an error", description, self.name )); - expect_error.verify_result(&error, description).unwrap(); + expect_error.verify_result(&error, description); } Expectation::Ignore => (), } + error } } } @@ -255,8 +300,9 @@ pub(crate) enum Expectation { fn deserialize_op<'de, 'a, T: 'a + DeserializeOwned + TestOperation>( value: Document, -) -> std::result::Result, bson::de::Error> { - bson::from_document::(value).map(|op| Box::new(op) as Box) +) -> std::result::Result, crate::bson_compat::DeError> { + crate::bson_compat::deserialize_from_document::(value) + .map(|op| Box::new(op) as Box) } impl<'de> Deserialize<'de> for Operation { @@ -266,7 +312,7 @@ impl<'de> Deserialize<'de> for Operation { struct OperationDefinition { pub(crate) name: String, pub(crate) object: OperationObject, - #[serde(default = "default_arguments")] + #[serde(default = "Document::new")] pub(crate) arguments: Document, pub(crate) expect_error: Option, pub(crate) expect_result: Option, @@ -274,10 +320,6 @@ impl<'de> Deserialize<'de> for Operation { pub(crate) ignore_result_and_error: Option, } - fn default_arguments() -> Document { - doc! {} - } - let definition = OperationDefinition::deserialize(deserializer)?; let boxed_op = match definition.name.as_str() { "insertOne" => deserialize_op::(definition.arguments), @@ -288,6 +330,7 @@ impl<'de> Deserialize<'de> for Operation { "deleteOne" => deserialize_op::(definition.arguments), "find" => deserialize_op::(definition.arguments), "createFindCursor" => deserialize_op::(definition.arguments), + "createCommandCursor" => deserialize_op::(definition.arguments), "aggregate" => deserialize_op::(definition.arguments), "distinct" => deserialize_op::(definition.arguments), "countDocuments" => deserialize_op::(definition.arguments), @@ -314,6 +357,7 @@ impl<'de> Deserialize<'de> for Operation { "createCollection" => deserialize_op::(definition.arguments), "dropCollection" => deserialize_op::(definition.arguments), "runCommand" => deserialize_op::(definition.arguments), + "runCursorCommand" => deserialize_op::(definition.arguments), "endSession" => deserialize_op::(definition.arguments), "assertSessionTransactionState" => { deserialize_op::(definition.arguments) @@ -335,6 +379,7 @@ impl<'de> Deserialize<'de> for Operation { "startTransaction" => deserialize_op::(definition.arguments), "commitTransaction" => deserialize_op::(definition.arguments), "abortTransaction" => deserialize_op::(definition.arguments), + "withTransaction" => deserialize_op::(definition.arguments), "createIndex" => deserialize_op::(definition.arguments), "listIndexes" => deserialize_op::(definition.arguments), "listIndexNames" => deserialize_op::(definition.arguments), @@ -348,8 +393,7 @@ impl<'de> Deserialize<'de> for Operation { } "close" => deserialize_op::(definition.arguments), "createChangeStream" => deserialize_op::(definition.arguments), - "rename" => deserialize_op::(definition.arguments), - "loop" => deserialize_op::(definition.arguments), + "rename" => deserialize_op::(definition.arguments), "waitForEvent" => deserialize_op::(definition.arguments), "assertEventCount" => deserialize_op::(definition.arguments), "runOnThread" => deserialize_op::(definition.arguments), @@ -364,21 +408,35 @@ impl<'de> Deserialize<'de> for Operation { "download" => deserialize_op::(definition.arguments), "downloadByName" => deserialize_op::(definition.arguments), "delete" => deserialize_op::(definition.arguments), + "deleteByName" => deserialize_op::(definition.arguments), "upload" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + "renameByName" => deserialize_op::(definition.arguments), + #[cfg(feature = "in-use-encryption")] "getKeyByAltName" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "deleteKey" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "getKey" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "addKeyAltName" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "createDataKey" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "getKeys" => deserialize_op::(definition.arguments), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] "removeKeyAltName" => deserialize_op::(definition.arguments), + "iterateOnce" => deserialize_op::(definition.arguments), + "createSearchIndex" => deserialize_op::(definition.arguments), + "createSearchIndexes" => deserialize_op::(definition.arguments), + "dropSearchIndex" => deserialize_op::(definition.arguments), + "listSearchIndexes" => deserialize_op::(definition.arguments), + "updateSearchIndex" => deserialize_op::(definition.arguments), + "clientBulkWrite" => deserialize_op::(definition.arguments), + #[cfg(feature = "in-use-encryption")] + "encrypt" => deserialize_op::(definition.arguments), + #[cfg(feature = "in-use-encryption")] + "decrypt" => deserialize_op::(definition.arguments), + "dropIndex" => deserialize_op::(definition.arguments), s => Ok(Box::new(UnimplementedOperation { _name: s.to_string(), }) as Box), @@ -429,2442 +487,72 @@ impl Deref for Operation { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct DeleteMany { - filter: Document, - #[serde(flatten)] - options: DeleteOptions, -} - -impl TestOperation for DeleteMany { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .delete_many(self.filter.clone(), self.options.clone()) - .await?; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct DeleteOne { - filter: Document, - session: Option, - #[serde(flatten)] - options: DeleteOptions, -} - -impl TestOperation for DeleteOne { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .delete_one_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .delete_one(self.filter.clone(), self.options.clone()) - .await? - } - }; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Find { - filter: Document, - session: Option, - // `FindOptions` cannot be embedded directly because serde doesn't support combining `flatten` - // and `deny_unknown_fields`, so its fields are replicated here. - allow_disk_use: Option, - allow_partial_results: Option, - batch_size: Option, - comment: Option, - hint: Option, - limit: Option, - max: Option, - max_scan: Option, - #[serde( - default, - rename = "maxTimeMS", - deserialize_with = "bson_util::deserialize_duration_option_from_u64_millis" - )] - max_time: Option, - min: Option, - no_cursor_timeout: Option, - projection: Option, - read_concern: Option, - return_key: Option, - show_record_id: Option, - skip: Option, - sort: Option, - collation: Option, - #[serde(rename = "let")] - let_vars: Option, -} - -impl Find { - async fn get_cursor<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> Result { - let collection = test_runner.get_collection(id).await; - - let (comment, comment_bson) = match &self.comment { - Some(Bson::String(string)) => (Some(string.clone()), None), - Some(bson) => (None, Some(bson.clone())), - None => (None, None), - }; - - // `FindOptions` is constructed without the use of `..Default::default()` to enforce at - // compile-time that any new fields added there need to be considered here. - let options = FindOptions { - allow_disk_use: self.allow_disk_use, - allow_partial_results: self.allow_partial_results, - batch_size: self.batch_size, - comment, - comment_bson, - hint: self.hint.clone(), - limit: self.limit, - max: self.max.clone(), - max_scan: self.max_scan, - max_time: self.max_time, - min: self.min.clone(), - no_cursor_timeout: self.no_cursor_timeout, - projection: self.projection.clone(), - read_concern: self.read_concern.clone(), - return_key: self.return_key, - show_record_id: self.show_record_id, - skip: self.skip, - sort: self.sort.clone(), - collation: self.collation.clone(), - cursor_type: None, - max_await_time: None, - selection_criteria: None, - let_vars: self.let_vars.clone(), - }; - match &self.session { - Some(session_id) => { - let cursor = with_mut_session!(test_runner, session_id, |session| async { - collection - .find_with_session(self.filter.clone(), options, session) - .await - }) - .await?; - Ok(TestCursor::Session { - cursor, - session_id: session_id.clone(), - }) - } - None => { - let cursor = collection.find(self.filter.clone(), options).await?; - Ok(TestCursor::Normal(Mutex::new(cursor))) - } - } - } -} - -impl TestOperation for Find { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let result = match self.get_cursor(id, test_runner).await? { - TestCursor::Session { - mut cursor, - session_id, - } => { - with_mut_session!(test_runner, session_id.as_str(), |s| async { - cursor.stream(s).try_collect::>().await - }) - .await? - } - TestCursor::Normal(cursor) => { - let cursor = cursor.into_inner(); - cursor.try_collect::>().await? - } - TestCursor::ChangeStream(_) => panic!("get_cursor returned a change stream"), - TestCursor::Closed => panic!("get_cursor returned a closed cursor"), - }; - Ok(Some(Bson::from(result).into())) - } - .boxed() - } - - fn returns_root_documents(&self) -> bool { - true - } -} - -#[derive(Debug, Default, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CreateFindCursor { - // `Find` cannot be embedded directly because serde doesn't support combining `flatten` - // and `deny_unknown_fields`, so its fields are replicated here. - filter: Document, - session: Option, - allow_disk_use: Option, - allow_partial_results: Option, - batch_size: Option, - comment: Option, - hint: Option, - limit: Option, - max: Option, - max_scan: Option, - #[serde(rename = "maxTimeMS")] - max_time: Option, - min: Option, - no_cursor_timeout: Option, - projection: Option, - read_concern: Option, - return_key: Option, - show_record_id: Option, - skip: Option, - sort: Option, - collation: Option, - #[serde(rename = "let")] - let_vars: Option, -} - -impl TestOperation for CreateFindCursor { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let find = Find { - filter: self.filter.clone(), - session: self.session.clone(), - allow_disk_use: self.allow_disk_use, - allow_partial_results: self.allow_partial_results, - batch_size: self.batch_size, - comment: self.comment.clone(), - hint: self.hint.clone(), - limit: self.limit, - max: self.max.clone(), - max_scan: self.max_scan, - max_time: self.max_time, - min: self.min.clone(), - no_cursor_timeout: self.no_cursor_timeout, - projection: self.projection.clone(), - read_concern: self.read_concern.clone(), - return_key: self.return_key, - show_record_id: self.show_record_id, - skip: self.skip, - sort: self.sort.clone(), - collation: self.collation.clone(), - let_vars: self.let_vars.clone(), - }; - let cursor = find.get_cursor(id, test_runner).await?; - Ok(Some(Entity::Cursor(cursor))) - } - .boxed() - } - - fn returns_root_documents(&self) -> bool { - false - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct InsertMany { - documents: Vec, - session: Option, +pub(super) struct CreateChangeStream { + pipeline: Vec, #[serde(flatten)] - options: InsertManyOptions, + options: Option, } -impl TestOperation for InsertMany { +impl TestOperation for CreateChangeStream { fn execute_entity_operation<'a>( &'a self, id: &'a str, test_runner: &'a TestRunner, ) -> BoxFuture<'a, Result>> { async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| { - async move { - collection - .insert_many_with_session( - self.documents.clone(), - self.options.clone(), - session, - ) - .await - } - .boxed() - }) - .await? - } - None => { - collection - .insert_many(self.documents.clone(), self.options.clone()) + let entities = test_runner.entities.read().await; + let target = entities.get(id).unwrap(); + let stream = match target { + Entity::Client(ce) => { + ce.watch() + .pipeline(self.pipeline.clone()) + .with_options(self.options.clone()) .await? } - }; - let ids: HashMap = result - .inserted_ids - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - .collect(); - let ids = to_bson(&ids)?; - Ok(Some(Bson::from(doc! { "insertedIds": ids }).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct InsertOne { - document: Document, - session: Option, - #[serde(flatten)] - options: InsertOneOptions, -} - -impl TestOperation for InsertOne { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .insert_one_with_session( - self.document.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .insert_one(self.document.clone(), self.options.clone()) + Entity::Database(db) => { + db.watch() + .pipeline(self.pipeline.clone()) + .with_options(self.options.clone()) .await? } - }; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct UpdateMany { - filter: Document, - update: UpdateModifications, - #[serde(flatten)] - options: UpdateOptions, -} - -impl TestOperation for UpdateMany { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .update_many( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) - .await?; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct UpdateOne { - filter: Document, - update: UpdateModifications, - #[serde(flatten)] - options: UpdateOptions, - session: Option, -} - -impl TestOperation for UpdateOne { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .update_one_with_session( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .update_one( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) + Entity::Collection(coll) => { + coll.watch() + .pipeline(self.pipeline.clone()) + .with_options(self.options.clone()) .await? } + _ => panic!("Invalid entity for createChangeStream"), }; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug)] -pub(super) struct Aggregate { - pipeline: Vec, - session: Option, - options: AggregateOptions, -} - -// TODO RUST-1364: remove this impl and derive Deserialize instead -impl<'de> Deserialize<'de> for Aggregate { - fn deserialize>(deserializer: D) -> std::result::Result { - #[derive(Deserialize)] - struct Helper { - pipeline: Vec, - session: Option, - comment: Option, - #[serde(flatten)] - options: AggregateOptions, - } - - let mut helper = Helper::deserialize(deserializer)?; - match helper.comment { - Some(Bson::String(string)) => { - helper.options.comment = Some(string); - } - Some(bson) => { - helper.options.comment_bson = Some(bson); - } - _ => {} - } - - Ok(Self { - pipeline: helper.pipeline, - session: helper.session, - options: helper.options, - }) - } -} - -impl TestOperation for Aggregate { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let result = match &self.session { - Some(session_id) => { - enum AggregateEntity { - Collection(Collection), - Database(Database), - Other(String), - } - let entity = match test_runner.entities.read().await.get(id).unwrap() { - Entity::Collection(c) => AggregateEntity::Collection(c.clone()), - Entity::Database(d) => AggregateEntity::Database(d.clone()), - other => AggregateEntity::Other(format!("{:?}", other)), - }; - with_mut_session!(test_runner, session_id, |session| async { - let mut cursor = match entity { - AggregateEntity::Collection(collection) => { - collection - .aggregate_with_session( - self.pipeline.clone(), - self.options.clone(), - session, - ) - .await? - } - AggregateEntity::Database(db) => { - db.aggregate_with_session( - self.pipeline.clone(), - self.options.clone(), - session, - ) - .await? - } - AggregateEntity::Other(debug) => { - panic!("Cannot execute aggregate on {}", &debug) - } - }; - cursor.stream(session).try_collect::>().await - }) - .await? - } - None => { - let entities = test_runner.entities.read().await; - let cursor = match entities.get(id).unwrap() { - Entity::Collection(collection) => { - collection - .aggregate(self.pipeline.clone(), self.options.clone()) - .await? - } - Entity::Database(db) => { - db.aggregate(self.pipeline.clone(), self.options.clone()) - .await? - } - other => panic!("Cannot execute aggregate on {:?}", &other), - }; - cursor.try_collect::>().await? - } - }; - Ok(Some(Bson::from(result).into())) + Ok(Some(Entity::Cursor(TestCursor::ChangeStream(Mutex::new( + stream.with_type::(), + ))))) } .boxed() } - - fn returns_root_documents(&self) -> bool { - true - } } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Distinct { - field_name: String, - filter: Option, - session: Option, - #[serde(flatten)] - options: DistinctOptions, +pub(super) struct CreateEntities { + entities: Vec, } -impl TestOperation for Distinct { - fn execute_entity_operation<'a>( +impl TestOperation for CreateEntities { + fn execute_test_runner_operation<'a>( &'a self, - id: &'a str, test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .distinct_with_session( - &self.field_name, - self.filter.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .distinct(&self.field_name, self.filter.clone(), self.options.clone()) - .await? - } - }; - Ok(Some(Bson::Array(result).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CountDocuments { - filter: Document, - session: Option, - #[serde(flatten)] - options: CountOptions, -} - -impl TestOperation for CountDocuments { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .count_documents_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .count_documents(self.filter.clone(), self.options.clone()) - .await? - } - }; - Ok(Some(Bson::Int64(result.try_into().unwrap()).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct EstimatedDocumentCount { - #[serde(flatten)] - options: EstimatedDocumentCountOptions, -} - -impl TestOperation for EstimatedDocumentCount { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .estimated_document_count(self.options.clone()) - .await?; - Ok(Some(Bson::Int64(result.try_into().unwrap()).into())) - } - .boxed() - } -} - -#[derive(Debug, Default)] -pub(super) struct FindOne { - filter: Option, - options: FindOneOptions, -} - -// TODO RUST-1364: remove this impl and derive Deserialize instead -impl<'de> Deserialize<'de> for FindOne { - fn deserialize>(deserializer: D) -> std::result::Result { - #[derive(Deserialize)] - struct Helper { - filter: Option, - comment: Option, - #[serde(flatten)] - options: FindOneOptions, - } - - let mut helper = Helper::deserialize(deserializer)?; - match helper.comment { - Some(Bson::String(string)) => { - helper.options.comment = Some(string); - } - Some(bson) => { - helper.options.comment_bson = Some(bson); - } - _ => {} - } - - Ok(Self { - filter: helper.filter, - options: helper.options, - }) - } -} - -impl TestOperation for FindOne { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .find_one(self.filter.clone(), self.options.clone()) - .await?; - match result { - Some(result) => Ok(Some(Bson::from(result).into())), - None => Ok(Some(Entity::None)), - } - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct ListDatabases { - filter: Option, - session: Option, - #[serde(flatten)] - options: ListDatabasesOptions, -} - -impl TestOperation for ListDatabases { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let client = test_runner.get_client(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - client - .list_databases_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - client - .list_databases(self.filter.clone(), self.options.clone()) - .await? - } - }; - Ok(Some(bson::to_bson(&result)?.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct ListDatabaseNames { - filter: Option, - #[serde(flatten)] - options: ListDatabasesOptions, -} - -impl TestOperation for ListDatabaseNames { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let client = test_runner.get_client(id).await; - let result = client - .list_database_names(self.filter.clone(), self.options.clone()) - .await?; - let result: Vec = result.iter().map(|s| Bson::String(s.to_string())).collect(); - Ok(Some(Bson::Array(result).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct ListCollections { - filter: Option, - session: Option, - #[serde(flatten)] - options: ListCollectionsOptions, -} - -impl TestOperation for ListCollections { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let db = test_runner.get_database(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - let mut cursor = db - .list_collections_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await?; - cursor.stream(session).try_collect::>().await - }) - .await? - } - None => { - let cursor = db - .list_collections(self.filter.clone(), self.options.clone()) - .await?; - cursor.try_collect::>().await? - } - }; - Ok(Some(bson::to_bson(&result)?.into())) - } - .boxed() - } - - fn returns_root_documents(&self) -> bool { - true - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct ListCollectionNames { - filter: Option, -} - -impl TestOperation for ListCollectionNames { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let db = test_runner.get_database(id).await; - let result = db.list_collection_names(self.filter.clone()).await?; - let result: Vec = result.iter().map(|s| Bson::String(s.to_string())).collect(); - Ok(Some(Bson::from(result).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct ReplaceOne { - filter: Document, - replacement: Document, - #[serde(flatten)] - options: ReplaceOptions, -} - -impl TestOperation for ReplaceOne { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .replace_one( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - ) - .await?; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct FindOneAndUpdate { - filter: Document, - update: UpdateModifications, - session: Option, - #[serde(flatten)] - options: FindOneAndUpdateOptions, -} - -impl TestOperation for FindOneAndUpdate { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - collection - .find_one_and_update_with_session( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - session, - ) - .await - }) - .await? - } - None => { - collection - .find_one_and_update( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct FindOneAndReplace { - filter: Document, - replacement: Document, - #[serde(flatten)] - options: FindOneAndReplaceOptions, -} - -impl TestOperation for FindOneAndReplace { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .find_one_and_replace( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - ) - .await?; - let result = to_bson(&result)?; - - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct FindOneAndDelete { - filter: Document, - #[serde(flatten)] - options: FindOneAndDeleteOptions, -} - -impl TestOperation for FindOneAndDelete { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let result = collection - .find_one_and_delete(self.filter.clone(), self.options.clone()) - .await?; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct FailPointCommand { - fail_point: FailPoint, - client: String, -} - -impl TestOperation for FailPointCommand { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let client = test_runner.get_client(&self.client).await; - let guard = self - .fail_point - .enable(&client, Some(ReadPreference::Primary.into())) - .await - .unwrap(); - test_runner.fail_point_guards.write().await.push(guard); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct TargetedFailPoint { - fail_point: FailPoint, - session: String, -} - -impl TestOperation for TargetedFailPoint { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let selection_criteria = - with_mut_session!(test_runner, self.session.as_str(), |session| async { - session - .transaction - .pinned_mongos() - .cloned() - .unwrap_or_else(|| panic!("ClientSession not pinned")) - }) - .await; - let fail_point_guard = test_runner - .internal_client - .enable_failpoint(self.fail_point.clone(), Some(selection_criteria)) - .await - .unwrap(); - test_runner - .fail_point_guards - .write() - .await - .push(fail_point_guard); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertCollectionExists { - collection_name: String, - database_name: String, -} - -impl TestOperation for AssertCollectionExists { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let db = test_runner.internal_client.database(&self.database_name); - let names = db.list_collection_names(None).await.unwrap(); - assert!(names.contains(&self.collection_name)); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertCollectionNotExists { - collection_name: String, - database_name: String, -} - -impl TestOperation for AssertCollectionNotExists { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let db = test_runner.internal_client.database(&self.database_name); - let names = db.list_collection_names(None).await.unwrap(); - assert!(!names.contains(&self.collection_name)); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CreateCollection { - collection: String, - #[serde(flatten)] - options: CreateCollectionOptions, - session: Option, -} - -impl TestOperation for CreateCollection { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let database = test_runner.get_database(id).await; - - if let Some(session_id) = &self.session { - with_mut_session!(test_runner, session_id, |session| async { - database - .create_collection_with_session( - &self.collection, - self.options.clone(), - session, - ) - .await - }) - .await?; - } else { - database - .create_collection(&self.collection, self.options.clone()) - .await?; - } - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct DropCollection { - collection: String, - #[serde(flatten)] - options: DropCollectionOptions, - session: Option, -} - -impl TestOperation for DropCollection { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let database = test_runner.get_database(id).await; - let collection = database.collection::(&self.collection).clone(); - - if let Some(session_id) = &self.session { - with_mut_session!(test_runner, session_id, |session| async { - collection - .drop_with_session(self.options.clone(), session) - .await - }) - .await?; - } else { - collection.drop(self.options.clone()).await?; - } - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct RunCommand { - command: Document, - // We don't need to use this field, but it needs to be included during deserialization so that - // we can use the deny_unknown_fields tag. - #[serde(rename = "commandName")] - _command_name: String, - read_concern: Option, - read_preference: Option, - session: Option, - write_concern: Option, -} - -impl TestOperation for RunCommand { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let mut command = self.command.clone(); - if let Some(ref read_concern) = self.read_concern { - command.insert("readConcern", read_concern.clone()); - } - if let Some(ref write_concern) = self.write_concern { - command.insert("writeConcern", write_concern.clone()); - } - - let db = test_runner.get_database(id).await; - let result = match &self.session { - Some(session_id) => { - with_mut_session!(test_runner, session_id, |session| async { - db.run_command_with_session(command, self.read_preference.clone(), session) - .await - }) - .await? - } - None => { - db.run_command(command, self.read_preference.clone()) - .await? - } - }; - let result = to_bson(&result)?; - Ok(Some(result.into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct EndSession {} - -impl TestOperation for EndSession { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - with_mut_session!(test_runner, id, |session| async { - session.client_session.take(); - }) - .await; - runtime::delay_for(Duration::from_secs(1)).await; - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSessionTransactionState { - session: String, - state: String, -} - -impl TestOperation for AssertSessionTransactionState { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let session_state = - with_mut_session!(test_runner, self.session.as_str(), |session| async { - match &session.transaction.state { - TransactionState::None => "none", - TransactionState::Starting => "starting", - TransactionState::InProgress => "inprogress", - TransactionState::Committed { data_committed: _ } => "committed", - TransactionState::Aborted => "aborted", - } - }) - .await; - assert_eq!(session_state, self.state); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSessionPinned { - session: String, -} - -impl TestOperation for AssertSessionPinned { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let is_pinned = - with_mut_session!(test_runner, self.session.as_str(), |session| async { - session.transaction.pinned_mongos().is_some() - }) - .await; - assert!(is_pinned); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSessionUnpinned { - session: String, -} - -impl TestOperation for AssertSessionUnpinned { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let is_pinned = with_mut_session!(test_runner, self.session.as_str(), |session| { - async move { session.transaction.pinned_mongos().is_some() } - }) - .await; - assert!(!is_pinned); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertDifferentLsidOnLastTwoCommands { - client: String, -} - -impl TestOperation for AssertDifferentLsidOnLastTwoCommands { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let entities = test_runner.entities.read().await; - let client = entities.get(&self.client).unwrap().as_client(); - let events = client.get_all_command_started_events(); - - let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); - let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); - assert_ne!(lsid1, lsid2); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSameLsidOnLastTwoCommands { - client: String, -} - -impl TestOperation for AssertSameLsidOnLastTwoCommands { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let entities = test_runner.entities.read().await; - let client = entities.get(&self.client).unwrap().as_client(); - client.sync_workers().await; - let events = client.get_all_command_started_events(); - - let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); - let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); - assert_eq!(lsid1, lsid2); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSessionDirty { - session: String, -} - -impl TestOperation for AssertSessionDirty { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let dirty = with_mut_session!(test_runner, self.session.as_str(), |session| { - async move { session.is_dirty() }.boxed() - }) - .await; - assert!(dirty); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertSessionNotDirty { - session: String, -} - -impl TestOperation for AssertSessionNotDirty { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let dirty = with_mut_session!(test_runner, self.session.as_str(), |session| { - async move { session.is_dirty() } - }) - .await; - assert!(!dirty); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct StartTransaction {} - -impl TestOperation for StartTransaction { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - with_mut_session!(test_runner, id, |session| { - async move { session.start_transaction(None).await } - }) - .await?; - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CommitTransaction {} - -impl TestOperation for CommitTransaction { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - with_mut_session!(test_runner, id, |session| { - async move { session.commit_transaction().await } - }) - .await?; - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AbortTransaction {} - -impl TestOperation for AbortTransaction { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - with_mut_session!(test_runner, id, |session| { - async move { session.abort_transaction().await } - }) - .await?; - Ok(None) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] -pub(super) struct CreateIndex { - session: Option, - keys: Document, - name: Option, -} - -impl TestOperation for CreateIndex { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let options = IndexOptions::builder().name(self.name.clone()).build(); - let index = IndexModel::builder() - .keys(self.keys.clone()) - .options(options) - .build(); - - let collection = test_runner.get_collection(id).await; - let name = match self.session { - Some(ref session_id) => { - with_mut_session!(test_runner, session_id, |session| { - async move { - collection - .create_index_with_session(index, None, session) - .await - .map(|model| model.index_name) - } - }) - .await? - } - None => collection.create_index(index, None).await?.index_name, - }; - Ok(Some(Bson::String(name).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct ListIndexes { - session: Option, - #[serde(flatten)] - options: ListIndexesOptions, -} - -impl TestOperation for ListIndexes { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let indexes: Vec = match self.session { - Some(ref session) => { - with_mut_session!(test_runner, session, |session| { - async { - collection - .list_indexes_with_session(self.options.clone(), session) - .await? - .stream(session) - .try_collect() - .await - } - }) - .await? - } - None => { - collection - .list_indexes(self.options.clone()) - .await? - .try_collect() - .await? - } - }; - let indexes: Vec = indexes - .iter() - .map(|index| bson::to_document(index).unwrap()) - .collect(); - Ok(Some(Bson::from(indexes).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -pub(super) struct ListIndexNames { - session: Option, -} - -impl TestOperation for ListIndexNames { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let collection = test_runner.get_collection(id).await; - let names = match self.session { - Some(ref session) => { - with_mut_session!(test_runner, session.as_str(), |s| { - async move { collection.list_index_names_with_session(s).await } - }) - .await? - } - None => collection.list_index_names().await?, - }; - Ok(Some(Bson::from(names).into())) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertIndexExists { - collection_name: String, - database_name: String, - index_name: String, -} - -impl TestOperation for AssertIndexExists { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let coll = test_runner - .internal_client - .database(&self.database_name) - .collection::(&self.collection_name); - let names = coll.list_index_names().await.unwrap(); - assert!(names.contains(&self.index_name)); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertIndexNotExists { - collection_name: String, - database_name: String, - index_name: String, -} - -impl TestOperation for AssertIndexNotExists { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let coll = test_runner - .internal_client - .database(&self.database_name) - .collection::(&self.collection_name); - match coll.list_index_names().await { - Ok(indexes) => assert!(!indexes.contains(&self.index_name)), - // a namespace not found error indicates that the index does not exist - Err(err) => assert_eq!(err.sdam_code(), Some(26)), - } - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct IterateUntilDocumentOrError {} - -impl TestOperation for IterateUntilDocumentOrError { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - // A `SessionCursor` also requires a `&mut Session`, which would cause conflicting - // borrows, so take the cursor from the map and return it after execution instead. - let mut cursor = test_runner - .entities - .write() - .await - .remove(id) - .unwrap() - .into_cursor(); - let next = match &mut cursor { - TestCursor::Normal(cursor) => { - let mut cursor = cursor.lock().await; - cursor.next().await - } - TestCursor::Session { cursor, session_id } => { - cursor - .next( - test_runner - .entities - .write() - .await - .get_mut(session_id) - .unwrap() - .as_mut_session_entity(), - ) - .await - } - TestCursor::ChangeStream(stream) => { - let mut stream = stream.lock().await; - stream.next().await.map(|res| { - res.map(|ev| match bson::to_bson(&ev) { - Ok(Bson::Document(doc)) => doc, - _ => panic!("invalid serialization result"), - }) - }) - } - TestCursor::Closed => None, - }; - test_runner - .entities - .write() - .await - .insert(id.to_string(), Entity::Cursor(cursor)); - next.transpose() - .map(|opt| opt.map(|doc| Entity::Bson(Bson::Document(doc)))) - } - .boxed() - } - - fn returns_root_documents(&self) -> bool { - true - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Close {} - -impl TestOperation for Close { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let mut entities = test_runner.entities.write().await; - let target_entity = entities.get(id).unwrap(); - match target_entity { - Entity::Client(_) => { - let mut client = entities.get_mut(id).unwrap().as_mut_client(); - let closed_client_topology_id = client.topology_id; - client.client = None; - - let mut entities_to_remove = vec![]; - for (key, value) in entities.iter() { - match value { - // skip clients so that we don't remove the client entity itself from - // the map: we want to preserve it so we can - // access the other data stored on the entity. - Entity::Client(_) => {} - _ => { - if value.client_topology_id().await - == Some(closed_client_topology_id) - { - entities_to_remove.push(key.clone()); - } - } - } - } - for entity_id in entities_to_remove { - entities.remove(&entity_id); - } - - Ok(None) - } - Entity::Cursor(_) => { - let cursor = entities.get_mut(id).unwrap().as_mut_cursor(); - let rx = cursor.make_kill_watcher().await; - *cursor = TestCursor::Closed; - drop(entities); - let _ = rx.await; - Ok(None) - } - _ => panic!( - "Unsupported entity {:?} for close operation; expected Client or Cursor", - target_entity - ), - } - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertNumberConnectionsCheckedOut { - client: String, - connections: u32, -} - -impl TestOperation for AssertNumberConnectionsCheckedOut { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let client = test_runner.get_client(&self.client).await; - client.sync_workers().await; - assert_eq!(client.connections_checked_out(), self.connections); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CreateChangeStream { - pipeline: Vec, - #[serde(flatten)] - options: Option, -} - -impl TestOperation for CreateChangeStream { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let entities = test_runner.entities.read().await; - let target = entities.get(id).unwrap(); - let stream = match target { - Entity::Client(ce) => { - ce.watch(self.pipeline.clone(), self.options.clone()) - .await? - } - Entity::Database(db) => { - db.watch(self.pipeline.clone(), self.options.clone()) - .await? - } - Entity::Collection(coll) => { - coll.watch(self.pipeline.clone(), self.options.clone()) - .await? - } - _ => panic!("Invalid entity for createChangeStream"), - }; - Ok(Some(Entity::Cursor(TestCursor::ChangeStream(Mutex::new( - stream.with_type::(), - ))))) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct RenameCollection { - to: String, -} - -impl TestOperation for RenameCollection { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let target = test_runner.get_collection(id).await; - let ns = target.namespace(); - let mut to_ns = ns.clone(); - to_ns.coll = self.to.clone(); - let cmd = doc! { - "renameCollection": crate::bson::to_bson(&ns)?, - "to": crate::bson::to_bson(&to_ns)?, - }; - let admin = test_runner.internal_client.database("admin"); - admin.run_command(cmd, None).await?; - Ok(None) - } - .boxed() - } -} - -macro_rules! report_error { - ($loop:expr, $error:expr, $entities:expr) => {{ - let error = format!("{:?}", $error); - report_error_or_failure!( - $loop.store_errors_as_entity, - $loop.store_failures_as_entity, - error, - $entities - ); - }}; -} - -macro_rules! report_failure { - ($loop:expr, $name:expr, $actual:expr, $expected:expr, $entities:expr) => {{ - let error = format!( - "{} error: got {:?}, expected {:?}", - $name, $actual, $expected - ); - report_error_or_failure!( - $loop.store_failures_as_entity, - $loop.store_errors_as_entity, - error, - $entities - ); - }}; -} - -macro_rules! report_error_or_failure { - ($first_option:expr, $second_option:expr, $error:expr, $entities:expr) => {{ - let id = if let Some(ref id) = $first_option { - id - } else if let Some(ref id) = $second_option { - id - } else { - panic!( - "At least one of storeErrorsAsEntity and storeFailuresAsEntity must be specified \ - for a loop operation" - ); - }; - - match $entities.get_mut(id) { - Some(Entity::Bson(Bson::Array(array))) => { - let doc = doc! { - "error": $error, - "time": OffsetDateTime::now_utc().unix_timestamp(), - }; - array.push(doc.into()); - } - _ => panic!("Test runner should contain a Bson::Array entity for {}", id), - }; - - // The current iteration should end if an error or failure is encountered. - break; - }}; -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Loop { - operations: Vec, - store_errors_as_entity: Option, - store_failures_as_entity: Option, - store_successes_as_entity: Option, - store_iterations_as_entity: Option, -} - -impl TestOperation for Loop { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - if let Some(id) = &self.store_errors_as_entity { - let errors = Bson::Array(vec![]); - test_runner.insert_entity(id, errors).await; - } - if let Some(id) = &self.store_failures_as_entity { - let failures = Bson::Array(vec![]); - test_runner.insert_entity(id, failures).await; - } - if let Some(id) = &self.store_successes_as_entity { - let successes = Bson::Int64(0); - test_runner.insert_entity(id, successes).await; - } - if let Some(id) = &self.store_iterations_as_entity { - let iterations = Bson::Int64(0); - test_runner.insert_entity(id, iterations).await; - } - - let continue_looping = Arc::new(AtomicBool::new(true)); - let continue_looping_handle = continue_looping.clone(); - ctrlc::set_handler(move || { - continue_looping_handle.store(false, Ordering::SeqCst); - }) - .expect("Failed to set ctrl-c handler"); - - while continue_looping.load(Ordering::SeqCst) { - for operation in &self.operations { - let result = match operation.object { - OperationObject::TestRunner => { - panic!("Operations within a loop must be entity operations") - } - OperationObject::Entity(ref id) => { - operation.execute_entity_operation(id, test_runner).await - } - }; - - let mut entities = test_runner.entities.write().await; - match (result, &operation.expectation) { - ( - Ok(entity), - Expectation::Result { - expected_value, - save_as_entity, - }, - ) => { - if let Some(expected_value) = expected_value { - let actual_value = match entity { - Some(Entity::Bson(ref actual_value)) => Some(actual_value), - None => None, - _ => { - report_failure!( - self, - &operation.name, - entity, - expected_value, - &mut entities - ); - } - }; - if results_match( - actual_value, - expected_value, - operation.returns_root_documents(), - Some(&entities), - ) - .is_ok() - { - self.report_success(&mut entities); - } else { - report_failure!( - self, - &operation.name, - actual_value, - expected_value, - &mut entities - ); - } - } else { - self.report_success(&mut entities); - } - if let (Some(entity), Some(id)) = (entity, save_as_entity) { - entities.insert(id.to_string(), entity); - } - } - (Ok(result), Expectation::Error(ref expected_error)) => { - report_failure!( - self, - &operation.name, - result, - expected_error, - &mut entities - ); - } - (Ok(_), Expectation::Ignore) => { - self.report_success(&mut entities); - } - (Err(error), Expectation::Error(ref expected_error)) => { - match expected_error.verify_result(&error, operation.name.as_str()) { - Ok(_) => self.report_success(&mut entities), - Err(e) => report_error_or_failure!( - self.store_failures_as_entity, - self.store_errors_as_entity, - e, - &mut entities - ), - } - } - (Err(error), Expectation::Result { .. } | Expectation::Ignore) => { - report_error!(self, error, &mut entities); - } - } - } - let mut entities = test_runner.entities.write().await; - self.report_iteration(&mut entities); - } - } - .boxed() - } -} - -impl Loop { - fn report_iteration(&self, entities: &mut EntityMap) { - Self::increment_count(self.store_iterations_as_entity.as_ref(), entities) - } - - fn report_success(&self, test_runner: &mut EntityMap) { - Self::increment_count(self.store_successes_as_entity.as_ref(), test_runner) - } - - fn increment_count(id: Option<&String>, entities: &mut EntityMap) { - if let Some(id) = id { - match entities.get_mut(id) { - Some(Entity::Bson(Bson::Int64(count))) => *count += 1, - _ => panic!("Test runner should contain a Bson::Int64 entity for {}", id), - } - } - } -} -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct RunOnThread { - thread: String, - operation: Arc, -} - -impl TestOperation for RunOnThread { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let thread = test_runner.get_thread(self.thread.as_str()).await; - thread.run_operation(self.operation.clone()); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct WaitForThread { - thread: String, -} - -impl TestOperation for WaitForThread { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let thread = test_runner.get_thread(self.thread.as_str()).await; - thread.wait().await.unwrap_or_else(|e| { - panic!("thread {:?} did not exit successfully: {}", self.thread, e) - }); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertEventCount { - client: String, - event: ExpectedEvent, - count: usize, -} - -impl TestOperation for AssertEventCount { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let client = test_runner.get_client(self.client.as_str()).await; - let entities = test_runner.entities.clone(); - let actual_events = client - .observer - .lock() - .await - .matching_events(&self.event, entities) - .await; - assert_eq!( - actual_events.len(), - self.count, - "expected to see {} events matching: {:#?}, instead saw: {:#?}", - self.count, - self.event, - actual_events - ); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct WaitForEvent { - client: String, - event: ExpectedEvent, - count: usize, -} - -impl TestOperation for WaitForEvent { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let client = test_runner.get_client(self.client.as_str()).await; - let entities = test_runner.entities.clone(); - client - .observer - .lock() - .await - .wait_for_matching_events(&self.event, self.count, entities) - .await - .unwrap(); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct RecordTopologyDescription { - id: String, - client: String, -} - -impl TestOperation for RecordTopologyDescription { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let client = test_runner.get_client(&self.client).await; - let description = client.topology_description(); - test_runner.insert_entity(&self.id, description).await; - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct AssertTopologyType { - topology_description: String, - topology_type: TopologyType, -} - -impl TestOperation for AssertTopologyType { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async { - let td = test_runner - .get_topology_description(&self.topology_description) - .await; - assert_eq!(td.topology_type, self.topology_type); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct WaitForPrimaryChange { - client: String, - prior_topology_description: String, - #[serde(rename = "timeoutMS")] - timeout_ms: Option, -} - -impl TestOperation for WaitForPrimaryChange { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - async move { - let client = test_runner.get_client(&self.client).await; - let td = test_runner - .get_topology_description(&self.prior_topology_description) - .await; - let old_primary = td.servers_with_type(&[ServerType::RsPrimary]).next(); - let timeout = Duration::from_millis(self.timeout_ms.unwrap_or(10_000)); - - runtime::timeout(timeout, async { - let mut watcher = client.topology().watch(); - - loop { - let latest = watcher.observe_latest(); - if let Some(primary) = latest.description.primary() { - if Some(primary) != old_primary { - return; - } - } - watcher.wait_for_update(None).await; - } - }) - .await - .unwrap(); - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Wait { - ms: u64, -} - -impl TestOperation for Wait { - fn execute_test_runner_operation<'a>( - &'a self, - _test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - runtime::delay_for(Duration::from_millis(self.ms)).boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CreateEntities { - entities: Vec, -} - -impl TestOperation for CreateEntities { - fn execute_test_runner_operation<'a>( - &'a self, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, ()> { - test_runner - .populate_entity_map(&self.entities[..], "createEntities operation") - .boxed() + ) -> BoxFuture<'a, ()> { + test_runner + .populate_entity_map(&self.entities[..], "createEntities operation") + .boxed() } + #[cfg(feature = "tracing-unstable")] fn test_file_entities(&self) -> Option<&Vec> { Some(&self.entities) } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Download { - id: Bson, -} - -impl TestOperation for Download { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let bucket = test_runner.get_bucket(id).await; - - // First, read via the download_to_writer API. - let mut buf: Vec = vec![]; - bucket - .download_to_futures_0_3_writer(self.id.clone(), &mut buf) - .await?; - let writer_data = hex::encode(buf); - - // Next, read via the open_download_stream API. - let mut buf: Vec = vec![]; - let mut stream = bucket.open_download_stream(self.id.clone()).await?; - stream.read_to_end(&mut buf).await?; - let stream_data = hex::encode(buf); - - // Assert that both APIs returned the same data. - assert_eq!(writer_data, stream_data); - - Ok(Some(Entity::Bson(writer_data.into()))) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct DownloadByName { - filename: String, - #[serde(flatten)] - options: GridFsDownloadByNameOptions, -} - -impl TestOperation for DownloadByName { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let bucket = test_runner.get_bucket(id).await; - - // First, read via the download_to_writer API. - let mut buf: Vec = vec![]; - bucket - .download_to_futures_0_3_writer_by_name( - self.filename.clone(), - &mut buf, - self.options.clone(), - ) - .await?; - let writer_data = hex::encode(buf); - - // Next, read via the open_download_stream API. - let mut buf: Vec = vec![]; - let mut stream = bucket - .open_download_stream_by_name(self.filename.clone(), self.options.clone()) - .await?; - stream.read_to_end(&mut buf).await?; - let stream_data = hex::encode(buf); - - // Assert that both APIs returned the same data. - assert_eq!(writer_data, stream_data); - - Ok(Some(Entity::Bson(writer_data.into()))) - } - .boxed() - } -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Delete { - id: Bson, -} - -impl TestOperation for Delete { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let bucket = test_runner.get_bucket(id).await; - bucket.delete(self.id.clone()).await?; - Ok(None) - } - .boxed() - } -} -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct Upload { - source: Document, - filename: String, - // content_type and disableMD5 are deprecated and no longer supported. - // Options included for deserialization. - #[serde(rename = "contentType")] - _content_type: Option, - #[serde(rename = "disableMD5")] - _disable_md5: Option, - #[serde(flatten)] - options: GridFsUploadOptions, -} - -impl TestOperation for Upload { - fn execute_entity_operation<'a>( - &'a self, - id: &'a str, - test_runner: &'a TestRunner, - ) -> BoxFuture<'a, Result>> { - async move { - let bucket = test_runner.get_bucket(id).await; - let hex_string = self.source.get("$$hexBytes").unwrap().as_str().unwrap(); - let bytes = hex::decode(hex_string).unwrap(); - - let id = bucket - .upload_from_futures_0_3_reader( - self.filename.clone(), - &bytes[..], - self.options.clone(), - ) - .await?; - - Ok(Some(Entity::Bson(id.into()))) - } - .boxed() - } -} - #[derive(Debug, Deserialize)] pub(super) struct UnimplementedOperation { _name: String, diff --git a/src/test/spec/unified_runner/operation/bulk_write.rs b/src/test/spec/unified_runner/operation/bulk_write.rs new file mode 100644 index 000000000..7b5219d10 --- /dev/null +++ b/src/test/spec/unified_runner/operation/bulk_write.rs @@ -0,0 +1,84 @@ +use futures_core::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +use crate::{ + bson_compat::serialize_to_bson, + error::Result, + options::{ + BulkWriteOptions, + DeleteManyModel, + DeleteOneModel, + InsertOneModel, + ReplaceOneModel, + UpdateManyModel, + UpdateOneModel, + WriteModel, + }, + test::spec::unified_runner::{Entity, TestRunner}, +}; + +use super::{with_mut_session, with_opt_session, TestOperation}; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct BulkWrite { + session: Option, + models: Vec, + verbose_results: Option, + #[serde(flatten)] + options: BulkWriteOptions, +} + +impl<'de> Deserialize<'de> for WriteModel { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + enum WriteModelHelper { + InsertOne(InsertOneModel), + UpdateOne(UpdateOneModel), + UpdateMany(UpdateManyModel), + ReplaceOne(ReplaceOneModel), + DeleteOne(DeleteOneModel), + DeleteMany(DeleteManyModel), + } + + match WriteModelHelper::deserialize(deserializer)? { + WriteModelHelper::InsertOne(model) => Ok(Self::InsertOne(model)), + WriteModelHelper::UpdateOne(model) => Ok(Self::UpdateOne(model)), + WriteModelHelper::UpdateMany(model) => Ok(Self::UpdateMany(model)), + WriteModelHelper::ReplaceOne(model) => Ok(Self::ReplaceOne(model)), + WriteModelHelper::DeleteOne(model) => Ok(Self::DeleteOne(model)), + WriteModelHelper::DeleteMany(model) => Ok(Self::DeleteMany(model)), + } + } +} + +impl TestOperation for BulkWrite { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let client = test_runner.get_client(id).await; + let action = client + .bulk_write(self.models.clone()) + .with_options(self.options.clone()); + let result = if let Some(true) = self.verbose_results { + with_opt_session!(test_runner, &self.session, action.verbose_results()) + .await + .and_then(|result| Ok(serialize_to_bson(&result)?)) + } else { + with_opt_session!(test_runner, &self.session, action) + .await + .and_then(|result| Ok(serialize_to_bson(&result)?)) + }?; + Ok(Some(result.into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/collection.rs b/src/test/spec/unified_runner/operation/collection.rs new file mode 100644 index 000000000..ec4893191 --- /dev/null +++ b/src/test/spec/unified_runner/operation/collection.rs @@ -0,0 +1,199 @@ +use crate::{ + bson::{doc, Bson, Document}, + error::Result, + options::{AggregateOptions, CreateCollectionOptions, DropCollectionOptions}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, + Collection, + Database, +}; +use futures::{future::BoxFuture, TryStreamExt}; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertCollectionExists { + collection_name: String, + database_name: String, +} + +impl TestOperation for AssertCollectionExists { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let db = test_runner.internal_client.database(&self.database_name); + let names = db.list_collection_names().await.unwrap(); + assert!(names.contains(&self.collection_name)); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertCollectionNotExists { + collection_name: String, + database_name: String, +} + +impl TestOperation for AssertCollectionNotExists { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let db = test_runner.internal_client.database(&self.database_name); + let names = db.list_collection_names().await.unwrap(); + assert!(!names.contains(&self.collection_name)); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CreateCollection { + collection: String, + #[serde(flatten)] + options: CreateCollectionOptions, + session: Option, +} + +impl TestOperation for CreateCollection { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let database = test_runner.get_database(id).await; + with_opt_session!( + test_runner, + &self.session, + database + .create_collection(&self.collection) + .with_options(self.options.clone()), + ) + .await?; + Ok(Some(Entity::Collection( + database.collection(&self.collection), + ))) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DropCollection { + collection: String, + #[serde(flatten)] + options: DropCollectionOptions, + session: Option, +} + +impl TestOperation for DropCollection { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let database = test_runner.get_database(id).await; + let collection = database.collection::(&self.collection).clone(); + with_opt_session!( + test_runner, + &self.session, + collection.drop().with_options(self.options.clone()), + ) + .await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +pub(super) struct Aggregate { + pipeline: Vec, + session: Option, + #[serde(flatten)] + options: AggregateOptions, +} + +impl TestOperation for Aggregate { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let result = match &self.session { + Some(session_id) => { + enum AggregateEntity { + Collection(Collection), + Database(Database), + Other(String), + } + let entity = match test_runner.entities.read().await.get(id).unwrap() { + Entity::Collection(c) => AggregateEntity::Collection(c.clone()), + Entity::Database(d) => AggregateEntity::Database(d.clone()), + other => AggregateEntity::Other(format!("{:?}", other)), + }; + with_mut_session!(test_runner, session_id, |session| async { + let mut cursor = match entity { + AggregateEntity::Collection(collection) => { + collection + .aggregate(self.pipeline.clone()) + .with_options(self.options.clone()) + .session(&mut *session) + .await? + } + AggregateEntity::Database(db) => { + db.aggregate(self.pipeline.clone()) + .with_options(self.options.clone()) + .session(&mut *session) + .await? + } + AggregateEntity::Other(debug) => { + panic!("Cannot execute aggregate on {}", &debug) + } + }; + cursor.stream(session).try_collect::>().await + }) + .await? + } + None => { + let entities = test_runner.entities.read().await; + let cursor = match entities.get(id).unwrap() { + Entity::Collection(collection) => { + collection + .aggregate(self.pipeline.clone()) + .with_options(self.options.clone()) + .await? + } + Entity::Database(db) => { + db.aggregate(self.pipeline.clone()) + .with_options(self.options.clone()) + .await? + } + other => panic!("Cannot execute aggregate on {:?}", &other), + }; + cursor.try_collect::>().await? + } + }; + Ok(Some(Bson::from(result).into())) + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + true + } +} diff --git a/src/test/spec/unified_runner/operation/command.rs b/src/test/spec/unified_runner/operation/command.rs new file mode 100644 index 000000000..1ce5734c1 --- /dev/null +++ b/src/test/spec/unified_runner/operation/command.rs @@ -0,0 +1,155 @@ +use crate::{ + action::Action, + bson::Document, + bson_compat::serialize_to_bson, + error::Result, + options::{RunCursorCommandOptions, SelectionCriteria}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestCursor, + TestRunner, + }, +}; +use futures::{future::BoxFuture, TryStreamExt}; +use futures_util::FutureExt; +use serde::Deserialize; +use tokio::sync::Mutex; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct RunCommand { + command: Document, + // We don't need to use this field, but it needs to be included during deserialization so that + // we can use the deny_unknown_fields tag. + #[serde(rename = "commandName")] + _command_name: String, + read_preference: Option, + session: Option, +} + +impl TestOperation for RunCommand { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let command = self.command.clone(); + + let db = test_runner.get_database(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + db.run_command(command) + .optional(self.read_preference.clone(), |a, rp| { + a.selection_criteria(rp) + }), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct RunCursorCommand { + command: Document, + // We don't need to use this field, but it needs to be included during deserialization so that + // we can use the deny_unknown_fields tag. + #[serde(rename = "commandName")] + _command_name: String, + + #[serde(flatten)] + options: RunCursorCommandOptions, + session: Option, +} + +impl TestOperation for RunCursorCommand { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let command = self.command.clone(); + let db = test_runner.get_database(id).await; + let options = self.options.clone(); + + let action = db.run_cursor_command(command).with_options(options); + let result = match &self.session { + Some(session_id) => { + with_mut_session!(test_runner, session_id, |session| async { + let mut cursor = action.session(&mut *session).await?; + cursor.stream(session).try_collect::>().await + }) + .await? + } + None => { + let cursor = action.await?; + cursor.try_collect::>().await? + } + }; + + Ok(Some(crate::bson_compat::serialize_to_bson(&result)?.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct CreateCommandCursor { + command: Document, + // We don't need to use this field, but it needs to be included during deserialization so that + // we can use the deny_unknown_fields tag. + #[serde(rename = "commandName")] + _command_name: String, + + #[serde(flatten)] + options: RunCursorCommandOptions, + session: Option, +} + +impl TestOperation for CreateCommandCursor { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let command = self.command.clone(); + let db = test_runner.get_database(id).await; + let options = self.options.clone(); + + let action = db.run_cursor_command(command).with_options(options); + match &self.session { + Some(session_id) => { + let mut ses_cursor = None; + with_mut_session!(test_runner, session_id, |session| async { + ses_cursor = Some(action.session(session).await); + }) + .await; + let test_cursor = TestCursor::Session { + cursor: ses_cursor.unwrap().unwrap(), + session_id: session_id.clone(), + }; + Ok(Some(Entity::Cursor(test_cursor))) + } + None => { + let doc_cursor = action.await?; + let test_cursor = TestCursor::Normal(Mutex::new(doc_cursor)); + Ok(Some(Entity::Cursor(test_cursor))) + } + } + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + false + } +} diff --git a/src/test/spec/unified_runner/operation/connection.rs b/src/test/spec/unified_runner/operation/connection.rs new file mode 100644 index 000000000..68d6d1873 --- /dev/null +++ b/src/test/spec/unified_runner/operation/connection.rs @@ -0,0 +1,93 @@ +use crate::{ + error::Result, + test::spec::unified_runner::{operation::TestOperation, Entity, TestCursor, TestRunner}, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Close {} + +impl TestOperation for Close { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let mut entities = test_runner.entities.write().await; + let target_entity = entities.get(id).unwrap(); + match target_entity { + Entity::Client(_) => { + let client = entities.get_mut(id).unwrap().as_mut_client(); + let closed_client_topology_id = client.topology_id; + client + .client + .take() + .unwrap() + .shutdown() + .immediate(true) + .await; + + let mut entities_to_remove = vec![]; + for (key, value) in entities.iter() { + match value { + // skip clients so that we don't remove the client entity itself from + // the map: we want to preserve it so we can + // access the other data stored on the entity. + Entity::Client(_) => {} + _ => { + if value.client_topology_id().await + == Some(closed_client_topology_id) + { + entities_to_remove.push(key.clone()); + } + } + } + } + for entity_id in entities_to_remove { + entities.remove(&entity_id); + } + + Ok(None) + } + Entity::Cursor(_) => { + let cursor = entities.get_mut(id).unwrap().as_mut_cursor(); + let rx = cursor.make_kill_watcher().await; + *cursor = TestCursor::Closed; + drop(entities); + let _ = rx.await; + Ok(None) + } + _ => panic!( + "Unsupported entity {:?} for close operation; expected Client or Cursor", + target_entity + ), + } + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertNumberConnectionsCheckedOut { + client: String, + connections: u32, +} + +impl TestOperation for AssertNumberConnectionsCheckedOut { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let client = test_runner.get_client(&self.client).await; + client.sync_workers().await; + assert_eq!(client.connections_checked_out(), self.connections); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/count.rs b/src/test/spec/unified_runner/operation/count.rs new file mode 100644 index 000000000..c4d31aba5 --- /dev/null +++ b/src/test/spec/unified_runner/operation/count.rs @@ -0,0 +1,132 @@ +use crate::{ + bson::{Bson, Document}, + error::Result, + options::{CountOptions, DistinctOptions, EstimatedDocumentCountOptions}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + ExpectedEvent, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Distinct { + field_name: String, + filter: Option, + session: Option, + #[serde(flatten)] + options: DistinctOptions, +} + +impl TestOperation for Distinct { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .distinct(&self.field_name, self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()), + ) + .await?; + Ok(Some(Bson::Array(result).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CountDocuments { + session: Option, + filter: Option, + #[serde(flatten)] + options: CountOptions, +} + +impl TestOperation for CountDocuments { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .count_documents(self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()), + ) + .await?; + Ok(Some(Bson::Int64(result.try_into().unwrap()).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct EstimatedDocumentCount { + #[serde(flatten)] + options: EstimatedDocumentCountOptions, +} + +impl TestOperation for EstimatedDocumentCount { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = collection + .estimated_document_count() + .with_options(self.options.clone()) + .await?; + Ok(Some(Bson::Int64(result.try_into().unwrap()).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertEventCount { + client: String, + event: ExpectedEvent, + count: usize, +} + +impl TestOperation for AssertEventCount { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let client = test_runner.get_client(self.client.as_str()).await; + let entities = test_runner.entities.read().await; + let actual_events = client.matching_events(&self.event, &entities); + assert_eq!( + actual_events.len(), + self.count, + "expected to see {} events matching: {:#?}, instead saw: {:#?}", + self.count, + self.event, + actual_events + ); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/csfle.rs b/src/test/spec/unified_runner/operation/csfle.rs index 64363b14a..04c01cf45 100644 --- a/src/test/spec/unified_runner/operation/csfle.rs +++ b/src/test/spec/unified_runner/operation/csfle.rs @@ -1,13 +1,15 @@ use std::fmt::Debug; use futures::{future::BoxFuture, stream::TryStreamExt, FutureExt}; +use mongocrypt::ctx::Algorithm; use serde::Deserialize; use super::{Entity, TestOperation, TestRunner}; use crate::{ - bson::{doc, Bson}, - client_encryption::{DataKeyOptions, MasterKey}, + action::csfle::DataKeyOptions, + bson::{doc, Binary, Bson, RawBson}, + client_encryption::{LocalMasterKey, MasterKey}, error::Result, }; @@ -39,7 +41,7 @@ impl TestOperation for GetKeyByAltName { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(super) struct DeleteKey { - id: bson::Binary, + id: crate::bson::Binary, } impl TestOperation for DeleteKey { @@ -51,9 +53,9 @@ impl TestOperation for DeleteKey { async move { let ce = test_runner.get_client_encryption(id).await; let result = ce.delete_key(&self.id).await?; - Ok(Some(Entity::Bson(Bson::Document(bson::to_document( - &result, - )?)))) + Ok(Some(Entity::Bson(Bson::Document( + crate::bson_compat::serialize_to_document(&result)?, + )))) } .boxed() } @@ -62,7 +64,7 @@ impl TestOperation for DeleteKey { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(super) struct GetKey { - id: bson::Binary, + id: crate::bson::Binary, } impl TestOperation for GetKey { @@ -86,7 +88,7 @@ impl TestOperation for GetKey { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(super) struct AddKeyAltName { - id: bson::Binary, + id: crate::bson::Binary, key_alt_name: String, } @@ -108,34 +110,48 @@ impl TestOperation for AddKeyAltName { } } -impl<'de> Deserialize<'de> for DataKeyOptions { +#[derive(Debug)] +pub(super) struct CreateDataKey { + kms_provider: mongocrypt::ctx::KmsProvider, + master_key: MasterKey, + opts: Option, +} + +impl<'de> Deserialize<'de> for CreateDataKey { fn deserialize(deserializer: D) -> std::result::Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] - struct Helper { + struct TestOptions { master_key: Option, key_alt_names: Option>, - key_material: Option, + key_material: Option, + } + #[derive(Deserialize)] + #[serde(rename_all = "camelCase", deny_unknown_fields)] + struct TestOp { + kms_provider: mongocrypt::ctx::KmsProvider, + opts: Option, } - let h = Helper::deserialize(deserializer)?; - Ok(DataKeyOptions { - master_key: h.master_key.unwrap_or(MasterKey::Local), - key_alt_names: h.key_alt_names, - key_material: h.key_material.map(|bin| bin.bytes), + let t_op = TestOp::deserialize(deserializer)?; + Ok(CreateDataKey { + kms_provider: t_op.kms_provider, + master_key: t_op + .opts + .as_ref() + .and_then(|o| o.master_key.as_ref()) + .cloned() + .unwrap_or(LocalMasterKey::builder().build().into()), + opts: t_op.opts.map(|to| DataKeyOptions { + key_alt_names: to.key_alt_names, + key_material: to.key_material.map(|bin| bin.bytes), + }), }) } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub(super) struct CreateDataKey { - kms_provider: mongocrypt::ctx::KmsProvider, - opts: Option, -} - impl TestOperation for CreateDataKey { fn execute_entity_operation<'a>( &'a self, @@ -145,7 +161,9 @@ impl TestOperation for CreateDataKey { async move { let ce = test_runner.get_client_encryption(id).await; let key = ce - .create_data_key_final(&self.kms_provider, self.opts.clone()) + .create_data_key(self.master_key.clone()) + .with_options(self.opts.clone()) + .test_kms_provider(self.kms_provider.clone()) .await?; Ok(Some(Entity::Bson(Bson::Binary(key)))) } @@ -179,7 +197,7 @@ impl TestOperation for GetKeys { #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(super) struct RemoveKeyAltName { - id: bson::Binary, + id: crate::bson::Binary, key_alt_name: String, } @@ -200,3 +218,70 @@ impl TestOperation for RemoveKeyAltName { .boxed() } } + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Encrypt { + value: RawBson, + opts: EncryptOptions, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct EncryptOptions { + key_alt_name: String, + algorithm: String, +} + +fn algorithm_from_string(algorithm: &str) -> Algorithm { + match algorithm { + "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" => Algorithm::Deterministic, + other => panic!("unsupported encrypt algorithm: {}", other), + } +} + +impl TestOperation for Encrypt { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let client_encryption = test_runner.get_client_encryption(id).await; + let algorithm = algorithm_from_string(self.opts.algorithm.as_str()); + + let encrypted_value = client_encryption + .encrypt( + self.value.clone(), + self.opts.key_alt_name.clone(), + algorithm, + ) + .await?; + + Ok(Some(Bson::Binary(encrypted_value).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Decrypt { + value: Binary, +} + +impl TestOperation for Decrypt { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let client_encryption = test_runner.get_client_encryption(id).await; + let raw_value = self.value.as_raw_binary(); + let decrypted_value: Bson = client_encryption.decrypt(raw_value).await?.try_into()?; + Ok(Some(decrypted_value.into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/delete.rs b/src/test/spec/unified_runner/operation/delete.rs new file mode 100644 index 000000000..3f2f4ce4d --- /dev/null +++ b/src/test/spec/unified_runner/operation/delete.rs @@ -0,0 +1,78 @@ +use crate::{ + bson::Document, + bson_compat::serialize_to_bson, + error::Result, + options::DeleteOptions, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DeleteMany { + filter: Document, + session: Option, + #[serde(flatten)] + options: DeleteOptions, +} + +impl TestOperation for DeleteMany { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .delete_many(self.filter.clone()) + .with_options(self.options.clone()) + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DeleteOne { + filter: Document, + session: Option, + #[serde(flatten)] + options: DeleteOptions, +} + +impl TestOperation for DeleteOne { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .delete_one(self.filter.clone()) + .with_options(self.options.clone()), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/failpoint.rs b/src/test/spec/unified_runner/operation/failpoint.rs new file mode 100644 index 000000000..b2fb6a59e --- /dev/null +++ b/src/test/spec/unified_runner/operation/failpoint.rs @@ -0,0 +1,72 @@ +use crate::test::{ + spec::unified_runner::{ + operation::{with_mut_session, TestOperation}, + Entity, + TestRunner, + }, + util::fail_point::FailPoint, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct FailPointCommand { + fail_point: FailPoint, + client: String, +} + +impl TestOperation for FailPointCommand { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let client = test_runner.get_client(&self.client).await; + let guard = client + .enable_fail_point(self.fail_point.clone()) + .await + .unwrap(); + test_runner.fail_point_guards.write().await.push(guard); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct TargetedFailPoint { + fail_point: FailPoint, + session: String, +} + +impl TestOperation for TargetedFailPoint { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let selection_criteria = + with_mut_session!(test_runner, self.session.as_str(), |session| async { + session + .transaction + .pinned_mongos() + .cloned() + .unwrap_or_else(|| panic!("ClientSession not pinned")) + }) + .await; + let guard = test_runner + .internal_client + .enable_fail_point( + self.fail_point + .clone() + .selection_criteria(selection_criteria), + ) + .await + .unwrap(); + test_runner.fail_point_guards.write().await.push(guard); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/find.rs b/src/test/spec/unified_runner/operation/find.rs new file mode 100644 index 000000000..a638a191e --- /dev/null +++ b/src/test/spec/unified_runner/operation/find.rs @@ -0,0 +1,367 @@ +use std::time::Duration; + +use crate::{ + bson::{Bson, Document}, + bson_compat::serialize_to_bson, + error::Result, + options::{ + Collation, + FindOneAndDeleteOptions, + FindOneAndReplaceOptions, + FindOneAndUpdateOptions, + FindOneOptions, + FindOptions, + Hint, + ReadConcern, + UpdateModifications, + }, + serde_util, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestCursor, + TestRunner, + }, +}; +use futures::{future::BoxFuture, TryStreamExt}; +use futures_util::FutureExt; +use serde::{Deserialize, Deserializer}; +use tokio::sync::Mutex; + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Find { + filter: Document, + session: Option, + // `FindOptions` cannot be embedded directly because serde doesn't support combining `flatten` + // and `deny_unknown_fields`, so its fields are replicated here. + allow_disk_use: Option, + allow_partial_results: Option, + batch_size: Option, + comment: Option, + hint: Option, + limit: Option, + max: Option, + max_scan: Option, + #[serde( + default, + rename = "maxTimeMS", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" + )] + max_time: Option, + min: Option, + no_cursor_timeout: Option, + projection: Option, + read_concern: Option, + return_key: Option, + show_record_id: Option, + skip: Option, + sort: Option, + collation: Option, + #[serde(rename = "let")] + let_vars: Option, +} + +impl Find { + async fn get_cursor<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> Result { + let collection = test_runner.get_collection(id).await; + + // `FindOptions` is constructed without the use of `..Default::default()` to enforce at + // compile-time that any new fields added there need to be considered here. + let options = FindOptions { + allow_disk_use: self.allow_disk_use, + allow_partial_results: self.allow_partial_results, + batch_size: self.batch_size, + comment: self.comment.clone(), + hint: self.hint.clone(), + limit: self.limit, + max: self.max.clone(), + max_scan: self.max_scan, + max_time: self.max_time, + min: self.min.clone(), + no_cursor_timeout: self.no_cursor_timeout, + projection: self.projection.clone(), + read_concern: self.read_concern.clone(), + return_key: self.return_key, + show_record_id: self.show_record_id, + skip: self.skip, + sort: self.sort.clone(), + collation: self.collation.clone(), + cursor_type: None, + max_await_time: None, + selection_criteria: None, + let_vars: self.let_vars.clone(), + }; + let act = collection.find(self.filter.clone()).with_options(options); + match &self.session { + Some(session_id) => { + let cursor = with_mut_session!(test_runner, session_id, |session| async { + act.session(session).await + }) + .await?; + Ok(TestCursor::Session { + cursor, + session_id: session_id.clone(), + }) + } + None => { + let cursor = act.await?; + Ok(TestCursor::Normal(Mutex::new(cursor))) + } + } + } +} + +impl TestOperation for Find { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let result = match self.get_cursor(id, test_runner).await? { + TestCursor::Session { + mut cursor, + session_id, + } => { + with_mut_session!(test_runner, session_id.as_str(), |s| async { + cursor.stream(s).try_collect::>().await + }) + .await? + } + TestCursor::Normal(cursor) => { + let cursor = cursor.into_inner(); + cursor.try_collect::>().await? + } + TestCursor::ChangeStream(_) => panic!("get_cursor returned a change stream"), + TestCursor::Closed => panic!("get_cursor returned a closed cursor"), + }; + Ok(Some(Bson::from(result).into())) + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + true + } +} + +#[derive(Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CreateFindCursor { + // `Find` cannot be embedded directly because serde doesn't support combining `flatten` + // and `deny_unknown_fields`, so its fields are replicated here. + filter: Document, + session: Option, + allow_disk_use: Option, + allow_partial_results: Option, + batch_size: Option, + comment: Option, + hint: Option, + limit: Option, + max: Option, + max_scan: Option, + #[serde(rename = "maxTimeMS")] + max_time: Option, + min: Option, + no_cursor_timeout: Option, + projection: Option, + read_concern: Option, + return_key: Option, + show_record_id: Option, + skip: Option, + sort: Option, + collation: Option, + #[serde(rename = "let")] + let_vars: Option, +} + +impl TestOperation for CreateFindCursor { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let find = Find { + filter: self.filter.clone(), + session: self.session.clone(), + allow_disk_use: self.allow_disk_use, + allow_partial_results: self.allow_partial_results, + batch_size: self.batch_size, + comment: self.comment.clone(), + hint: self.hint.clone(), + limit: self.limit, + max: self.max.clone(), + max_scan: self.max_scan, + max_time: self.max_time, + min: self.min.clone(), + no_cursor_timeout: self.no_cursor_timeout, + projection: self.projection.clone(), + read_concern: self.read_concern.clone(), + return_key: self.return_key, + show_record_id: self.show_record_id, + skip: self.skip, + sort: self.sort.clone(), + collation: self.collation.clone(), + let_vars: self.let_vars.clone(), + }; + let cursor = find.get_cursor(id, test_runner).await?; + Ok(Some(Entity::Cursor(cursor))) + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + false + } +} + +#[derive(Debug, Default)] +pub(super) struct FindOne { + filter: Option, + options: FindOneOptions, +} + +// TODO RUST-1364: remove this impl and derive Deserialize instead +impl<'de> Deserialize<'de> for FindOne { + fn deserialize>(deserializer: D) -> std::result::Result { + #[derive(Deserialize)] + struct Helper { + filter: Option, + #[serde(flatten)] + options: FindOneOptions, + } + + let helper = Helper::deserialize(deserializer)?; + + Ok(Self { + filter: helper.filter, + options: helper.options, + }) + } +} + +impl TestOperation for FindOne { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = collection + .find_one(self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()) + .await?; + match result { + Some(result) => Ok(Some(Bson::from(result).into())), + None => Ok(Some(Entity::None)), + } + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct FindOneAndUpdate { + filter: Document, + update: UpdateModifications, + session: Option, + #[serde(flatten)] + options: FindOneAndUpdateOptions, +} + +impl TestOperation for FindOneAndUpdate { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .find_one_and_update(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct FindOneAndReplace { + filter: Document, + replacement: Document, + session: Option, + #[serde(flatten)] + options: FindOneAndReplaceOptions, +} + +impl TestOperation for FindOneAndReplace { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .find_one_and_replace(self.filter.clone(), self.replacement.clone()) + .with_options(self.options.clone()) + ) + .await?; + let result = serialize_to_bson(&result)?; + + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct FindOneAndDelete { + filter: Document, + session: Option, + #[serde(flatten)] + options: FindOneAndDeleteOptions, +} + +impl TestOperation for FindOneAndDelete { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .find_one_and_delete(self.filter.clone()) + .with_options(self.options.clone()) + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/gridfs.rs b/src/test/spec/unified_runner/operation/gridfs.rs new file mode 100644 index 000000000..684b71470 --- /dev/null +++ b/src/test/spec/unified_runner/operation/gridfs.rs @@ -0,0 +1,172 @@ +use crate::{ + bson::{Bson, Document}, + error::Result, + gridfs::{GridFsDownloadByNameOptions, GridFsUploadOptions}, + test::spec::unified_runner::{operation::TestOperation, Entity, TestRunner}, +}; +use futures::{future::BoxFuture, AsyncReadExt, AsyncWriteExt}; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Download { + id: Bson, +} + +impl TestOperation for Download { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + + let mut buf: Vec = vec![]; + bucket + .open_download_stream(self.id.clone()) + .await? + .read_to_end(&mut buf) + .await?; + let writer_data = hex::encode(buf); + + Ok(Some(Entity::Bson(writer_data.into()))) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DownloadByName { + filename: String, + #[serde(flatten)] + options: GridFsDownloadByNameOptions, +} + +impl TestOperation for DownloadByName { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + + let mut buf: Vec = vec![]; + bucket + .open_download_stream_by_name(&self.filename) + .with_options(self.options.clone()) + .await? + .read_to_end(&mut buf) + .await?; + let writer_data = hex::encode(buf); + + Ok(Some(Entity::Bson(writer_data.into()))) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Delete { + id: Bson, +} + +impl TestOperation for Delete { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + bucket.delete(self.id.clone()).await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DeleteByName { + filename: String, +} + +impl TestOperation for DeleteByName { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + bucket.delete_by_name(&self.filename).await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Upload { + source: Document, + filename: String, + #[serde(flatten)] + options: GridFsUploadOptions, +} + +impl TestOperation for Upload { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + let hex_string = self.source.get("$$hexBytes").unwrap().as_str().unwrap(); + let bytes = hex::decode(hex_string).unwrap(); + + let id = { + let mut stream = bucket + .open_upload_stream(&self.filename) + .with_options(self.options.clone()) + .await?; + stream.write_all(&bytes[..]).await?; + stream.close().await?; + stream.id().clone() + }; + + Ok(Some(Entity::Bson(id))) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct RenameByName { + filename: String, + new_filename: String, +} + +impl TestOperation for RenameByName { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let bucket = test_runner.get_bucket(id).await; + bucket + .rename_by_name(&self.filename, &self.new_filename) + .await?; + Ok(None) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/index.rs b/src/test/spec/unified_runner/operation/index.rs new file mode 100644 index 000000000..d5ad6cba2 --- /dev/null +++ b/src/test/spec/unified_runner/operation/index.rs @@ -0,0 +1,192 @@ +use crate::{ + bson::{Bson, Document}, + error::Result, + options::{DropIndexOptions, IndexOptions, ListIndexesOptions}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, + IndexModel, +}; +use futures::{future::BoxFuture, TryStreamExt}; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(super) struct CreateIndex { + session: Option, + keys: Document, + name: Option, +} + +impl TestOperation for CreateIndex { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let options = IndexOptions::builder().name(self.name.clone()).build(); + let index = IndexModel::builder() + .keys(self.keys.clone()) + .options(options) + .build(); + + let collection = test_runner.get_collection(id).await; + let name = + with_opt_session!(test_runner, &self.session, collection.create_index(index)) + .await? + .index_name; + Ok(Some(Bson::String(name).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +pub(super) struct ListIndexes { + session: Option, + #[serde(flatten)] + options: ListIndexesOptions, +} + +impl TestOperation for ListIndexes { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let act = collection.list_indexes().with_options(self.options.clone()); + let indexes: Vec = match self.session { + Some(ref session) => { + with_mut_session!(test_runner, session, |session| { + async { + act.session(&mut *session) + .await? + .stream(session) + .try_collect() + .await + } + }) + .await? + } + None => act.await?.try_collect().await?, + }; + let indexes: Vec = indexes + .iter() + .map(|index| crate::bson_compat::serialize_to_document(index).unwrap()) + .collect(); + Ok(Some(Bson::from(indexes).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +pub(super) struct ListIndexNames { + session: Option, +} + +impl TestOperation for ListIndexNames { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let names = + with_opt_session!(test_runner, &self.session, collection.list_index_names(),) + .await?; + Ok(Some(Bson::from(names).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertIndexExists { + collection_name: String, + database_name: String, + index_name: String, +} + +impl TestOperation for AssertIndexExists { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let coll = test_runner + .internal_client + .database(&self.database_name) + .collection::(&self.collection_name); + let names = coll.list_index_names().await.unwrap(); + assert!(names.contains(&self.index_name)); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertIndexNotExists { + collection_name: String, + database_name: String, + index_name: String, +} + +impl TestOperation for AssertIndexNotExists { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let coll = test_runner + .internal_client + .database(&self.database_name) + .collection::(&self.collection_name); + match coll.list_index_names().await { + Ok(indexes) => assert!(!indexes.contains(&self.index_name)), + // a namespace not found error indicates that the index does not exist + Err(err) => assert_eq!(err.sdam_code(), Some(26)), + } + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +pub(super) struct DropIndex { + session: Option, + name: String, + #[serde(flatten)] + options: Option, +} + +impl TestOperation for DropIndex { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + with_opt_session!( + test_runner, + &self.session, + collection + .drop_index(&self.name) + .with_options(self.options.clone()) + ) + .await?; + Ok(None) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/insert.rs b/src/test/spec/unified_runner/operation/insert.rs new file mode 100644 index 000000000..da9cbafc5 --- /dev/null +++ b/src/test/spec/unified_runner/operation/insert.rs @@ -0,0 +1,87 @@ +use std::collections::HashMap; + +use crate::{ + bson::{doc, Bson, Document}, + bson_compat::serialize_to_bson, +}; +use serde::Deserialize; + +use crate::{ + error::Result, + options::{InsertManyOptions, InsertOneOptions}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct InsertOne { + document: Document, + session: Option, + #[serde(flatten)] + options: InsertOneOptions, +} +impl TestOperation for InsertOne { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .insert_one(self.document.clone()) + .with_options(self.options.clone()), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct InsertMany { + documents: Vec, + session: Option, + #[serde(flatten)] + options: InsertManyOptions, +} + +impl TestOperation for InsertMany { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .insert_many(&self.documents) + .with_options(self.options.clone()), + ) + .await?; + let ids: HashMap = result + .inserted_ids + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(); + let ids = serialize_to_bson(&ids)?; + Ok(Some(Bson::from(doc! { "insertedIds": ids }).into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/iteration.rs b/src/test/spec/unified_runner/operation/iteration.rs new file mode 100644 index 000000000..9404dac74 --- /dev/null +++ b/src/test/spec/unified_runner/operation/iteration.rs @@ -0,0 +1,105 @@ +use crate::{ + bson::Bson, + error::Result, + test::spec::unified_runner::{operation::TestOperation, Entity, TestCursor, TestRunner}, +}; +use futures::{future::BoxFuture, StreamExt}; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct IterateOnce {} + +impl TestOperation for IterateOnce { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let mut cursor = test_runner.take_cursor(id).await; + match &mut cursor { + TestCursor::Normal(cursor) => { + let mut cursor = cursor.lock().await; + cursor.try_advance().await?; + } + TestCursor::Session { cursor, session_id } => { + cursor + .try_advance( + test_runner + .entities + .write() + .await + .get_mut(session_id) + .unwrap() + .as_mut_session(), + ) + .await?; + } + TestCursor::ChangeStream(change_stream) => { + let mut change_stream = change_stream.lock().await; + change_stream.next_if_any().await?; + } + TestCursor::Closed => panic!("Attempted to call IterateOnce on a closed cursor"), + } + test_runner.return_cursor(id, cursor).await; + Ok(None) + } + .boxed() + } +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct IterateUntilDocumentOrError {} + +impl TestOperation for IterateUntilDocumentOrError { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + // A `SessionCursor` also requires a `&mut Session`, which would cause conflicting + // borrows, so take the cursor from the map and return it after execution instead. + let mut cursor = test_runner.take_cursor(id).await; + let next = match &mut cursor { + TestCursor::Normal(cursor) => { + let mut cursor = cursor.lock().await; + cursor.next().await + } + TestCursor::Session { cursor, session_id } => { + cursor + .next( + test_runner + .entities + .write() + .await + .get_mut(session_id) + .unwrap() + .as_mut_session(), + ) + .await + } + TestCursor::ChangeStream(stream) => { + let mut stream = stream.lock().await; + stream.next().await.map(|res| { + res.map(|ev| match crate::bson_compat::serialize_to_bson(&ev) { + Ok(Bson::Document(doc)) => doc, + _ => panic!("invalid serialization result"), + }) + }) + } + TestCursor::Closed => None, + }; + test_runner.return_cursor(id, cursor).await; + next.transpose() + .map(|opt| opt.map(|doc| Entity::Bson(Bson::Document(doc)))) + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + true + } +} diff --git a/src/test/spec/unified_runner/operation/list.rs b/src/test/spec/unified_runner/operation/list.rs new file mode 100644 index 000000000..49eaf918b --- /dev/null +++ b/src/test/spec/unified_runner/operation/list.rs @@ -0,0 +1,139 @@ +use crate::{ + action::Action, + bson::{Bson, Document}, + error::Result, + options::ListCollectionsOptions, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, +}; +use futures::{future::BoxFuture, TryStreamExt}; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ListDatabases { + session: Option, + #[serde(flatten)] + options: crate::db::options::ListDatabasesOptions, +} + +impl TestOperation for ListDatabases { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let client = test_runner.get_client(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + client.list_databases().with_options(self.options.clone()), + ) + .await?; + Ok(Some(crate::bson_compat::serialize_to_bson(&result)?.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ListDatabaseNames { + #[serde(flatten)] + options: crate::db::options::ListDatabasesOptions, +} + +impl TestOperation for ListDatabaseNames { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let client = test_runner.get_client(id).await; + let result = client + .list_database_names() + .with_options(self.options.clone()) + .await?; + let result: Vec = result.iter().map(|s| Bson::String(s.to_string())).collect(); + Ok(Some(Bson::Array(result).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ListCollections { + session: Option, + #[serde(flatten)] + options: ListCollectionsOptions, +} + +impl TestOperation for ListCollections { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let db = test_runner.get_database(id).await; + let result = match &self.session { + Some(session_id) => { + with_mut_session!(test_runner, session_id, |session| async { + let mut cursor = db + .list_collections() + .with_options(self.options.clone()) + .session(&mut *session) + .await?; + cursor.stream(session).try_collect::>().await + }) + .await? + } + None => { + let cursor = db + .list_collections() + .with_options(self.options.clone()) + .await?; + cursor.try_collect::>().await? + } + }; + Ok(Some(crate::bson_compat::serialize_to_bson(&result)?.into())) + } + .boxed() + } + + fn returns_root_documents(&self) -> bool { + true + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ListCollectionNames { + filter: Option, +} + +impl TestOperation for ListCollectionNames { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let db = test_runner.get_database(id).await; + let result = db + .list_collection_names() + .optional(self.filter.clone(), |b, f| b.filter(f)) + .await?; + let result: Vec = result.iter().map(|s| Bson::String(s.to_string())).collect(); + Ok(Some(Bson::from(result).into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/rename.rs b/src/test/spec/unified_runner/operation/rename.rs new file mode 100644 index 000000000..c69971874 --- /dev/null +++ b/src/test/spec/unified_runner/operation/rename.rs @@ -0,0 +1,81 @@ +use crate::bson::{doc, Bson, Document}; +use futures::FutureExt; +use serde::Deserialize; + +use crate::{ + error::Result, + gridfs::GridFsBucket, + test::spec::unified_runner::{Entity, TestRunner}, + BoxFuture, + Collection, +}; + +use super::TestOperation; + +#[derive(Debug, Deserialize)] +#[serde(transparent)] +pub(super) struct Rename(Document); + +impl TestOperation for Rename { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + match test_runner.entities.read().await.get(id).unwrap() { + Entity::Collection(c) => { + let args: RenameCollection = + crate::bson_compat::deserialize_from_document(self.0.clone()).unwrap(); + args.run(c.clone(), test_runner).await + } + Entity::Bucket(b) => { + let args: RenameBucket = + crate::bson_compat::deserialize_from_document(self.0.clone()).unwrap(); + args.run(b.clone()).await + } + other => panic!("cannot execute rename on {:?}", other), + } + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct RenameCollection { + to: String, +} + +impl RenameCollection { + async fn run( + &self, + target: Collection, + test_runner: &TestRunner, + ) -> Result> { + let ns = target.namespace(); + let mut to_ns = ns.clone(); + to_ns.coll.clone_from(&self.to); + let cmd = doc! { + "renameCollection": crate::bson_compat::serialize_to_bson(&ns)?, + "to": crate::bson_compat::serialize_to_bson(&to_ns)?, + }; + let admin = test_runner.internal_client.database("admin"); + admin.run_command(cmd).await?; + Ok(None) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +struct RenameBucket { + id: Bson, + new_filename: String, +} + +impl RenameBucket { + async fn run(&self, target: GridFsBucket) -> Result> { + target.rename(self.id.clone(), &self.new_filename).await?; + Ok(None) + } +} diff --git a/src/test/spec/unified_runner/operation/search_index.rs b/src/test/spec/unified_runner/operation/search_index.rs new file mode 100644 index 000000000..a4ca4caf8 --- /dev/null +++ b/src/test/spec/unified_runner/operation/search_index.rs @@ -0,0 +1,160 @@ +use crate::{ + bson::{Bson, Document}, + bson_compat::serialize_to_bson, +}; +use futures_core::future::BoxFuture; +use futures_util::{FutureExt, TryStreamExt}; +use serde::Deserialize; + +use crate::{ + action::Action, + coll::options::AggregateOptions, + error::Result, + search_index::options::{ + CreateSearchIndexOptions, + DropSearchIndexOptions, + ListSearchIndexOptions, + UpdateSearchIndexOptions, + }, + test::spec::unified_runner::{Entity, TestRunner}, + SearchIndexModel, +}; + +use super::TestOperation; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CreateSearchIndex { + model: SearchIndexModel, + #[serde(flatten)] + options: CreateSearchIndexOptions, +} + +impl TestOperation for CreateSearchIndex { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let name = collection + .create_search_index(self.model.clone()) + .with_options(self.options.clone()) + .await?; + Ok(Some(Bson::String(name).into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CreateSearchIndexes { + models: Vec, + #[serde(flatten)] + options: CreateSearchIndexOptions, +} + +impl TestOperation for CreateSearchIndexes { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let names = collection + .create_search_indexes(self.models.clone()) + .with_options(self.options.clone()) + .await?; + Ok(Some(serialize_to_bson(&names)?.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct DropSearchIndex { + name: String, + #[serde(flatten)] + options: DropSearchIndexOptions, +} + +impl TestOperation for DropSearchIndex { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + collection + .drop_search_index(&self.name) + .with_options(self.options.clone()) + .await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ListSearchIndexes { + name: Option, + aggregation_options: Option, + #[serde(flatten)] + options: ListSearchIndexOptions, +} + +impl TestOperation for ListSearchIndexes { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let cursor = collection + .list_search_indexes() + .optional(self.name.clone(), |a, n| a.name(n)) + .optional(self.aggregation_options.clone(), |a, o| { + a.aggregate_options(o) + }) + .with_options(self.options.clone()) + .await?; + let values: Vec<_> = cursor.try_collect().await?; + Ok(Some(serialize_to_bson(&values)?.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct UpdateSearchIndex { + name: String, + definition: Document, + #[serde(flatten)] + options: UpdateSearchIndexOptions, +} + +impl TestOperation for UpdateSearchIndex { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + collection + .update_search_index(&self.name, self.definition.clone()) + .with_options(self.options.clone()) + .await?; + Ok(None) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/session.rs b/src/test/spec/unified_runner/operation/session.rs new file mode 100644 index 000000000..0eb18da4b --- /dev/null +++ b/src/test/spec/unified_runner/operation/session.rs @@ -0,0 +1,203 @@ +use crate::{ + client::session::TransactionState, + error::Result, + test::spec::unified_runner::{ + operation::{with_mut_session, TestOperation}, + Entity, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; +use std::time::Duration; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct EndSession {} + +impl TestOperation for EndSession { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + match test_runner.entities.write().await.get_mut(id) { + Some(Entity::Session(session)) => session.client_session.take(), + e => panic!("expected session for {:?}, got {:?}", id, e), + }; + tokio::time::sleep(Duration::from_secs(1)).await; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSessionTransactionState { + session: String, + state: String, +} + +impl TestOperation for AssertSessionTransactionState { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let session_state = + with_mut_session!(test_runner, self.session.as_str(), |session| async { + match &session.transaction.state { + TransactionState::None => "none", + TransactionState::Starting => "starting", + TransactionState::InProgress => "in_progress", + TransactionState::Committed { data_committed: _ } => "committed", + TransactionState::Aborted => "aborted", + } + }) + .await; + assert_eq!(session_state, self.state); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSessionPinned { + session: String, +} + +impl TestOperation for AssertSessionPinned { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let is_pinned = + with_mut_session!(test_runner, self.session.as_str(), |session| async { + session.transaction.is_pinned() + }) + .await; + assert!(is_pinned); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSessionUnpinned { + session: String, +} + +impl TestOperation for AssertSessionUnpinned { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let is_pinned = with_mut_session!(test_runner, self.session.as_str(), |session| { + async move { session.transaction.pinned_mongos().is_some() } + }) + .await; + assert!(!is_pinned); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertDifferentLsidOnLastTwoCommands { + client: String, +} + +impl TestOperation for AssertDifferentLsidOnLastTwoCommands { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let entities = test_runner.entities.read().await; + let client = entities.get(&self.client).unwrap().as_client(); + let events = client.get_all_command_started_events(); + + let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); + let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); + assert_ne!(lsid1, lsid2); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSameLsidOnLastTwoCommands { + client: String, +} + +impl TestOperation for AssertSameLsidOnLastTwoCommands { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let entities = test_runner.entities.read().await; + let client = entities.get(&self.client).unwrap().as_client(); + client.sync_workers().await; + let events = client.get_all_command_started_events(); + + let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); + let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); + assert_eq!(lsid1, lsid2); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSessionDirty { + session: String, +} + +impl TestOperation for AssertSessionDirty { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let dirty = with_mut_session!(test_runner, self.session.as_str(), |session| { + async move { session.is_dirty() }.boxed() + }) + .await; + assert!(dirty); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertSessionNotDirty { + session: String, +} + +impl TestOperation for AssertSessionNotDirty { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let dirty = with_mut_session!(test_runner, self.session.as_str(), |session| { + async move { session.is_dirty() } + }) + .await; + assert!(!dirty); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/thread.rs b/src/test/spec/unified_runner/operation/thread.rs new file mode 100644 index 000000000..e8c3a0940 --- /dev/null +++ b/src/test/spec/unified_runner/operation/thread.rs @@ -0,0 +1,47 @@ +use std::sync::Arc; + +use crate::test::spec::unified_runner::{operation::TestOperation, Operation, TestRunner}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct RunOnThread { + thread: String, + operation: Arc, +} + +impl TestOperation for RunOnThread { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let thread = test_runner.get_thread(self.thread.as_str()).await; + thread.run_operation(self.operation.clone()); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct WaitForThread { + thread: String, +} + +impl TestOperation for WaitForThread { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let thread = test_runner.get_thread(self.thread.as_str()).await; + thread.wait().await.unwrap_or_else(|e| { + panic!("thread {:?} did not exit successfully: {}", self.thread, e) + }); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/topology.rs b/src/test/spec/unified_runner/operation/topology.rs new file mode 100644 index 000000000..8423a8744 --- /dev/null +++ b/src/test/spec/unified_runner/operation/topology.rs @@ -0,0 +1,50 @@ +use crate::{ + test::spec::unified_runner::{operation::TestOperation, TestRunner}, + TopologyType, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct RecordTopologyDescription { + id: String, + client: String, +} + +impl TestOperation for RecordTopologyDescription { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let client = test_runner.get_client(&self.client).await; + let description = client.topology_description(); + test_runner.insert_entity(&self.id, description).await; + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AssertTopologyType { + topology_description: String, + topology_type: TopologyType, +} + +impl TestOperation for AssertTopologyType { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let td = test_runner + .get_topology_description(&self.topology_description) + .await; + assert_eq!(td.topology_type, self.topology_type); + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/transaction.rs b/src/test/spec/unified_runner/operation/transaction.rs new file mode 100644 index 000000000..87f7dc7a4 --- /dev/null +++ b/src/test/spec/unified_runner/operation/transaction.rs @@ -0,0 +1,138 @@ +use super::Entity; +use crate::{ + error::Result, + options::TransactionOptions, + test::spec::unified_runner::{ + entity, + operation::{with_mut_session, TestOperation}, + Operation, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct StartTransaction { + #[serde(flatten)] + options: TransactionOptions, +} + +impl TestOperation for StartTransaction { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + with_mut_session!(test_runner, id, |session| { + async move { + session + .start_transaction() + .with_options(self.options.clone()) + .await + } + }) + .await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct CommitTransaction {} + +impl TestOperation for CommitTransaction { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + with_mut_session!(test_runner, id, |session| { + async move { session.commit_transaction().await } + }) + .await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct AbortTransaction {} + +impl TestOperation for AbortTransaction { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + with_mut_session!(test_runner, id, |session| async move { + session.abort_transaction().await + }) + .await?; + Ok(None) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct WithTransaction { + callback: Vec, + #[serde(flatten)] + options: Option, +} + +impl TestOperation for WithTransaction { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + with_mut_session!(test_runner, id, |session| async move { + // `and_run2` runs afoul of a rustc bug here: https://github.com/rust-lang/rust/issues/64552 + #[allow(deprecated)] + session + .start_transaction() + .with_options(self.options.clone()) + .and_run( + (&self.callback, test_runner), + |session, (callback, test_runner)| { + async move { + test_runner.entities.write().await.insert( + id.to_string(), + Entity::SessionPtr(entity::SessionPtr(session)), + ); + let mut result = Ok(()); + for op in callback.iter() { + let r = + op.execute_fallible(test_runner, "withTransaction").await; + if r.is_err() { + result = r; + break; + } + } + test_runner.entities.write().await.remove(id); + result + } + .boxed() + }, + ) + .await + }) + .await?; + Ok(None) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/update.rs b/src/test/spec/unified_runner/operation/update.rs new file mode 100644 index 000000000..ec6d25d1a --- /dev/null +++ b/src/test/spec/unified_runner/operation/update.rs @@ -0,0 +1,113 @@ +use crate::{ + bson::Document, + bson_compat::serialize_to_bson, + error::Result, + options::{ReplaceOptions, UpdateModifications, UpdateOptions}, + test::spec::unified_runner::{ + operation::{with_mut_session, with_opt_session, TestOperation}, + Entity, + TestRunner, + }, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct UpdateMany { + filter: Document, + update: UpdateModifications, + session: Option, + #[serde(flatten)] + options: UpdateOptions, +} + +impl TestOperation for UpdateMany { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .update_many(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct UpdateOne { + filter: Document, + update: UpdateModifications, + #[serde(flatten)] + options: UpdateOptions, + session: Option, +} + +impl TestOperation for UpdateOne { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .update_one(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()), + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct ReplaceOne { + filter: Document, + replacement: Document, + session: Option, + #[serde(flatten)] + options: ReplaceOptions, +} + +impl TestOperation for ReplaceOne { + fn execute_entity_operation<'a>( + &'a self, + id: &'a str, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, Result>> { + async move { + let collection = test_runner.get_collection(id).await; + let result = with_opt_session!( + test_runner, + &self.session, + collection + .replace_one(self.filter.clone(), self.replacement.clone()) + .with_options(self.options.clone()) + ) + .await?; + let result = serialize_to_bson(&result)?; + Ok(Some(result.into())) + } + .boxed() + } +} diff --git a/src/test/spec/unified_runner/operation/wait.rs b/src/test/spec/unified_runner/operation/wait.rs new file mode 100644 index 000000000..e878a6038 --- /dev/null +++ b/src/test/spec/unified_runner/operation/wait.rs @@ -0,0 +1,92 @@ +use std::time::Duration; + +use crate::{ + runtime, + test::spec::unified_runner::{operation::TestOperation, ExpectedEvent, TestRunner}, + ServerType, +}; +use futures::future::BoxFuture; +use futures_util::FutureExt; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct WaitForEvent { + client: String, + event: ExpectedEvent, + count: usize, +} + +impl TestOperation for WaitForEvent { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async { + let client = test_runner.get_client(self.client.as_str()).await; + let entities = test_runner.entities.clone(); + client + .wait_for_matching_events(&self.event, self.count, entities) + .await + .unwrap(); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct WaitForPrimaryChange { + client: String, + prior_topology_description: String, + #[serde(rename = "timeoutMS")] + timeout_ms: Option, +} + +impl TestOperation for WaitForPrimaryChange { + fn execute_test_runner_operation<'a>( + &'a self, + test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + async move { + let client = test_runner.get_client(&self.client).await; + let td = test_runner + .get_topology_description(&self.prior_topology_description) + .await; + let old_primary = td.servers_with_type(&[ServerType::RsPrimary]).next(); + let timeout = Duration::from_millis(self.timeout_ms.unwrap_or(10_000)); + + runtime::timeout(timeout, async { + let mut watcher = client.topology().watch(); + + loop { + let latest = watcher.observe_latest(); + if let Some(primary) = latest.description.primary() { + if Some(primary) != old_primary { + return; + } + } + watcher.wait_for_update(None).await; + } + }) + .await + .unwrap(); + } + .boxed() + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(super) struct Wait { + ms: u64, +} + +impl TestOperation for Wait { + fn execute_test_runner_operation<'a>( + &'a self, + _test_runner: &'a TestRunner, + ) -> BoxFuture<'a, ()> { + tokio::time::sleep(Duration::from_millis(self.ms)).boxed() + } +} diff --git a/src/test/spec/unified_runner/test_event.rs b/src/test/spec/unified_runner/test_event.rs index 3500deb49..e0b5a8077 100644 --- a/src/test/spec/unified_runner/test_event.rs +++ b/src/test/spec/unified_runner/test_event.rs @@ -3,9 +3,11 @@ use crate::{ event::{ cmap::{CmapEvent, ConnectionCheckoutFailedReason, ConnectionClosedReason}, command::CommandEvent, + sdam::SdamEvent, }, - test::{Event, SdamEvent}, + test::Event, ServerType, + TopologyType, }; use serde::Deserialize; @@ -82,8 +84,21 @@ pub(crate) enum ExpectedSdamEvent { previous_description: Option, new_description: Option, }, - #[serde(rename = "topologyDescriptionChangedEvent")] - TopologyDescriptionChanged {}, + #[serde(rename = "topologyOpeningEvent")] + TopologyOpening {}, + #[serde(rename = "topologyClosedEvent")] + TopologyClosed {}, + #[serde(rename = "topologyDescriptionChangedEvent", rename_all = "camelCase")] + TopologyDescriptionChanged { + previous_description: Option, + new_description: Option, + }, + #[serde(rename = "serverHeartbeatStartedEvent", rename_all = "camelCase")] + ServerHeartbeatStarted { awaited: Option }, + #[serde(rename = "serverHeartbeatSucceededEvent", rename_all = "camelCase")] + ServerHeartbeatSucceeded { awaited: Option }, + #[serde(rename = "serverHeartbeatFailedEvent", rename_all = "camelCase")] + ServerHeartbeatFailed { awaited: Option }, } #[derive(Debug, Deserialize)] @@ -93,6 +108,13 @@ pub(crate) struct TestServerDescription { pub(crate) server_type: Option, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct TestTopologyDescription { + #[serde(rename = "type")] + pub(crate) topology_type: Option, +} + #[derive(Copy, Clone, Debug, Deserialize)] pub(crate) enum ObserveEvent { #[serde(rename = "commandStartedEvent")] @@ -125,8 +147,18 @@ pub(crate) enum ObserveEvent { ConnectionCheckedIn, #[serde(rename = "serverDescriptionChangedEvent")] ServerDescriptionChanged, + #[serde(rename = "topologyOpeningEvent")] + TopologyOpening, + #[serde(rename = "topologyClosedEvent")] + TopologyClosed, #[serde(rename = "topologyDescriptionChangedEvent")] TopologyDescriptionChanged, + #[serde(rename = "serverHeartbeatStartedEvent")] + ServerHeartbeatStarted, + #[serde(rename = "serverHeartbeatSucceededEvent")] + ServerHeartbeatSucceeded, + #[serde(rename = "serverHeartbeatFailedEvent")] + ServerHeartbeatFailed, } impl ObserveEvent { @@ -153,10 +185,20 @@ impl ObserveEvent { ) => true, (Self::ConnectionCheckedOut, Event::Cmap(CmapEvent::ConnectionCheckedOut(_))) => true, (Self::ConnectionCheckedIn, Event::Cmap(CmapEvent::ConnectionCheckedIn(_))) => true, + (Self::TopologyOpening, Event::Sdam(SdamEvent::TopologyOpening(_))) => true, + (Self::TopologyClosed, Event::Sdam(SdamEvent::TopologyClosed(_))) => true, ( Self::TopologyDescriptionChanged, Event::Sdam(SdamEvent::TopologyDescriptionChanged(_)), ) => true, + (Self::ServerHeartbeatStarted, Event::Sdam(SdamEvent::ServerHeartbeatStarted(_))) => { + true + } + ( + Self::ServerHeartbeatSucceeded, + Event::Sdam(SdamEvent::ServerHeartbeatSucceeded(_)), + ) => true, + (Self::ServerHeartbeatFailed, Event::Sdam(SdamEvent::ServerHeartbeatFailed(_))) => true, _ => false, } } diff --git a/src/test/spec/unified_runner/test_file.rs b/src/test/spec/unified_runner/test_file.rs index 14e6af557..d182087dc 100644 --- a/src/test/spec/unified_runner/test_file.rs +++ b/src/test/spec/unified_runner/test_file.rs @@ -1,10 +1,8 @@ -#[cfg(feature = "tracing-unstable")] -use std::collections::HashMap; -use std::{borrow::Cow, fmt::Write, sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use percent_encoding::NON_ALPHANUMERIC; +use pretty_assertions::assert_eq; use regex::Regex; -use semver::{Version, VersionReq}; +use semver::Version; use serde::{Deserialize, Deserializer}; use tokio::sync::oneshot; @@ -14,13 +12,15 @@ use super::{results_match, ExpectedEvent, ObserveEvent, Operation}; use crate::trace; use crate::{ bson::{doc, Bson, Deserializer as BsonDeserializer, Document}, - client::options::{ServerApi, ServerApiVersion, SessionOptions}, + client::options::{ServerApi, ServerApiVersion}, concern::{Acknowledgment, ReadConcernLevel}, - error::Error, + error::{BulkWriteError, Error, ErrorKind}, gridfs::options::GridFsBucketOptions, options::{ + AuthMechanism, ClientOptions, CollectionOptions, + CreateCollectionOptions, DatabaseOptions, HedgedReadOptions, ReadConcern, @@ -28,7 +28,17 @@ use crate::{ SelectionCriteria, WriteConcern, }, - test::{serde_helpers::deserialize_nonempty_vec, Serverless, TestClient, DEFAULT_URI}, + serde_util, + test::{ + auth_enabled, + get_client_options, + get_server_parameters, + get_topology, + server_version_matches, + Event, + Serverless, + DEFAULT_URI, + }, }; #[derive(Debug, Deserialize)] @@ -37,11 +47,11 @@ pub(crate) struct TestFile { pub(crate) description: String, #[serde(deserialize_with = "deserialize_schema_version")] pub(crate) schema_version: Version, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) run_on_requirements: Option>, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) create_entities: Option>, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) initial_data: Option>, pub(crate) tests: Vec, // We don't need to use this field, but it needs to be included during deserialization so that @@ -71,12 +81,13 @@ where pub(crate) struct RunOnRequirement { min_server_version: Option, max_server_version: Option, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] topologies: Option>, server_parameters: Option, serverless: Option, auth: Option, csfle: Option, + auth_mechanism: Option, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq)] @@ -84,44 +95,38 @@ pub(crate) struct RunOnRequirement { pub(crate) enum Topology { Single, ReplicaSet, + #[serde(alias = "sharded-replicaset")] Sharded, #[serde(rename = "load-balanced")] LoadBalanced, } impl RunOnRequirement { - pub(crate) async fn can_run_on(&self, client: &TestClient) -> Result<(), String> { + pub(crate) async fn can_run_on(&self) -> Result<(), String> { if let Some(ref min_version) = self.min_server_version { - let req = VersionReq::parse(&format!(">= {}", &min_version)).unwrap(); - if !req.matches(&client.server_version) { - return Err(format!( - "min server version {:?}, actual {:?}", - min_version, client.server_version - )); + if !server_version_matches(&format!(">= {min_version}")).await { + return Err(format!("does not match min server version: {min_version}")); } } if let Some(ref max_version) = self.max_server_version { - let req = VersionReq::parse(&format!("<= {}", &max_version)).unwrap(); - if !req.matches(&client.server_version) { - return Err(format!( - "max server version {:?}, actual {:?}", - max_version, client.server_version - )); + if !server_version_matches(&format!("<= {max_version}")).await { + return Err(format!("does not match max server version: {max_version}")); } } if let Some(ref topologies) = self.topologies { - let client_topology = client.topology(); - if !topologies.contains(&client_topology) { + let client_topology = get_topology().await; + if !topologies.contains(client_topology) { return Err(format!( "allowed topologies {:?}, actual: {:?}", topologies, client_topology )); } } - if let Some(ref actual_server_parameters) = self.server_parameters { + if let Some(ref required_server_parameters) = self.server_parameters { + let actual_server_parameters = get_server_parameters().await; if results_match( - Some(&Bson::Document(client.server_parameters.clone())), - &Bson::Document(actual_server_parameters.clone()), + Some(&Bson::Document(actual_server_parameters.clone())), + &Bson::Document(required_server_parameters.clone()), false, None, ) @@ -129,7 +134,7 @@ impl RunOnRequirement { { return Err(format!( "required server parameters {:?}, actual {:?}", - actual_server_parameters, client.server_parameters + required_server_parameters, actual_server_parameters )); } } @@ -139,19 +144,29 @@ impl RunOnRequirement { } } if let Some(ref auth) = self.auth { - if *auth != client.auth_enabled() { + if *auth != auth_enabled().await { return Err("requires auth".to_string()); } } - if let Some(csfle) = &self.csfle { - if *csfle && std::env::var("CSFLE_LOCAL_KEY").is_err() { - return Err("requires csfle env".to_string()); + if self.csfle == Some(true) && !cfg!(feature = "in-use-encryption") { + return Err("requires csfle but in-use-encryption feature not enabled".to_string()); + } + if let Some(ref auth_mechanism) = self.auth_mechanism { + let actual_mechanism = get_client_options() + .await + .credential + .as_ref() + .and_then(|c| c.mechanism.as_ref()); + if !actual_mechanism.is_some_and(|actual_mechanism| actual_mechanism == auth_mechanism) + { + return Err(format!("requires {:?} auth mechanism", auth_mechanism)); } } Ok(()) } } +#[allow(clippy::large_enum_variant)] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) enum TestFileEntity { @@ -161,36 +176,29 @@ pub(crate) enum TestFileEntity { Session(Session), Bucket(Bucket), Thread(Thread), - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] ClientEncryption(ClientEncryption), } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase", deny_unknown_fields)] -pub struct StoreEventsAsEntity { - pub id: String, - pub events: Vec, -} - #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) struct Client { pub(crate) id: String, pub(crate) uri_options: Option, pub(crate) use_multiple_mongoses: Option, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) observe_events: Option>, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) ignore_command_monitoring_events: Option>, #[serde(default)] pub(crate) observe_sensitive_commands: Option, #[serde(default, deserialize_with = "deserialize_server_api_test_format")] pub(crate) server_api: Option, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] - pub(crate) store_events_as_entities: Option>, #[cfg(feature = "tracing-unstable")] #[serde(default, deserialize_with = "deserialize_tracing_level_map")] pub(crate) observe_log_messages: Option>, + #[cfg(feature = "in-use-encryption")] + pub(crate) auto_encrypt_opts: Option, } impl Client { @@ -226,7 +234,13 @@ pub(crate) fn merge_uri_options( uri_options: Option<&Document>, use_multiple_hosts: bool, ) -> String { - let given_uri = if !use_multiple_hosts && !given_uri.starts_with("mongodb+srv") { + let direct_connection = uri_options + .and_then(|options| options.get_bool("directConnection").ok()) + .unwrap_or(false); + + // TODO RUST-1308: use the ConnectionString type to remove hosts + let uri = if (!use_multiple_hosts || direct_connection) && !given_uri.starts_with("mongodb+srv") + { let hosts_regex = Regex::new(r"mongodb://([^/]*)").unwrap(); let single_host = hosts_regex .captures(given_uri) @@ -237,58 +251,72 @@ pub(crate) fn merge_uri_options( .split(',') .next() .expect("expected URI to contain at least one host, but it had none"); - hosts_regex.replace(given_uri, format!("mongodb://{}", single_host)) + hosts_regex + .replace(given_uri, format!("mongodb://{}", single_host)) + .to_string() } else { - Cow::Borrowed(given_uri) + given_uri.to_string() }; - let uri_options = match uri_options { - Some(opts) => opts, - None => return given_uri.to_string(), + let Some(mut uri_options) = uri_options.cloned() else { + return uri; }; - let mut given_uri_parts = given_uri.split('?'); + let (mut uri, existing_options) = match uri.split_once("?") { + Some((pre_options, options)) => (pre_options.to_string(), Some(options)), + None => (uri, None), + }; - let mut uri = String::from(given_uri_parts.next().unwrap()); - // A connection string has two slashes before the host list and one slash before the auth db - // name. If an auth db name is not provided the latter slash might not be present, so it needs - // to be added manually. - if uri.chars().filter(|c| *c == '/').count() < 3 { - uri.push('/'); - } - uri.push('?'); - - if let Some(options) = given_uri_parts.next() { - let options = options.split('&'); - for option in options { - let key = option.split('=').next().unwrap(); - // The provided URI options should override any existing options in the connection - // string. + if let Some(existing_options) = existing_options { + for option in existing_options.split("&") { + let (key, value) = option.split_once("=").unwrap(); + // prefer the option specified by the test if !uri_options.contains_key(key) { - uri.push_str(option); - uri.push('&'); + uri_options.insert(key, value); } } } + if direct_connection { + uri_options.remove("replicaSet"); + } + + let mut join = '?'; for (key, value) in uri_options { + uri.push(join); + if join == '?' { + join = '&'; + } + uri.push_str(&key); + uri.push('='); + let value = value.to_string(); - // to_string() wraps quotations around Bson strings let value = value.trim_start_matches('\"').trim_end_matches('\"'); - let _ = write!( - &mut uri, - "{}={}&", - &key, - percent_encoding::percent_encode(value.as_bytes(), NON_ALPHANUMERIC) - ); + uri.push_str(value); } - // remove the trailing '&' from the URI (or '?' if no options are present) - uri.pop(); - uri } +#[cfg(feature = "in-use-encryption")] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub(crate) struct AutoEncryptionOpts { + pub(crate) kms_providers: HashMap, + pub(crate) key_vault_namespace: crate::Namespace, + pub(crate) bypass_auto_encryption: Option, + pub(crate) schema_map: Option>, + pub(crate) encrypted_fields_map: Option>, + pub(crate) extra_options: Option, + pub(crate) bypass_query_analysis: Option, + #[serde( + default, + rename = "keyExpirationMS", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" + )] + pub(crate) key_cache_expiration: Option, +} + #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) struct Database { @@ -312,7 +340,7 @@ pub(crate) struct Collection { pub(crate) struct Session { pub(crate) id: String, pub(crate) client: String, - pub(crate) session_options: Option, + pub(crate) session_options: Option, } #[derive(Debug, Deserialize)] @@ -329,7 +357,7 @@ pub(crate) struct Thread { pub(crate) id: String, } -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) struct ClientEncryption { @@ -337,13 +365,19 @@ pub(crate) struct ClientEncryption { pub(crate) client_encryption_opts: ClientEncryptionOpts, } -#[cfg(feature = "in-use-encryption-unstable")] +#[cfg(feature = "in-use-encryption")] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) struct ClientEncryptionOpts { pub(crate) key_vault_client: String, pub(crate) key_vault_namespace: crate::Namespace, - pub(crate) kms_providers: Document, + pub(crate) kms_providers: HashMap, + #[serde( + default, + rename = "keyExpirationMS", + deserialize_with = "serde_util::deserialize_duration_option_from_u64_millis" + )] + pub(crate) key_cache_expiration: Option, } /// Messages used for communicating with test runner "threads". @@ -386,21 +420,22 @@ pub(crate) struct CollectionData { pub(crate) collection_name: String, pub(crate) database_name: String, pub(crate) documents: Vec, + pub(crate) create_options: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub(crate) struct TestCase { pub(crate) description: String, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) run_on_requirements: Option>, pub(crate) skip_reason: Option, pub(crate) operations: Vec, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) expect_events: Option>, #[cfg(feature = "tracing-unstable")] pub(crate) expect_log_messages: Option>, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) outcome: Option>, } @@ -421,6 +456,23 @@ pub(crate) enum ExpectedEventType { // TODO RUST-1055 Remove this when connection usage is serialized. #[serde(skip)] CmapWithoutConnectionReady, + Sdam, +} + +impl ExpectedEventType { + pub(crate) fn matches(&self, event: &Event) -> bool { + match (self, event) { + (ExpectedEventType::Cmap, Event::Cmap(_)) => true, + ( + ExpectedEventType::CmapWithoutConnectionReady, + Event::Cmap(crate::event::cmap::CmapEvent::ConnectionReady(_)), + ) => false, + (ExpectedEventType::CmapWithoutConnectionReady, Event::Cmap(_)) => true, + (ExpectedEventType::Command, Event::Command(_)) => true, + (ExpectedEventType::Sdam, Event::Sdam(_)) => true, + _ => false, + } + } } #[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] @@ -435,18 +487,23 @@ pub(crate) enum EventMatch { #[serde(deny_unknown_fields, rename_all = "camelCase")] pub(crate) struct ExpectedMessages { pub(crate) client: String, + pub(crate) ignore_extra_messages: Option, + pub(crate) ignore_messages: Option>, pub(crate) messages: Vec, } #[cfg(feature = "tracing-unstable")] #[derive(Debug, Deserialize)] -#[serde(deny_unknown_fields)] -#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields, rename_all = "camelCase")] pub(crate) struct ExpectedMessage { - #[serde(deserialize_with = "deserialize_tracing_level")] - pub(crate) level: tracing::Level, - #[serde(rename = "component", deserialize_with = "deserialize_tracing_target")] - pub(crate) target: String, + #[serde(default, deserialize_with = "deserialize_option_tracing_level")] + pub(crate) level: Option, + #[serde( + default, + rename = "component", + deserialize_with = "deserialize_option_tracing_target" + )] + pub(crate) target: Option, pub(crate) failure_is_redacted: Option, pub(crate) data: Document, } @@ -460,110 +517,123 @@ pub(crate) struct ExpectError { pub(crate) error_contains: Option, pub(crate) error_code: Option, pub(crate) error_code_name: Option, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) error_labels_contain: Option>, - #[serde(default, deserialize_with = "deserialize_nonempty_vec")] + #[serde(default, deserialize_with = "serde_util::deserialize_nonempty_vec")] pub(crate) error_labels_omit: Option>, pub(crate) expect_result: Option, + #[serde(default, deserialize_with = "serde_util::deserialize_indexed_map")] + pub(crate) write_errors: Option>, + pub(crate) write_concern_errors: Option>, } impl ExpectError { - pub(crate) fn verify_result( - &self, - error: &Error, - description: impl AsRef, - ) -> std::result::Result<(), String> { - let description = description.as_ref(); + pub(crate) fn verify_result(&self, error: &Error, description: impl AsRef) { + let context = format!( + "test description: {}\nerror: {:?}\n", + description.as_ref(), + error + ); if let Some(is_client_error) = self.is_client_error { - if is_client_error != !error.is_server_error() { - return Err(format!( - "{}: expected client error but got {:?}", - description, error - )); - } + assert_eq!(!error.is_server_error(), is_client_error, "{context}"); } + if let Some(error_contains) = &self.error_contains { - match &error.message() { - Some(msg) if msg.contains(error_contains) => (), - _ => { - return Err(format!( - "{}: \"{}\" should include message field", - description, error - )) - } - } + let Some(message) = error.message() else { + panic!("{context}expected error to have message"); + }; + assert!(message.contains(error_contains), "{context}"); } + if let Some(error_code) = self.error_code { - match &error.sdam_code() { - Some(code) => { - if code != &error_code { - return Err(format!( - "{}: error code {} ({:?}) did not match expected error code {}", - description, - code, - error.code_name(), - error_code - )); - } - } - None => { - return Err(format!( - "{}: {:?} was expected to include code {} but had no code", - description, error, error_code - )) - } - } + let Some(actual_code) = error.code() else { + panic!("{context}expected error to have code"); + }; + assert_eq!(actual_code, error_code, "{context}"); } if let Some(expected_code_name) = &self.error_code_name { - match error.code_name() { - Some(name) => { - if name != expected_code_name { - return Err(format!( - "{}: error code name \"{}\" did not match expected error code name \ - \"{}\"", - description, name, expected_code_name, - )); - } - } - None => { - return Err(format!( - "{}: {:?} was expected to include code name \"{}\" but had no code name", - description, error, expected_code_name - )) - } - } + let Some(actual_code_name) = error.code_name() else { + panic!("{}: expected error to have code name", context); + }; + assert_eq!(actual_code_name, expected_code_name, "{}", context); } + if let Some(error_labels_contain) = &self.error_labels_contain { for label in error_labels_contain { - if !error.contains_label(label) { - return Err(format!( - "{}: expected {:?} to contain label \"{}\"", - description, error, label - )); - } + assert!(error.contains_label(label), "{}", context); } } + if let Some(error_labels_omit) = &self.error_labels_omit { for label in error_labels_omit { - if error.contains_label(label) { - return Err(format!( - "{}: expected {:?} to omit label \"{}\"", - description, error, label - )); + assert!(!error.contains_label(label), "{}", context); + } + } + + if let Some(ref expected_result) = self.expect_result { + match *error.kind { + ErrorKind::BulkWrite(BulkWriteError { + ref partial_result, .. + }) => { + let actual_result = partial_result.as_ref().map(|result| { + crate::bson_compat::serialize_to_bson(result).expect(&context) + }); + results_match(actual_result.as_ref(), expected_result, false, None) + .expect(&context); } + // Skip this assertion for insert_many tests, as InsertManyError does not report + // partial results. + ErrorKind::InsertMany(_) => {} + _ => panic!("{context}expected error with partial result"), } } - if self.expect_result.is_some() { - // TODO RUST-260: match against partial results + + if let Some(ref write_errors) = self.write_errors { + let ErrorKind::BulkWrite(BulkWriteError { + write_errors: ref actual_write_errors, + .. + }) = *error.kind + else { + panic!("{context}expected client bulk write error"); + }; + + for (expected_index, expected_error) in write_errors { + let actual_error = actual_write_errors.get(expected_index).expect(&context); + let actual_error = crate::bson_compat::serialize_to_bson(&actual_error) + .map_err(|e| e.to_string()) + .expect(&context); + results_match(Some(&actual_error), expected_error, true, None).expect(&context); + } + } + + if let Some(ref write_concern_errors) = self.write_concern_errors { + let ErrorKind::BulkWrite(BulkWriteError { + write_concern_errors: ref actual_write_concern_errors, + .. + }) = *error.kind + else { + panic!("{context}expected client bulk write error"); + }; + + assert_eq!( + actual_write_concern_errors.len(), + write_concern_errors.len(), + "{context}" + ); + + for (actual, expected) in actual_write_concern_errors.iter().zip(write_concern_errors) { + let actual = crate::bson_compat::serialize_to_bson(&actual) + .map_err(|e| e.to_string()) + .expect(&context); + results_match(Some(&actual), expected, true, None).expect(&context); + } } - Ok(()) } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn merged_uri_options() { let options = doc! { "ssl": true, @@ -571,7 +641,7 @@ async fn merged_uri_options() { "readconcernlevel": "local", }; let uri = merge_uri_options(&DEFAULT_URI, Some(&options), true); - let options = ClientOptions::parse_uri(&uri, None).await.unwrap(); + let options = ClientOptions::parse(&uri).await.unwrap(); assert!(options.tls_options().is_some()); @@ -594,11 +664,21 @@ fn deserialize_selection_criteria() { match selection_criteria { SelectionCriteria::ReadPreference(read_preference) => match read_preference { - ReadPreference::SecondaryPreferred { options } => { + ReadPreference::SecondaryPreferred { + options: Some(options), + } => { assert_eq!(options.max_staleness, Some(Duration::from_secs(100))); - assert_eq!(options.hedge, Some(HedgedReadOptions::with_enabled(true))); + #[allow(deprecated)] + let hedge = options.hedge; + assert_eq!( + hedge, + Some(HedgedReadOptions::builder().enabled(true).build()) + ); } - other => panic!("Expected mode SecondaryPreferred, got {:?}", other), + other => panic!( + "Expected mode SecondaryPreferred with options, got {:?}", + other + ), }, SelectionCriteria::Predicate(_) => panic!("Expected read preference, got predicate"), } @@ -644,11 +724,13 @@ where } #[cfg(feature = "tracing-unstable")] -fn deserialize_tracing_target<'de, D>(deserializer: D) -> std::result::Result +fn deserialize_option_tracing_target<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> where D: Deserializer<'de>, { - String::deserialize(deserializer).map(|s| log_component_as_tracing_target(&s)) + String::deserialize(deserializer).map(|s| Some(log_component_as_tracing_target(&s))) } #[cfg(feature = "tracing-unstable")] @@ -657,18 +739,20 @@ fn log_component_as_tracing_target(component: &String) -> String { "command" => trace::COMMAND_TRACING_EVENT_TARGET.to_string(), "connection" => trace::CONNECTION_TRACING_EVENT_TARGET.to_string(), "serverSelection" => trace::SERVER_SELECTION_TRACING_EVENT_TARGET.to_string(), + "topology" => trace::TOPOLOGY_TRACING_EVENT_TARGET.to_string(), _ => panic!("Unknown tracing target: {}", component), } } #[cfg(feature = "tracing-unstable")] -fn deserialize_tracing_level<'de, D>( +fn deserialize_option_tracing_level<'de, D>( deserializer: D, -) -> std::result::Result +) -> std::result::Result, D::Error> where D: Deserializer<'de>, { String::deserialize(deserializer)? .parse::() + .map(Some) .map_err(|e| serde::de::Error::custom(format!("{}", e))) } diff --git a/src/test/spec/unified_runner/test_runner.rs b/src/test/spec/unified_runner/test_runner.rs index e2263aa98..1ef5b2a10 100644 --- a/src/test/spec/unified_runner/test_runner.rs +++ b/src/test/spec/unified_runner/test_runner.rs @@ -7,36 +7,31 @@ use tokio::sync::{mpsc, RwLock}; use crate::{ bson::{doc, Document}, client::options::ClientOptions, - concern::{Acknowledgment, WriteConcern}, + concern::WriteConcern, gridfs::GridFsBucket, - options::{ - CollectionOptions, - CreateCollectionOptions, - FindOptions, - ReadConcern, - ReadPreference, - SelectionCriteria, - }, + options::{CollectionOptions, ReadConcern, ReadPreference, SelectionCriteria}, runtime, sdam::{TopologyDescription, MIN_HEARTBEAT_FREQUENCY}, test::{ + get_client_options, log_uncaptured, + server_version_lte, spec::unified_runner::{ - entity::EventList, matcher::events_match, test_file::{ExpectedEventType, TestFile}, }, + topology_is_sharded, update_options_for_testing, - util::FailPointGuard, - EventHandler, + util::fail_point::FailPointGuard, TestClient, - CLIENT_OPTIONS, DEFAULT_URI, LOAD_BALANCED_MULTIPLE_URI, LOAD_BALANCED_SINGLE_URI, - SERVERLESS, SERVER_API, }, + Client, + ClientSession, + ClusterTime, Collection, Database, }; @@ -50,12 +45,10 @@ use super::{ CollectionData, Entity, SessionEntity, + TestCursor, TestFileEntity, }; -#[cfg(feature = "in-use-encryption-unstable")] -use crate::test::KmsProviderList; - #[cfg(feature = "tracing-unstable")] use crate::test::{ spec::unified_runner::matcher::tracing_events_match, @@ -74,7 +67,7 @@ const SKIPPED_OPERATIONS: &[&str] = &[ ]; static MIN_SPEC_VERSION: Version = Version::new(1, 0, 0); -static MAX_SPEC_VERSION: Version = Version::new(1, 14, 0); +static MAX_SPEC_VERSION: Version = Version::new(1, 23, 0); pub(crate) type EntityMap = HashMap; @@ -83,26 +76,16 @@ pub(crate) struct TestRunner { pub(crate) internal_client: TestClient, pub(crate) entities: Arc>, pub(crate) fail_point_guards: Arc>>, + pub(crate) cluster_time: Arc>>, } impl TestRunner { pub(crate) async fn new() -> Self { Self { - internal_client: TestClient::new().await, + internal_client: Client::for_test().await, entities: Default::default(), fail_point_guards: Default::default(), - } - } - - pub(crate) async fn new_with_connection_string(connection_string: &str) -> Self { - #[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] - let options = ClientOptions::parse(connection_string).await.unwrap(); - #[cfg(any(feature = "sync", feature = "tokio-sync"))] - let options = ClientOptions::parse(connection_string).unwrap(); - Self { - internal_client: TestClient::with_options(Some(options)).await, - entities: Arc::new(RwLock::new(EntityMap::new())), - fail_point_guards: Arc::new(RwLock::new(Vec::new())), + cluster_time: Default::default(), } } @@ -115,7 +98,7 @@ impl TestRunner { let schema_version = &test_file.schema_version; assert!( schema_version >= &MIN_SPEC_VERSION && schema_version <= &MAX_SPEC_VERSION, - "Test runner not compatible with specification version {}", + "Test runner not compatible with schema version {}", schema_version ); @@ -128,7 +111,7 @@ impl TestRunner { let mut can_run_on = false; let mut run_on_errors = vec![]; for requirement in requirements { - match requirement.can_run_on(&self.internal_client).await { + match requirement.can_run_on().await { Ok(()) => can_run_on = true, Err(e) => run_on_errors.push(e), } @@ -191,7 +174,7 @@ impl TestRunner { let mut can_run_on = false; let mut run_on_errors = vec![]; for requirement in requirements { - match requirement.can_run_on(&self.internal_client).await { + match requirement.can_run_on().await { Ok(()) => can_run_on = true, Err(e) => run_on_errors.push(e), } @@ -209,9 +192,22 @@ impl TestRunner { log_uncaptured(format!("Executing {:?}", &test_case.description)); if let Some(ref initial_data) = test_file.initial_data { + // If a test: + // * set `useMultipleMongoses: false` + // * and used `readConcern: { level: snapshot }` + // * and ran on a load-balanced replica set + // it was possible for the internal client to write data to a different + // mongos than the test execution client; the snapshot read concern would + // then pick a timestamp before the write, causing the data to not be + // visible to the test, causing very confusing flakes. Using a single-mongos client + // to do initial data population guarantees it'll have written the initial data + // to the same mongos as the test client in that particular configuration. + let data_client = Client::for_test().use_single_mongos().await; + let mut session = data_client.start_session().await.unwrap(); for data in initial_data { - self.insert_initial_data(data).await; + self.insert_initial_data(data, &mut session).await; } + *self.cluster_time.write().await = session.cluster_time().cloned(); } self.entities.write().await.clear(); @@ -220,13 +216,42 @@ impl TestRunner { .await; } + // Workaround for SERVER-39704: + // test runners MUST execute a non-transactional distinct command on + // each mongos server before running any test that might execute distinct within a + // transaction. + if topology_is_sharded().await + && server_version_lte(4, 2).await + && test_case.operations.iter().any(|op| op.name == "distinct") + { + self.internal_client.disable_command_events(true); + for server_address in self.internal_client.options().hosts.clone() { + for (_, entity) in self.entities.read().await.iter() { + if let Entity::Collection(coll) = entity { + let coll = self + .internal_client + .database(&coll.namespace().db) + .collection::(&coll.namespace().coll); + let server_address = server_address.clone(); + coll.distinct("_id", doc! {}) + .selection_criteria(SelectionCriteria::Predicate(Arc::new( + move |server_info| *server_info.address() == server_address, + ))) + .await + .unwrap(); + } + } + } + self.internal_client.disable_command_events(false); + } + #[cfg(feature = "tracing-unstable")] - let (mut tracing_subscriber, _levels_guard) = { + let (mut tracing_stream, _levels_guard) = { let tracing_levels = max_verbosity_levels_for_test_case(&test_file.create_entities, test_case); let guard = DEFAULT_GLOBAL_TRACING_HANDLER.set_levels(tracing_levels); - let subscriber = DEFAULT_GLOBAL_TRACING_HANDLER.subscribe(); - (subscriber, guard) + let stream = DEFAULT_GLOBAL_TRACING_HANDLER.event_stream(); + (stream, guard) }; for operation in &test_case.operations { @@ -238,11 +263,13 @@ impl TestRunner { // implicit session used in the first operation is returned to the pool before // the second operation is executed. if test_case.description == "Server supports implicit sessions" { - runtime::delay_for(Duration::from_secs(1)).await; + tokio::time::sleep(Duration::from_secs(1)).await; } } if let Some(ref events) = test_case.expect_events { + // Hack: make sure in-flight events are recorded. + tokio::time::sleep(Duration::from_millis(500)).await; for expected in events { let entities = self.entities.read().await; let entity = entities.get(&expected.client).unwrap(); @@ -277,9 +304,9 @@ impl TestRunner { for (actual, expected) in actual_events.iter().zip(expected_events) { if let Err(e) = events_match(actual, expected, Some(&entities)) { panic!( - "event mismatch: expected = {:#?}, actual = {:#?}\nall \ - expected:\n{:#?}\nall actual:\n{:#?}\nmismatch detail: {}", - expected, actual, expected_events, actual_events, e, + "event mismatch in {}: expected = {:#?}, actual = {:#?}\nmismatch \ + detail: {}", + test_case.description, expected, actual, e, ); } } @@ -290,8 +317,8 @@ impl TestRunner { if let Some(ref expected_messages) = test_case.expect_log_messages { self.sync_workers().await; - let all_tracing_events = tracing_subscriber - .collect_events(Duration::from_millis(1000), |_| true) + let all_tracing_events = tracing_stream + .collect(Duration::from_millis(1000), |_| true) .await; for expectation in expected_messages { @@ -299,22 +326,39 @@ impl TestRunner { let client_actual_events: Vec<_> = all_tracing_events .iter() - .filter(|e| e.topology_id() == client_topology_id.to_hex()) + .filter(|e| { + if e.topology_id() != client_topology_id.to_hex() { + return false; + } + if let Some(ref ignored_messages) = expectation.ignore_messages { + for ignored_message in ignored_messages { + if tracing_events_match(e, ignored_message).is_ok() { + return false; + } + } + } + true + }) .collect(); let expected_events = &expectation.messages; - assert_eq!( - client_actual_events.len(), - expected_events.len(), - "Actual tracing event count should match expected" - ); + if expectation.ignore_extra_messages != Some(true) { + assert_eq!( + client_actual_events.len(), + expected_events.len(), + "Actual tracing event count should match expected. Expected events = \ + {:#?}, actual events = {:#?}", + expected_events, + client_actual_events, + ); + } for (actual, expected) in client_actual_events.iter().zip(expected_events) { if let Err(e) = tracing_events_match(actual, expected) { panic!( - "tracing event mismatch: expected = {:#?}, actual = {:#?}\nall \ - expected:\n{:#?}\nall actual:\n{:#?}\nmismatch detail: {}", - expected, actual, expected_events, client_actual_events, e, + "tracing event mismatch: expected = {:#?}, actual = \ + {:#?}\nmismatch detail: {}", + expected, actual, e, ); } } @@ -340,50 +384,68 @@ impl TestRunner { .internal_client .get_coll_with_options(db_name, coll_name, options); - let options = FindOptions::builder().sort(doc! { "_id": 1 }).build(); let actual_data: Vec = collection - .find(doc! {}, options) + .find(doc! {}) + .sort(doc! { "_id": 1 }) .await .unwrap() .try_collect() .await .unwrap(); - assert_eq!(expected_data.documents, actual_data); + assert_eq!(actual_data, expected_data.documents); } } } } - pub(crate) async fn insert_initial_data(&self, data: &CollectionData) { - let write_concern = WriteConcern::builder().w(Acknowledgment::Majority).build(); + pub(crate) async fn insert_initial_data( + &self, + data: &CollectionData, + session: &mut ClientSession, + ) { + let client = session.client(); + let collection_options = CollectionOptions::builder() + .write_concern(WriteConcern::majority()) + .build(); + let db = client.database(&data.database_name); + let coll = db.collection_with_options(&data.collection_name, collection_options.clone()); + coll.drop().session(&mut *session).await.unwrap(); + db.collection_with_options::( + &format!("enxcol_.{}.esc", data.collection_name), + collection_options.clone(), + ) + .drop() + .session(&mut *session) + .await + .unwrap(); + db.collection_with_options::( + &format!("enxcol_.{}.ecoc", data.collection_name), + collection_options.clone(), + ) + .drop() + .session(&mut *session) + .await + .unwrap(); + + let mut create_options = data + .create_options + .as_ref() + .map_or_else(Default::default, Clone::clone); + create_options.write_concern = Some(WriteConcern::majority()); + client + .database(&data.database_name) + .create_collection(&data.collection_name) + .session(&mut *session) + .with_options(create_options) + .await + .unwrap(); if !data.documents.is_empty() { - let collection_options = CollectionOptions::builder() - .write_concern(write_concern) - .build(); - let coll = self - .internal_client - .init_db_and_coll_with_options( - &data.database_name, - &data.collection_name, - collection_options, - ) - .await; - coll.insert_many(data.documents.clone(), None) + coll.insert_many(data.documents.clone()) + .session(session) .await .unwrap(); - } else { - let collection_options = CreateCollectionOptions::builder() - .write_concern(write_concern) - .build(); - self.internal_client - .create_fresh_collection( - &data.database_name, - &data.collection_name, - collection_options, - ) - .await; } } @@ -395,17 +457,6 @@ impl TestRunner { for entity in create_entities { let (id, entity) = match entity { TestFileEntity::Client(client) => { - if let Some(store_events_as_entities) = &client.store_events_as_entities { - for store_events_as_entity in store_events_as_entities { - let event_list = EventList { - client_id: client.id.clone(), - event_names: store_events_as_entity.events.clone(), - }; - self.insert_entity(&store_events_as_entity.id, event_list) - .await; - } - } - let id = client.id.clone(); let observe_events = client.observe_events.clone(); let ignore_command_names = client.ignore_command_monitoring_events.clone(); @@ -413,9 +464,8 @@ impl TestRunner { client.observe_sensitive_commands.unwrap_or(false); let server_api = client.server_api.clone().or_else(|| SERVER_API.clone()); - let given_uri = if CLIENT_OPTIONS.get().await.load_balanced.unwrap_or(false) { - // for serverless testing, ignore use_multiple_mongoses. - if client.use_multiple_mongoses() && !*SERVERLESS { + let given_uri = if get_client_options().await.load_balanced.unwrap_or(false) { + if client.use_multiple_mongoses() { LOAD_BALANCED_MULTIPLE_URI.as_ref().expect( "Test requires URI for load balancer fronting multiple servers", ) @@ -432,26 +482,19 @@ impl TestRunner { client.uri_options.as_ref(), client.use_multiple_mongoses(), ); - let mut options = - ClientOptions::parse_uri(&uri, None) - .await - .unwrap_or_else(|e| { - panic!( - "[{}] invalid client URI: {}, error: {}", - description.as_ref(), - uri, - e - ) - }); + let mut options = ClientOptions::parse(&uri).await.unwrap_or_else(|e| { + panic!( + "[{}] invalid client URI: {}, error: {}", + description.as_ref(), + uri, + e + ) + }); update_options_for_testing(&mut options); - let handler = Arc::new(EventHandler::new()); - options.command_event_handler = Some(handler.clone()); - options.cmap_event_handler = Some(handler.clone()); - options.sdam_event_handler = Some(handler.clone()); options.server_api = server_api; - if client.use_multiple_mongoses() && TestClient::new().await.is_sharded() { + if client.use_multiple_mongoses() && topology_is_sharded().await { assert!( options.hosts.len() > 1, "[{}]: Test requires multiple mongos hosts", @@ -482,16 +525,43 @@ impl TestRunner { options.tracing_max_document_length_bytes = Some(10000); } - ( - id, - Entity::Client(ClientEntity::new( - options, - handler, - observe_events, - ignore_command_names, - observe_sensitive_commands, - )), - ) + let entity = ClientEntity::new( + options, + observe_events, + ignore_command_names, + observe_sensitive_commands, + ); + + #[cfg(feature = "in-use-encryption")] + if let Some(opts) = &client.auto_encrypt_opts { + use crate::client::csfle::options::{AutoEncryptionOptions, KmsProviders}; + + let real_opts = AutoEncryptionOptions { + key_vault_client: None, + key_vault_namespace: opts.key_vault_namespace.clone(), + kms_providers: KmsProviders::new( + crate::test::csfle::fill_kms_placeholders( + opts.kms_providers.clone(), + ), + ) + .unwrap(), + schema_map: opts.schema_map.clone(), + bypass_auto_encryption: opts.bypass_auto_encryption, + extra_options: opts.extra_options.clone(), + encrypted_fields_map: opts.encrypted_fields_map.clone(), + bypass_query_analysis: opts.bypass_query_analysis, + disable_crypt_shared: None, + key_cache_expiration: opts.key_cache_expiration, + }; + entity + .client() + .unwrap() + .init_csfle(real_opts) + .await + .unwrap(); + } + + (id, Entity::Client(entity)) } TestFileEntity::Database(database) => { let id = database.id.clone(); @@ -518,10 +588,14 @@ impl TestRunner { TestFileEntity::Session(session) => { let id = session.id.clone(); let client = self.get_client(&session.client).await; - let client_session = client - .start_session(session.session_options.clone()) + let mut client_session = client + .start_session() + .with_options(session.session_options.clone()) .await .unwrap(); + if let Some(time) = &*self.cluster_time.read().await { + client_session.advance_cluster_time(time); + } (id, Entity::Session(SessionEntity::new(client_session))) } TestFileEntity::Bucket(bucket) => { @@ -536,7 +610,7 @@ impl TestRunner { let (sender, mut receiver) = mpsc::unbounded_channel::(); let runner = self.clone(); let d = description.as_ref().to_string(); - runtime::execute(async move { + runtime::spawn(async move { while let Some(msg) = receiver.recv().await { match msg { ThreadMessage::ExecuteOperation(op) => { @@ -554,7 +628,7 @@ impl TestRunner { }); (thread.id.clone(), Entity::Thread(ThreadEntity { sender })) } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] TestFileEntity::ClientEncryption(client_enc) => { let id = client_enc.id.clone(); let opts = &client_enc.client_encryption_opts; @@ -564,16 +638,17 @@ impl TestRunner { .client() .unwrap() .clone(); - let kms_providers: HashMap = - bson::from_document(opts.kms_providers.clone()).unwrap(); - let kms_providers = fill_kms_placeholders(kms_providers); - let client_enc = crate::client_encryption::ClientEncryption::new( + let kms_providers = + crate::test::csfle::fill_kms_placeholders(opts.kms_providers.clone()); + let client_encryption = crate::client_encryption::ClientEncryption::builder( kv_client, opts.key_vault_namespace.clone(), kms_providers, ) + .key_cache_expiration(opts.key_cache_expiration) + .build() .unwrap(); - (id, Entity::ClientEncryption(Arc::new(client_enc))) + (id, Entity::ClientEncryption(Arc::new(client_encryption))) } }; self.insert_entity(&id, entity).await; @@ -668,7 +743,7 @@ impl TestRunner { .clone() } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) async fn get_client_encryption( &self, id: impl AsRef, @@ -681,36 +756,25 @@ impl TestRunner { .as_client_encryption() .clone() } -} -#[cfg(feature = "in-use-encryption-unstable")] -fn fill_kms_placeholders( - kms_providers: HashMap, -) -> KmsProviderList { - use crate::test::KMS_PROVIDERS_MAP; - - let placeholder = bson::Bson::Document(doc! { "$$placeholder": 1 }); - - let mut out = vec![]; - for (provider, mut config) in kms_providers { - for (key, value) in config.iter_mut() { - if *value == placeholder { - let new_value = KMS_PROVIDERS_MAP - .get(&provider) - .unwrap_or_else(|| panic!("missing config for {:?}", provider)) - .0 - .get(key) - .unwrap_or_else(|| { - panic!("provider config {:?} missing key {:?}", provider, key) - }) - .clone(); - *value = new_value; - } - } - let tls = KMS_PROVIDERS_MAP - .get(&provider) - .and_then(|(_, t)| t.clone()); - out.push((provider, config, tls)); + /// Removes the cursor with the given ID from the entity map. This method passes ownership of + /// the cursor to the caller so that a mutable reference to a ClientSession can be accessed from + /// the entity map simultaneously. Once the caller is finished with the cursor, it MUST be + /// returned to the test runner via the return_cursor method below. + pub(crate) async fn take_cursor(&self, id: impl AsRef) -> TestCursor { + self.entities + .write() + .await + .remove(id.as_ref()) + .unwrap() + .into_cursor() + } + + /// Returns the given cursor to the entity map. This method must be called after take_cursor. + pub(crate) async fn return_cursor(&self, id: impl AsRef, cursor: TestCursor) { + self.entities + .write() + .await + .insert(id.as_ref().into(), Entity::Cursor(cursor)); } - out } diff --git a/src/test/spec/v2_runner.rs b/src/test/spec/v2_runner.rs new file mode 100644 index 000000000..b9900e925 --- /dev/null +++ b/src/test/spec/v2_runner.rs @@ -0,0 +1,616 @@ +#[cfg(feature = "in-use-encryption")] +mod csfle; +pub(crate) mod operation; +pub(crate) mod test_event; +pub(crate) mod test_file; + +use std::{future::IntoFuture, sync::Arc, time::Duration}; + +use futures::{future::BoxFuture, FutureExt}; + +use crate::{ + bson::doc, + bson_compat::deserialize_from_bson, + coll::options::DropCollectionOptions, + concern::WriteConcern, + options::{ClientOptions, CreateCollectionOptions}, + sdam::{ServerInfo, MIN_HEARTBEAT_FREQUENCY}, + selection_criteria::SelectionCriteria, + test::{ + file_level_log, + get_client_options, + log_uncaptured, + server_version_gte, + server_version_lte, + spec::deserialize_spec_tests, + topology_is_sharded, + util::{ + fail_point::{FailPoint, FailPointGuard}, + get_default_name, + }, + EventClient, + TestClient, + }, + Client, + ClientSession, + Namespace, +}; + +use operation::OperationObject; +use test_event::CommandStartedEvent; +use test_file::{TestData, TestFile}; + +use super::Operation; + +const SKIPPED_OPERATIONS: &[&str] = &[ + "bulkWrite", + "count", + // TODO RUST-1657: unskip the download operations + "download", + "download_by_name", + "listCollectionObjects", + "listDatabaseObjects", + "mapReduce", +]; + +#[cfg(feature = "in-use-encryption")] +pub(crate) fn run_v2_tests(spec: &'static [&'static str]) -> RunV2TestsAction { + RunV2TestsAction { + spec, + skipped_files: None, + } +} + +pub(crate) struct RunV2TestsAction { + spec: &'static [&'static str], + skipped_files: Option>, +} + +impl RunV2TestsAction { + #[allow(dead_code)] + pub(crate) fn skip_files(self, skipped_files: &[&'static str]) -> Self { + Self { + skipped_files: Some(skipped_files.to_vec()), + ..self + } + } +} + +impl IntoFuture for RunV2TestsAction { + type Output = (); + type IntoFuture = BoxFuture<'static, Self::Output>; + + fn into_future(self) -> Self::IntoFuture { + async move { + for (test_file, path) in + deserialize_spec_tests::(self.spec, self.skipped_files.as_deref()) + { + run_v2_test(path, test_file).await; + } + } + .boxed() + } +} + +struct FileContext { + internal_client: TestClient, + is_csfle_test: bool, +} + +impl FileContext { + async fn new(path: &std::path::Path) -> Self { + let internal_client = Client::for_test().await; + let is_csfle_test = path.to_string_lossy().contains("client-side-encryption"); + + Self { + internal_client, + is_csfle_test, + } + } + + async fn check_topology(&self, test_file: &TestFile) -> bool { + if let Some(requirements) = &test_file.run_on { + for requirement in requirements { + if requirement.can_run_on().await { + return true; + } + } + false + } else { + true + } + } +} + +struct TestContext { + description: String, + ns: Namespace, + internal_client: TestClient, + + client: EventClient, + fail_point_guards: Vec, + session0: Option, + session1: Option, +} + +impl TestContext { + async fn new( + test_file: &TestFile, + test: &test_file::Test, + internal_client: &TestClient, + ) -> Self { + // Get the test target collection + let db_name = test_file + .database_name + .clone() + .unwrap_or_else(|| get_default_name(&test.description)); + let coll_name = test_file + .collection_name + .clone() + .unwrap_or_else(|| get_default_name(&test.description)); + let coll = internal_client.database(&db_name).collection(&coll_name); + + // Reset the test collection as needed + #[allow(unused_mut)] + let mut options = DropCollectionOptions::builder() + .write_concern(WriteConcern::majority()) + .build(); + #[cfg(feature = "in-use-encryption")] + if let Some(enc_fields) = &test_file.encrypted_fields { + options.encrypted_fields = Some(enc_fields.clone()); + } + if !(db_name.as_str() == "admin" + && topology_is_sharded().await + && server_version_gte(4, 7).await) + { + coll.drop().with_options(options).await.unwrap(); + } + + #[allow(unused_mut)] + let mut options = CreateCollectionOptions::builder() + .write_concern(WriteConcern::majority()) + .build(); + #[cfg(feature = "in-use-encryption")] + { + if let Some(schema) = &test_file.json_schema { + options.validator = Some(doc! { "$jsonSchema": schema }); + } + if let Some(enc_fields) = &test_file.encrypted_fields { + options.encrypted_fields = Some(enc_fields.clone()); + } + } + internal_client + .database(&db_name) + .create_collection(&coll_name) + .with_options(options) + .await + .unwrap(); + + // Insert test data + if let Some(data) = &test_file.data { + match data { + TestData::Single(data) => { + if !data.is_empty() { + coll.insert_many(data.clone()) + .write_concern(WriteConcern::majority()) + .await + .unwrap(); + } + } + TestData::Many(_) => panic!("{}: invalid data format", &test.description), + } + } + + // Construct the test client + let mut additional_options = match &test.client_options { + Some(opts) => ClientOptions::parse(&opts.uri).await.unwrap(), + None => ClientOptions::builder() + .hosts(get_client_options().await.hosts.clone()) + .build(), + }; + if additional_options.heartbeat_freq.is_none() { + additional_options.heartbeat_freq = Some(MIN_HEARTBEAT_FREQUENCY); + } + let builder = Client::for_test() + .options_for_multiple_mongoses( + additional_options, + test.use_multiple_mongoses.unwrap_or(false), + ) + .await + .min_heartbeat_freq(Some(Duration::from_millis(50))); + #[cfg(feature = "in-use-encryption")] + let builder = csfle::set_auto_enc(builder, test); + + let client = builder.monitor_events().await; + + // TODO RUST-900: Remove this extraneous call. + if topology_is_sharded().await + && server_version_lte(4, 2).await + && test.operations.iter().any(|op| op.name == "distinct") + { + for server_address in internal_client.options().hosts.clone() { + coll.distinct("_id", doc! {}) + .selection_criteria(SelectionCriteria::Predicate(Arc::new( + move |server_info: &ServerInfo| *server_info.address() == server_address, + ))) + .await + .unwrap(); + } + } + + // Persist fail point guards so they disable post-test. + let mut fail_point_guards: Vec = Vec::new(); + if let Some(ref fail_point) = test.fail_point { + let guard = client.enable_fail_point(fail_point.clone()).await.unwrap(); + fail_point_guards.push(guard); + } + + // Start the test sessions + let options = match test.session_options { + Some(ref options) => options.get("session0").cloned(), + None => None, + }; + let session0 = Some(client.start_session().with_options(options).await.unwrap()); + + let options = match test.session_options { + Some(ref options) => options.get("session1").cloned(), + None => None, + }; + let session1 = Some(client.start_session().with_options(options).await.unwrap()); + + Self { + description: test.description.clone(), + ns: Namespace { + db: db_name, + coll: coll_name, + }, + internal_client: internal_client.clone(), + client, + fail_point_guards, + session0, + session1, + } + } + + async fn run_operation( + &mut self, + operation: &Operation, + ) -> Option, crate::error::Error>> { + if operation.name == "endSession" { + let session = match &operation.object { + Some(OperationObject::Session0) => &mut self.session0, + Some(OperationObject::Session1) => &mut self.session1, + other => panic!("invalid object for `endSession`: {:?}", other), + }; + drop(session.take()); + tokio::time::sleep(Duration::from_secs(1)).await; + return None; + } + + let sessions = OpSessions { + session0: self.session0.as_mut(), + session1: self.session1.as_mut(), + }; + + let mut runner = OpRunner { + description: self.description.clone(), + internal_client: self.internal_client.clone(), + client: self.client.clone(), + ns: self.ns.clone(), + fail_point_guards: &mut self.fail_point_guards, + }; + + runner.run_operation(operation, sessions).await + } +} + +impl crate::test::util::TestClientBuilder { + async fn options_for_multiple_mongoses( + mut self, + mut options: ClientOptions, + use_multiple_mongoses: bool, + ) -> Self { + let is_load_balanced = options + .load_balanced + .or(get_client_options().await.load_balanced) + .unwrap_or(false); + + let default_options = if is_load_balanced { + // for serverless testing, ignore use_multiple_mongoses. + let uri = if use_multiple_mongoses { + crate::test::LOAD_BALANCED_MULTIPLE_URI + .as_ref() + .expect("MULTI_MONGOS_LB_URI is required") + } else { + crate::test::LOAD_BALANCED_SINGLE_URI + .as_ref() + .expect("SINGLE_MONGOS_LB_URI is required") + }; + let mut o = ClientOptions::parse(uri).await.unwrap(); + crate::test::update_options_for_testing(&mut o); + o + } else { + get_client_options().await.clone() + }; + options.merge(default_options); + + self = self.options(options); + if !use_multiple_mongoses { + self = self.use_single_mongos(); + } + self + } +} + +pub(crate) struct OpSessions<'a> { + session0: Option<&'a mut ClientSession>, + session1: Option<&'a mut ClientSession>, +} + +pub(crate) struct OpRunner<'a> { + description: String, + internal_client: TestClient, + + client: EventClient, + ns: Namespace, + fail_point_guards: &'a mut Vec, +} + +impl OpRunner<'_> { + pub(crate) async fn run_operation( + &mut self, + operation: &Operation, + mut sessions: OpSessions<'_>, + ) -> Option, crate::error::Error>> { + if operation.name == "withTransaction" { + if !matches!(&operation.object, Some(OperationObject::Session0)) { + panic!("invalid object for withTransaction: {:?}", operation.object); + } + return Some(operation.execute_recursive(self, sessions).await); + } + + let db = match &operation.database_options { + Some(options) => self + .client + .database_with_options(&self.ns.db, options.clone()), + None => self.client.database(&self.ns.db), + }; + let coll = match &operation.collection_options { + Some(options) => db.collection_with_options(&self.ns.coll, options.clone()), + None => db.collection(&self.ns.coll), + }; + + let session = match operation.session.as_deref() { + Some("session0") => sessions.session0.as_deref_mut(), + Some("session1") => sessions.session1.as_deref_mut(), + Some(other) => panic!("unknown session name: {}", other), + None => None, + }; + + Some(match operation.object { + Some(OperationObject::Collection) | None => { + let result = operation.execute_on_collection(&coll, session).await; + // This test (in src/test/spec/json/sessions/server-support.json) runs two + // operations with implicit sessions in sequence and then checks to see if they + // used the same lsid. We delay for one second to ensure that the + // implicit session used in the first operation is returned to the pool before + // the second operation is executed. + if self.description == "Server supports implicit sessions" { + tokio::time::sleep(Duration::from_secs(1)).await; + } + result + } + Some(OperationObject::Database) => operation.execute_on_database(&db, session).await, + Some(OperationObject::Client) => operation.execute_on_client(&self.client).await, + Some(OperationObject::Session0) => { + operation + .execute_on_session(sessions.session0.as_mut().unwrap()) + .await + } + Some(OperationObject::Session1) => { + operation + .execute_on_session(sessions.session1.as_mut().unwrap()) + .await + } + Some(OperationObject::TestRunner) => { + match operation.name.as_str() { + "assertDifferentLsidOnLastTwoCommands" => { + assert_different_lsid_on_last_two_commands(&self.client) + } + "assertSameLsidOnLastTwoCommands" => { + assert_same_lsid_on_last_two_commands(&self.client) + } + "assertSessionDirty" => { + assert!(session.unwrap().is_dirty()) + } + "assertSessionNotDirty" => { + assert!(!session.unwrap().is_dirty()) + } + "assertSessionTransactionState" + | "assertSessionPinned" + | "assertSessionUnpinned" => { + operation + .execute_on_session(session.unwrap()) + .await + .unwrap(); + } + "assertCollectionExists" + | "assertCollectionNotExists" + | "assertIndexExists" + | "assertIndexNotExists" => { + operation + .execute_on_client(&self.internal_client) + .await + .unwrap(); + } + "targetedFailPoint" => { + let fail_point: FailPoint = deserialize_from_bson( + operation + .execute_on_client(&self.internal_client) + .await + .unwrap() + .unwrap(), + ) + .unwrap(); + + let selection_criteria = session + .unwrap() + .transaction + .pinned_mongos() + .cloned() + .unwrap_or_else(|| panic!("ClientSession is not pinned")); + + let guard = self + .client + .enable_fail_point(fail_point.selection_criteria(selection_criteria)) + .await + .unwrap(); + self.fail_point_guards.push(guard); + } + "wait" => operation.execute().await, + other => panic!("unknown operation: {}", other), + } + return None; + } + Some(OperationObject::GridfsBucket) => { + panic!("unsupported operation: {}", operation.name) + } + }) + } +} + +async fn run_v2_test(path: std::path::PathBuf, test_file: TestFile) { + let file_ctx = FileContext::new(&path).await; + + file_level_log(format!("Running tests from {}", path.display(),)); + + if !file_ctx.check_topology(&test_file).await { + log_uncaptured("Client topology not compatible with test"); + return; + } + + for test in &test_file.tests { + if let Ok(description) = std::env::var("TEST_DESCRIPTION") { + if !test + .description + .to_lowercase() + .contains(&description.to_lowercase()) + { + continue; + } + } + + log_uncaptured(format!("Running {}", &test.description)); + + if test + .operations + .iter() + .any(|operation| SKIPPED_OPERATIONS.contains(&operation.name.as_str())) + { + log_uncaptured(format!( + "skipping {}: unsupported operation", + test.description + )); + continue; + } + + if let Some(skip_reason) = &test.skip_reason { + log_uncaptured(format!("skipping {}: {}", test.description, skip_reason)); + continue; + } + + match file_ctx + .internal_client + .database("admin") + .run_command(doc! { "killAllSessions": [] }) + .await + { + Ok(_) => {} + Err(err) => match err.sdam_code() { + Some(11601) => {} + _ => panic!("{}: killAllSessions failed", test.description), + }, + } + + #[cfg(feature = "in-use-encryption")] + csfle::populate_key_vault(&file_ctx.internal_client, test_file.key_vault_data.as_ref()) + .await; + + let mut test_ctx = TestContext::new(&test_file, test, &file_ctx.internal_client).await; + let session0_lsid = test_ctx.session0.as_ref().unwrap().id().clone(); + let session1_lsid = test_ctx.session1.as_ref().unwrap().id().clone(); + + for operation in &test.operations { + let result = match test_ctx.run_operation(operation).await { + Some(r) => r, + None => continue, + }; + + operation.assert_result_matches(&result, &test.description); + } + + test_ctx.session0.take(); + test_ctx.session1.take(); + + // wait for the transaction in progress to be aborted implicitly when the session is dropped + if test.description.as_str() == "implicit abort" { + tokio::time::sleep(Duration::from_secs(1)).await; + } + + if let Some(expectations) = &test.expectations { + let events: Vec = test_ctx + .client + .events + .get_all_command_started_events() + .into_iter() + .map(Into::into) + .collect(); + + assert!( + events.len() >= expectations.len(), + "[{}] expected events \n{:#?}\n got events\n{:#?}", + test.description, + expectations, + events + ); + for (actual_event, expected_event) in events.iter().zip(expectations.iter()) { + let result = + actual_event.matches_expected(expected_event, &session0_lsid, &session1_lsid); + assert!( + result.is_ok(), + "[{}] {}", + test.description, + result.unwrap_err() + ); + } + } + + if let Some(outcome) = &test.outcome { + outcome + .assert_matches_actual( + &test_ctx.ns.db, + &test_ctx.ns.coll, + if file_ctx.is_csfle_test { + &test_ctx.internal_client + } else { + &test_ctx.client + }, + ) + .await; + } + } +} + +fn assert_different_lsid_on_last_two_commands(client: &EventClient) { + let events = client.events.get_all_command_started_events(); + let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); + let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); + assert_ne!(lsid1, lsid2); +} + +fn assert_same_lsid_on_last_two_commands(client: &EventClient) { + let events = client.events.get_all_command_started_events(); + let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); + let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); + assert_eq!(lsid1, lsid2); +} diff --git a/src/test/spec/v2_runner/csfle.rs b/src/test/spec/v2_runner/csfle.rs index f373c7800..af752f7a4 100644 --- a/src/test/spec/v2_runner/csfle.rs +++ b/src/test/spec/v2_runner/csfle.rs @@ -1,10 +1,10 @@ -use bson::{doc, Document}; +use crate::bson::{doc, Document}; use mongocrypt::ctx::KmsProvider; use crate::{ coll::options::CollectionOptions, options::WriteConcern, - test::{util::TestClientBuilder, KMS_PROVIDERS_MAP}, + test::{csfle::AWS_KMS, util::TestClientBuilder}, Client, }; @@ -17,12 +17,12 @@ pub(crate) async fn populate_key_vault(client: &Client, kv_data: Option<&Vec( "datakeys", CollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) + .write_concern(WriteConcern::majority()) .build(), ); - datakeys.drop(None).await.unwrap(); + datakeys.drop().await.unwrap(); if !kv_data.is_empty() { - datakeys.insert_many(kv_data, None).await.unwrap(); + datakeys.insert_many(kv_data).await.unwrap(); } } } @@ -37,55 +37,51 @@ pub(crate) fn set_auto_enc(builder: TestClientBuilder, test: &Test) -> TestClien } else { return builder; }; + let kms_providers = &mut enc_opts.kms_providers; - for prov in [ - KmsProvider::Aws, - KmsProvider::Azure, - KmsProvider::Gcp, - KmsProvider::Kmip, - ] { - if kms_providers.credentials().contains_key(&prov) { - let opts = KMS_PROVIDERS_MAP.get(&prov).unwrap().clone(); - kms_providers.set(prov, opts.0, opts.1); - } - } - let aws_tls = KMS_PROVIDERS_MAP - .get(&KmsProvider::Aws) - .and_then(|(_, t)| t.as_ref()); + kms_providers.set_test_options(); + let aws_id = std::env::var("CSFLE_AWS_TEMP_ACCESS_KEY_ID").ok(); let aws_key = std::env::var("CSFLE_AWS_TEMP_SECRET_ACCESS_KEY").ok(); let aws_token = std::env::var("CSFLE_AWS_TEMP_SESSION_TOKEN").ok(); + if kms_providers .credentials() - .contains_key(&KmsProvider::Other("awsTemporary".to_string())) + .contains_key(&KmsProvider::other("awsTemporary")) { kms_providers.set( - KmsProvider::Aws, + KmsProvider::aws(), doc! { "accessKeyId": aws_id.unwrap(), "secretAccessKey": aws_key.unwrap(), "sessionToken": aws_token.unwrap(), }, - aws_tls.cloned(), + AWS_KMS.clone().2, ); - kms_providers.clear(&KmsProvider::Other("awsTemporary".to_string())); + kms_providers.clear(&KmsProvider::other("awsTemporary")); } else if kms_providers .credentials() - .contains_key(&KmsProvider::Other( - "awsTemporaryNoSessionToken".to_string(), - )) + .contains_key(&KmsProvider::other("awsTemporaryNoSessionToken")) { kms_providers.set( - KmsProvider::Aws, + KmsProvider::aws(), doc! { "accessKeyId": aws_id.unwrap(), "secretAccessKey": aws_key.unwrap(), }, - aws_tls.cloned(), + AWS_KMS.clone().2, ); - kms_providers.clear(&KmsProvider::Other( - "awsTemporaryNoSessionToken".to_string(), - )); + kms_providers.clear(&KmsProvider::other("awsTemporaryNoSessionToken")); } + + if let Ok(val) = std::env::var("CRYPT_SHARED_LIB_PATH") { + if !val.is_empty() { + enc_opts + .extra_options + .get_or_insert_with(|| doc! {}) + .insert("cryptSharedLibPath", val); + } + } + builder.encrypted_options(enc_opts) } diff --git a/src/test/spec/v2_runner/mod.rs b/src/test/spec/v2_runner/mod.rs deleted file mode 100644 index b1b8f0c22..000000000 --- a/src/test/spec/v2_runner/mod.rs +++ /dev/null @@ -1,557 +0,0 @@ -#[cfg(feature = "in-use-encryption-unstable")] -mod csfle; -pub(crate) mod operation; -pub(crate) mod test_event; -pub(crate) mod test_file; - -use std::{future::IntoFuture, ops::Deref, sync::Arc, time::Duration}; - -use futures::{future::BoxFuture, FutureExt}; -use semver::VersionReq; - -use crate::{ - bson::{doc, from_bson}, - coll::options::{DistinctOptions, DropCollectionOptions}, - concern::WriteConcern, - options::{ClientOptions, CreateCollectionOptions, InsertManyOptions}, - runtime, - sdam::{ServerInfo, MIN_HEARTBEAT_FREQUENCY}, - selection_criteria::SelectionCriteria, - test::{ - file_level_log, - log_uncaptured, - spec::deserialize_spec_tests, - util::{get_default_name, FailPointGuard}, - EventClient, - TestClient, - CLIENT_OPTIONS, - SERVERLESS, - }, - Client, - ClientSession, - Namespace, -}; - -use operation::OperationObject; -use test_event::CommandStartedEvent; -use test_file::{TestData, TestFile}; - -use super::Operation; - -const SKIPPED_OPERATIONS: &[&str] = &[ - "bulkWrite", - "count", - // TODO RUST-1657: unskip the download operations - "download", - "download_by_name", - "listCollectionObjects", - "listDatabaseObjects", - "mapReduce", -]; - -pub(crate) fn run_v2_tests(spec: &'static [&'static str]) -> RunV2TestsAction { - RunV2TestsAction { - spec, - skipped_files: None, - } -} - -pub(crate) struct RunV2TestsAction { - spec: &'static [&'static str], - skipped_files: Option>, -} - -impl RunV2TestsAction { - pub(crate) fn skip_files(self, skipped_files: &[&'static str]) -> Self { - Self { - skipped_files: Some(skipped_files.to_vec()), - ..self - } - } -} - -impl IntoFuture for RunV2TestsAction { - type Output = (); - type IntoFuture = BoxFuture<'static, Self::Output>; - - fn into_future(self) -> Self::IntoFuture { - async move { - for (test_file, path) in - deserialize_spec_tests::(self.spec, self.skipped_files.as_deref()) - { - run_v2_test(path, test_file).await; - } - } - .boxed() - } -} - -struct FileContext { - internal_client: TestClient, - is_csfle_test: bool, -} - -impl FileContext { - async fn new(path: &std::path::Path) -> Self { - let internal_client = TestClient::new().await; - let is_csfle_test = path.to_string_lossy().contains("client-side-encryption"); - - Self { - internal_client, - is_csfle_test, - } - } - - fn check_topology(&self, test_file: &TestFile) -> bool { - if let Some(requirements) = &test_file.run_on { - return requirements - .iter() - .any(|run_on| run_on.can_run_on(&self.internal_client)); - } - true - } -} - -struct TestContext { - description: String, - ns: Namespace, - internal_client: TestClient, - client: EventClient, - fail_point_guards: Vec, - session0: Option, - session1: Option, -} - -impl TestContext { - async fn new( - test_file: &TestFile, - test: &test_file::Test, - internal_client: &TestClient, - ) -> Self { - // Get the test target collection - let db_name = test_file - .database_name - .clone() - .unwrap_or_else(|| get_default_name(&test.description)); - let coll_name = test_file - .collection_name - .clone() - .unwrap_or_else(|| get_default_name(&test.description)); - let coll = internal_client.database(&db_name).collection(&coll_name); - - // Reset the test collection as needed - #[allow(unused_mut)] - let mut options = DropCollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(); - #[cfg(feature = "in-use-encryption-unstable")] - if let Some(enc_fields) = &test_file.encrypted_fields { - options.encrypted_fields = Some(enc_fields.clone()); - } - let req = VersionReq::parse(">=4.7").unwrap(); - if !(db_name.as_str() == "admin" - && internal_client.is_sharded() - && req.matches(&internal_client.server_version)) - { - coll.drop(options).await.unwrap(); - } - - #[allow(unused_mut)] - let mut options = CreateCollectionOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(); - #[cfg(feature = "in-use-encryption-unstable")] - { - if let Some(schema) = &test_file.json_schema { - options.validator = Some(doc! { "$jsonSchema": schema }); - } - if let Some(enc_fields) = &test_file.encrypted_fields { - options.encrypted_fields = Some(enc_fields.clone()); - } - } - internal_client - .database(&db_name) - .create_collection(&coll_name, options) - .await - .unwrap(); - - // Insert test data - if let Some(data) = &test_file.data { - match data { - TestData::Single(data) => { - if !data.is_empty() { - let options = InsertManyOptions::builder() - .write_concern(WriteConcern::MAJORITY) - .build(); - coll.insert_many(data.clone(), options).await.unwrap(); - } - } - TestData::Many(_) => panic!("{}: invalid data format", &test.description), - } - } - - // Construct the test client - let mut additional_options = match &test.client_options { - Some(opts) => ClientOptions::parse_uri(&opts.uri, None).await.unwrap(), - None => ClientOptions::builder() - .hosts(CLIENT_OPTIONS.get().await.hosts.clone()) - .build(), - }; - if additional_options.heartbeat_freq.is_none() { - additional_options.heartbeat_freq = Some(MIN_HEARTBEAT_FREQUENCY); - } - let builder = Client::test_builder() - .additional_options( - additional_options, - test.use_multiple_mongoses.unwrap_or(false), - ) - .await - .min_heartbeat_freq(Some(Duration::from_millis(50))); - #[cfg(feature = "in-use-encryption-unstable")] - let builder = csfle::set_auto_enc(builder, test); - let client = builder.event_client().build().await; - - // TODO RUST-900: Remove this extraneous call. - if internal_client.is_sharded() - && internal_client.server_version_lte(4, 2) - && test.operations.iter().any(|op| op.name == "distinct") - { - for server_address in internal_client.options().hosts.clone() { - let options = DistinctOptions::builder() - .selection_criteria(Some(SelectionCriteria::Predicate(Arc::new( - move |server_info: &ServerInfo| *server_info.address() == server_address, - )))) - .build(); - coll.distinct("_id", None, options).await.unwrap(); - } - } - - // Persist fail point guards so they disable post-test. - let mut fail_point_guards: Vec = Vec::new(); - if let Some(fail_point) = &test.fail_point { - fail_point_guards.push(fail_point.enable(client.deref(), None).await.unwrap()); - } - - // Start the test sessions - let options = match test.session_options { - Some(ref options) => options.get("session0").cloned(), - None => None, - }; - let session0 = Some(client.start_session(options).await.unwrap()); - - let options = match test.session_options { - Some(ref options) => options.get("session1").cloned(), - None => None, - }; - let session1 = Some(client.start_session(options).await.unwrap()); - - Self { - description: test.description.clone(), - ns: Namespace { - db: db_name, - coll: coll_name, - }, - internal_client: internal_client.clone(), - client, - fail_point_guards, - session0, - session1, - } - } - - async fn run_operation( - &mut self, - operation: &Operation, - ) -> Option, crate::error::Error>> { - if operation.name == "endSession" { - let session = match &operation.object { - Some(OperationObject::Session0) => &mut self.session0, - Some(OperationObject::Session1) => &mut self.session1, - other => panic!("invalid object for `endSession`: {:?}", other), - }; - drop(session.take()); - runtime::delay_for(Duration::from_secs(1)).await; - return None; - } - - let sessions = OpSessions { - session0: self.session0.as_mut(), - session1: self.session1.as_mut(), - }; - - let mut runner = OpRunner { - description: self.description.clone(), - internal_client: self.internal_client.clone(), - client: self.client.clone(), - ns: self.ns.clone(), - fail_point_guards: &mut self.fail_point_guards, - }; - - runner.run_operation(operation, sessions).await - } -} - -pub(crate) struct OpSessions<'a> { - session0: Option<&'a mut ClientSession>, - session1: Option<&'a mut ClientSession>, -} - -pub(crate) struct OpRunner<'a> { - description: String, - internal_client: TestClient, - client: EventClient, - ns: Namespace, - fail_point_guards: &'a mut Vec, -} - -impl<'a> OpRunner<'a> { - pub(crate) async fn run_operation<'b>( - &mut self, - operation: &Operation, - mut sessions: OpSessions<'b>, - ) -> Option, crate::error::Error>> { - if operation.name == "withTransaction" { - if !matches!(&operation.object, Some(OperationObject::Session0)) { - panic!("invalid object for withTransaction: {:?}", operation.object); - } - return Some(operation.execute_recursive(self, sessions).await); - } - - let db = match &operation.database_options { - Some(options) => self - .client - .database_with_options(&self.ns.db, options.clone()), - None => self.client.database(&self.ns.db), - }; - let coll = match &operation.collection_options { - Some(options) => db.collection_with_options(&self.ns.coll, options.clone()), - None => db.collection(&self.ns.coll), - }; - - let session = match operation.session.as_deref() { - Some("session0") => sessions.session0.as_deref_mut(), - Some("session1") => sessions.session1.as_deref_mut(), - Some(other) => panic!("unknown session name: {}", other), - None => None, - }; - - Some(match operation.object { - Some(OperationObject::Collection) | None => { - let result = operation.execute_on_collection(&coll, session).await; - // This test (in src/test/spec/json/sessions/server-support.json) runs two - // operations with implicit sessions in sequence and then checks to see if they - // used the same lsid. We delay for one second to ensure that the - // implicit session used in the first operation is returned to the pool before - // the second operation is executed. - if self.description == "Server supports implicit sessions" { - runtime::delay_for(Duration::from_secs(1)).await; - } - result - } - Some(OperationObject::Database) => operation.execute_on_database(&db, session).await, - Some(OperationObject::Client) => operation.execute_on_client(&self.client).await, - Some(OperationObject::Session0) => { - operation - .execute_on_session(sessions.session0.as_mut().unwrap()) - .await - } - Some(OperationObject::Session1) => { - operation - .execute_on_session(sessions.session1.as_mut().unwrap()) - .await - } - Some(OperationObject::TestRunner) => { - match operation.name.as_str() { - "assertDifferentLsidOnLastTwoCommands" => { - assert_different_lsid_on_last_two_commands(&self.client) - } - "assertSameLsidOnLastTwoCommands" => { - assert_same_lsid_on_last_two_commands(&self.client) - } - "assertSessionDirty" => { - assert!(session.unwrap().is_dirty()) - } - "assertSessionNotDirty" => { - assert!(!session.unwrap().is_dirty()) - } - "assertSessionTransactionState" - | "assertSessionPinned" - | "assertSessionUnpinned" => { - operation - .execute_on_session(session.unwrap()) - .await - .unwrap(); - } - "assertCollectionExists" - | "assertCollectionNotExists" - | "assertIndexExists" - | "assertIndexNotExists" => { - operation - .execute_on_client(&self.internal_client) - .await - .unwrap(); - } - "targetedFailPoint" => { - let fail_point = from_bson( - operation - .execute_on_client(&self.internal_client) - .await - .unwrap() - .unwrap(), - ) - .unwrap(); - - let selection_criteria = session - .unwrap() - .transaction - .pinned_mongos() - .cloned() - .unwrap_or_else(|| panic!("ClientSession is not pinned")); - - self.fail_point_guards.push( - self.client - .deref() - .enable_failpoint(fail_point, Some(selection_criteria)) - .await - .unwrap(), - ); - } - other => panic!("unknown operation: {}", other), - } - return None; - } - Some(OperationObject::GridfsBucket) => { - panic!("unsupported operation: {}", operation.name) - } - }) - } -} - -async fn run_v2_test(path: std::path::PathBuf, test_file: TestFile) { - let file_ctx = FileContext::new(&path).await; - - file_level_log(format!("Running tests from {}", path.display(),)); - - if !file_ctx.check_topology(&test_file) { - log_uncaptured("Client topology not compatible with test"); - return; - } - - for test in &test_file.tests { - log_uncaptured(format!("Running {}", &test.description)); - - if test - .operations - .iter() - .any(|operation| SKIPPED_OPERATIONS.contains(&operation.name.as_str())) - { - log_uncaptured(format!( - "skipping {}: unsupported operation", - test.description - )); - continue; - } - - if let Some(skip_reason) = &test.skip_reason { - log_uncaptured(format!("skipping {}: {}", test.description, skip_reason)); - continue; - } - - // `killAllSessions` isn't supported on serverless. - // TODO CLOUDP-84298 remove this conditional. - if !*SERVERLESS { - match file_ctx - .internal_client - .database("admin") - .run_command(doc! { "killAllSessions": [] }, None) - .await - { - Ok(_) => {} - Err(err) => match err.sdam_code() { - Some(11601) => {} - _ => panic!("{}: killAllSessions failed", test.description), - }, - } - } - - #[cfg(feature = "in-use-encryption-unstable")] - csfle::populate_key_vault(&file_ctx.internal_client, test_file.key_vault_data.as_ref()) - .await; - - let mut test_ctx = TestContext::new(&test_file, test, &file_ctx.internal_client).await; - let session0_lsid = test_ctx.session0.as_ref().unwrap().id().clone(); - let session1_lsid = test_ctx.session1.as_ref().unwrap().id().clone(); - - for operation in &test.operations { - let result = match test_ctx.run_operation(operation).await { - Some(r) => r, - None => continue, - }; - - operation.assert_result_matches(&result, &test.description); - } - - test_ctx.session0.take(); - test_ctx.session1.take(); - - // wait for the transaction in progress to be aborted implicitly when the session is dropped - if test.description.as_str() == "implicit abort" { - runtime::delay_for(Duration::from_secs(1)).await; - } - - if let Some(expectations) = &test.expectations { - let events: Vec = test_ctx - .client - .get_all_command_started_events() - .into_iter() - .map(Into::into) - .collect(); - - assert!( - events.len() >= expectations.len(), - "[{}] expected events \n{:#?}\n got events\n{:#?}", - test.description, - expectations, - events - ); - for (actual_event, expected_event) in events.iter().zip(expectations.iter()) { - let result = - actual_event.matches_expected(expected_event, &session0_lsid, &session1_lsid); - assert!( - result.is_ok(), - "[{}] {}", - test.description, - result.unwrap_err() - ); - } - } - - if let Some(outcome) = &test.outcome { - outcome - .assert_matches_actual( - &test_ctx.ns.db, - &test_ctx.ns.coll, - if file_ctx.is_csfle_test { - &test_ctx.internal_client - } else { - &test_ctx.client - }, - ) - .await; - } - } -} - -fn assert_different_lsid_on_last_two_commands(client: &EventClient) { - let events = client.get_all_command_started_events(); - let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); - let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); - assert_ne!(lsid1, lsid2); -} - -fn assert_same_lsid_on_last_two_commands(client: &EventClient) { - let events = client.get_all_command_started_events(); - let lsid1 = events[events.len() - 1].command.get("lsid").unwrap(); - let lsid2 = events[events.len() - 2].command.get("lsid").unwrap(); - assert_eq!(lsid1, lsid2); -} diff --git a/src/test/spec/v2_runner/operation.rs b/src/test/spec/v2_runner/operation.rs index 0ca4e055a..5fc6f3cc8 100644 --- a/src/test/spec/v2_runner/operation.rs +++ b/src/test/spec/v2_runner/operation.rs @@ -1,11 +1,13 @@ -use std::{collections::HashMap, convert::TryInto, fmt::Debug, ops::Deref}; +use std::{collections::HashMap, convert::TryInto, fmt::Debug, ops::Deref, time::Duration}; use futures::{future::BoxFuture, stream::TryStreamExt, FutureExt}; use serde::{de::Deserializer, Deserialize}; use crate::{ - bson::{doc, to_bson, Bson, Deserializer as BsonDeserializer, Document}, + action::Action, + bson::{doc, Bson, Deserializer as BsonDeserializer, Document}, client::session::TransactionState, + db::options::ListCollectionsOptions, error::Result, options::{ AggregateOptions, @@ -26,8 +28,6 @@ use crate::{ IndexOptions, InsertManyOptions, InsertOneOptions, - ListCollectionsOptions, - ListDatabasesOptions, ListIndexesOptions, ReadConcern, ReplaceOptions, @@ -36,7 +36,7 @@ use crate::{ UpdateOptions, }, selection_criteria::{ReadPreference, SelectionCriteria}, - test::{assert_matches, log_uncaptured, FailPoint, TestClient}, + test::{assert_matches, log_uncaptured, util::fail_point::FailPoint, TestClient}, ClientSession, Collection, Database, @@ -46,6 +46,10 @@ use crate::{ use super::{OpRunner, OpSessions}; pub(crate) trait TestOperation: Debug + Send + Sync { + fn execute(&self) -> BoxFuture<'_, ()> { + todo!() + } + fn execute_on_collection<'a>( &'a self, _collection: &'a Collection, @@ -162,7 +166,7 @@ impl Operation { .iter() .for_each(|label| assert!(!labels.contains(label))); } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] if let Some(t) = &operation_error.is_timeout_error { assert_eq!( *t, @@ -203,7 +207,7 @@ pub(crate) struct OperationError { pub(crate) error_code: Option, pub(crate) error_labels_contain: Option>, pub(crate) error_labels_omit: Option>, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) is_timeout_error: Option, } @@ -287,6 +291,7 @@ impl<'de> Deserialize<'de> for Operation { "assertIndexNotExists" => deserialize_op::(definition.arguments), "watch" => deserialize_op::(definition.arguments), "withTransaction" => deserialize_op::(definition.arguments), + "wait" => deserialize_op::(definition.arguments), _ => Ok(Box::new(UnimplementedOperation) as Box), } .map_err(|e| serde::de::Error::custom(format!("{}", e)))?; @@ -306,7 +311,7 @@ impl<'de> Deserialize<'de> for Operation { fn deserialize_op<'de, 'a, Op: TestOperation + Deserialize<'de> + 'a>( arguments: Document, -) -> std::result::Result, bson::de::Error> { +) -> std::result::Result, crate::bson_compat::DeError> { Ok(Box::new(Op::deserialize(BsonDeserializer::new( Bson::Document(arguments), ))?)) @@ -334,23 +339,12 @@ impl TestOperation for DeleteMany { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .delete_many_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .delete_many(self.filter.clone(), self.options.clone()) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .delete_many(self.filter.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -371,19 +365,12 @@ impl TestOperation for DeleteOne { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .delete_one_with_session(self.filter.clone(), self.options.clone(), session) - .await? - } - None => { - collection - .delete_one(self.filter.clone(), self.options.clone()) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .delete_one(self.filter.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -404,20 +391,19 @@ impl TestOperation for Find { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let act = collection + .find(self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()); let result = match session { Some(session) => { - let mut cursor = collection - .find_with_session(self.filter.clone(), self.options.clone(), session) - .await?; + let mut cursor = act.session(&mut *session).await?; cursor .stream(session) .try_collect::>() .await? } None => { - let cursor = collection - .find(self.filter.clone(), self.options.clone()) - .await?; + let cursor = act.await?; cursor.try_collect::>().await? } }; @@ -444,20 +430,17 @@ impl TestOperation for InsertMany { let options = self.options.clone(); async move { - let result = match session { - Some(session) => { - collection - .insert_many_with_session(documents, options, session) - .await? - } - None => collection.insert_many(documents, options).await?, - }; + let result = collection + .insert_many(documents) + .with_options(options) + .optional(session, |a, s| a.session(s)) + .await?; let ids: HashMap = result .inserted_ids .into_iter() .map(|(k, v)| (k.to_string(), v)) .collect(); - let ids = bson::to_bson(&ids)?; + let ids = crate::bson_compat::serialize_to_bson(&ids)?; Ok(Some(Bson::from(doc! { "insertedIds": ids }))) } .boxed() @@ -480,15 +463,12 @@ impl TestOperation for InsertOne { let document = self.document.clone(); let options = self.options.clone(); async move { - let result = match session { - Some(session) => { - collection - .insert_one_with_session(document, options, session) - .await? - } - None => collection.insert_one(document, options).await?, - }; - let result = bson::to_bson(&result)?; + let result = collection + .insert_one(document) + .with_options(options) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -510,28 +490,12 @@ impl TestOperation for UpdateMany { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .update_many_with_session( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .update_many( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .update_many(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -553,28 +517,12 @@ impl TestOperation for UpdateOne { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .update_one_with_session( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .update_one( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .update_one(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -596,24 +544,19 @@ impl TestOperation for Aggregate { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let act = collection + .aggregate(self.pipeline.clone()) + .with_options(self.options.clone()); let result = match session { Some(session) => { - let mut cursor = collection - .aggregate_with_session( - self.pipeline.clone(), - self.options.clone(), - session, - ) - .await?; + let mut cursor = act.session(&mut *session).await?; cursor .stream(session) .try_collect::>() .await? } None => { - let cursor = collection - .aggregate(self.pipeline.clone(), self.options.clone()) - .await?; + let cursor = act.await?; cursor.try_collect::>().await? } }; @@ -628,24 +571,19 @@ impl TestOperation for Aggregate { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let action = database + .aggregate(self.pipeline.clone()) + .with_options(self.options.clone()); let result = match session { Some(session) => { - let mut cursor = database - .aggregate_with_session( - self.pipeline.clone(), - self.options.clone(), - session, - ) - .await?; + let mut cursor = action.session(&mut *session).await?; cursor .stream(session) .try_collect::>() .await? } None => { - let cursor = database - .aggregate(self.pipeline.clone(), self.options.clone()) - .await?; + let cursor = action.await?; cursor.try_collect::>().await? } }; @@ -672,23 +610,11 @@ impl TestOperation for Distinct { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .distinct_with_session( - &self.field_name, - self.filter.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .distinct(&self.field_name, self.filter.clone(), self.options.clone()) - .await? - } - }; + let result = collection + .distinct(&self.field_name, self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; Ok(Some(Bson::Array(result))) } .boxed() @@ -697,7 +623,7 @@ impl TestOperation for Distinct { #[derive(Debug, Deserialize)] pub(super) struct CountDocuments { - filter: Document, + filter: Option, #[serde(flatten)] options: Option, } @@ -709,22 +635,11 @@ impl TestOperation for CountDocuments { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .count_documents_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .count_documents(self.filter.clone(), self.options.clone()) - .await? - } - }; + let result = collection + .count_documents(self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()) + .optional(session, |a, v| a.session(v)) + .await?; Ok(Some(Bson::Int64(result.try_into().unwrap()))) } .boxed() @@ -745,7 +660,8 @@ impl TestOperation for EstimatedDocumentCount { ) -> BoxFuture<'a, Result>> { async move { let result = collection - .estimated_document_count(self.options.clone()) + .estimated_document_count() + .with_options(self.options.clone()) .await?; Ok(Some(Bson::Int64(result.try_into().unwrap()))) } @@ -767,17 +683,12 @@ impl TestOperation for FindOne { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let action = collection + .find_one(self.filter.clone().unwrap_or_default()) + .with_options(self.options.clone()); let result = match session { - Some(session) => { - collection - .find_one_with_session(self.filter.clone(), self.options.clone(), session) - .await? - } - None => { - collection - .find_one(self.filter.clone(), self.options.clone()) - .await? - } + Some(session) => action.session(session).await?, + None => action.await?, }; match result { Some(result) => Ok(Some(Bson::from(result))), @@ -790,7 +701,6 @@ impl TestOperation for FindOne { #[derive(Debug, Deserialize)] pub(super) struct ListCollections { - filter: Option, #[serde(flatten)] options: Option, } @@ -805,22 +715,21 @@ impl TestOperation for ListCollections { let result = match session { Some(session) => { let mut cursor = database - .list_collections_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) + .list_collections() + .with_options(self.options.clone()) + .session(&mut *session) .await?; cursor.stream(session).try_collect::>().await? } None => { let cursor = database - .list_collections(self.filter.clone(), self.options.clone()) + .list_collections() + .with_options(self.options.clone()) .await?; cursor.try_collect::>().await? } }; - Ok(Some(bson::to_bson(&result)?)) + Ok(Some(crate::bson_compat::serialize_to_bson(&result)?)) } .boxed() } @@ -838,13 +747,12 @@ impl TestOperation for ListCollectionNames { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let action = database + .list_collection_names() + .optional(self.filter.clone(), |b, f| b.filter(f)); let result = match session { - Some(session) => { - database - .list_collection_names_with_session(self.filter.clone(), session) - .await? - } - None => database.list_collection_names(self.filter.clone()).await?, + Some(session) => action.session(session).await?, + None => action.await?, }; let result: Vec = result.into_iter().map(|s| s.into()).collect(); Ok(Some(result.into())) @@ -868,28 +776,12 @@ impl TestOperation for ReplaceOne { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .replace_one_with_session( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .replace_one( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .replace_one(self.filter.clone(), self.replacement.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -911,28 +803,12 @@ impl TestOperation for FindOneAndUpdate { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .find_one_and_update_with_session( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .find_one_and_update( - self.filter.clone(), - self.update.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .find_one_and_update(self.filter.clone(), self.update.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -954,28 +830,12 @@ impl TestOperation for FindOneAndReplace { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .find_one_and_replace_with_session( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .find_one_and_replace( - self.filter.clone(), - self.replacement.clone(), - self.options.clone(), - ) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .find_one_and_replace(self.filter.clone(), self.replacement.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -996,23 +856,12 @@ impl TestOperation for FindOneAndDelete { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - collection - .find_one_and_delete_with_session( - self.filter.clone(), - self.options.clone(), - session, - ) - .await? - } - None => { - collection - .find_one_and_delete(self.filter.clone(), self.options.clone()) - .await? - } - }; - let result = bson::to_bson(&result)?; + let result = collection + .find_one_and_delete(self.filter.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; + let result = crate::bson_compat::serialize_to_bson(&result)?; Ok(Some(result)) } .boxed() @@ -1026,8 +875,16 @@ pub(super) struct TargetedFailPoint { } impl TestOperation for TargetedFailPoint { - fn execute_on_client<'a>(&'a self, _client: &'a TestClient) -> BoxFuture>> { - async move { Ok(Some(to_bson(&self.fail_point)?)) }.boxed() + fn execute_on_client<'a>( + &'a self, + _client: &'a TestClient, + ) -> BoxFuture<'a, Result>> { + async move { + let command_document = + crate::bson_compat::serialize_to_document(&self.fail_point).unwrap(); + Ok(Some(command_document.into())) + } + .boxed() } } @@ -1065,9 +922,8 @@ impl TestOperation for AssertSessionUnpinned { #[derive(Debug, Deserialize)] pub(super) struct ListDatabases { - filter: Option, #[serde(flatten)] - options: Option, + options: Option, } impl TestOperation for ListDatabases { @@ -1077,9 +933,10 @@ impl TestOperation for ListDatabases { ) -> BoxFuture<'a, Result>> { async move { let result = client - .list_databases(self.filter.clone(), self.options.clone()) + .list_databases() + .with_options(self.options.clone()) .await?; - Ok(Some(bson::to_bson(&result)?)) + Ok(Some(crate::bson_compat::serialize_to_bson(&result)?)) } .boxed() } @@ -1087,9 +944,8 @@ impl TestOperation for ListDatabases { #[derive(Debug, Deserialize)] pub(super) struct ListDatabaseNames { - filter: Option, #[serde(flatten)] - options: Option, + options: Option, } impl TestOperation for ListDatabaseNames { @@ -1099,7 +955,8 @@ impl TestOperation for ListDatabaseNames { ) -> BoxFuture<'a, Result>> { async move { let result = client - .list_database_names(self.filter.clone(), self.options.clone()) + .list_database_names() + .with_options(self.options.clone()) .await?; let result: Vec = result.into_iter().map(|s| s.into()).collect(); Ok(Some(result.into())) @@ -1157,7 +1014,8 @@ impl TestOperation for StartTransaction { ) -> BoxFuture<'a, Result>> { async move { session - .start_transaction(self.options.clone()) + .start_transaction() + .with_options(self.options.clone()) .await .map(|_| None) } @@ -1207,14 +1065,11 @@ impl TestOperation for RunCommand { .read_preference .as_ref() .map(|read_preference| SelectionCriteria::ReadPreference(read_preference.clone())); - let result = match session { - Some(session) => { - database - .run_command_with_session(self.command.clone(), selection_criteria, session) - .await - } - None => database.run_command(self.command.clone(), None).await, - }; + let result = database + .run_command(self.command.clone()) + .optional(selection_criteria, |a, s| a.selection_criteria(s)) + .optional(session, |a, s| a.session(s)) + .await; result.map(|doc| Some(Bson::Document(doc))) } .boxed() @@ -1235,20 +1090,12 @@ impl TestOperation for DropCollection { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - database - .collection::(&self.collection) - .drop_with_session(self.options.clone(), session) - .await - } - None => { - database - .collection::(&self.collection) - .drop(self.options.clone()) - .await - } - }; + let result = database + .collection::(&self.collection) + .drop() + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await; result.map(|_| None) } .boxed() @@ -1269,22 +1116,11 @@ impl TestOperation for CreateCollection { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - let result = match session { - Some(session) => { - database - .create_collection_with_session( - &self.collection, - self.options.clone(), - session, - ) - .await - } - None => { - database - .create_collection(&self.collection, self.options.clone()) - .await - } - }; + let result = database + .create_collection(&self.collection) + .with_options(self.options.clone()) + .optional(session, |b, s| b.session(s)) + .await; result.map(|_| None) } .boxed() @@ -1307,10 +1143,10 @@ impl TestOperation for AssertCollectionExists { .database_with_options( &self.database, DatabaseOptions::builder() - .read_concern(ReadConcern::MAJORITY) + .read_concern(ReadConcern::majority()) .build(), ) - .list_collection_names(None) + .list_collection_names() .await .unwrap(); assert!( @@ -1340,7 +1176,7 @@ impl TestOperation for AssertCollectionNotExists { async move { let collections = client .database(&self.database) - .list_collection_names(None) + .list_collection_names() .await .unwrap(); assert!(!collections.contains(&self.collection)); @@ -1370,15 +1206,11 @@ impl TestOperation for CreateIndex { .options(options) .build(); - let name = match session { - Some(session) => { - collection - .create_index_with_session(index, None, session) - .await? - .index_name - } - None => collection.create_index(index, None).await?.index_name, - }; + let name = collection + .create_index(index) + .optional(session, |a, s| a.session(s)) + .await? + .index_name; Ok(Some(name.into())) } .boxed() @@ -1399,18 +1231,11 @@ impl TestOperation for DropIndex { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { - match session { - Some(session) => { - collection - .drop_index_with_session(self.name.clone(), self.options.clone(), session) - .await? - } - None => { - collection - .drop_index(self.name.clone(), self.options.clone()) - .await? - } - } + collection + .drop_index(self.name.clone()) + .with_options(self.options.clone()) + .optional(session, |a, s| a.session(s)) + .await?; Ok(None) } .boxed() @@ -1430,26 +1255,20 @@ impl TestOperation for ListIndexes { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let act = collection.list_indexes().with_options(self.options.clone()); let indexes: Vec = match session { Some(session) => { - collection - .list_indexes_with_session(self.options.clone(), session) + act.session(&mut *session) .await? .stream(session) .try_collect() .await? } - None => { - collection - .list_indexes(self.options.clone()) - .await? - .try_collect() - .await? - } + None => act.await?.try_collect().await?, }; let indexes: Vec = indexes .iter() - .map(|index| bson::to_document(index).unwrap()) + .map(|index| crate::bson_compat::serialize_to_document(index).unwrap()) .collect(); Ok(Some(indexes.into())) } @@ -1467,9 +1286,10 @@ impl TestOperation for ListIndexNames { session: Option<&'a mut ClientSession>, ) -> BoxFuture<'a, Result>> { async move { + let act = collection.list_index_names(); let names = match session { - Some(session) => collection.list_index_names_with_session(session).await?, - None => collection.list_index_names().await?, + Some(session) => act.session(session).await?, + None => act.await?, }; Ok(Some(names.into())) } @@ -1489,10 +1309,10 @@ impl TestOperation for Watch { async move { match session { None => { - collection.watch(None, None).await?; + collection.watch().await?; } Some(s) => { - collection.watch_with_session(None, None, s).await?; + collection.watch().session(s).await?; } } Ok(None) @@ -1508,10 +1328,10 @@ impl TestOperation for Watch { async move { match session { None => { - database.watch(None, None).await?; + database.watch().await?; } Some(s) => { - database.watch_with_session(None, None, s).await?; + database.watch().session(s).await?; } } Ok(None) @@ -1524,7 +1344,7 @@ impl TestOperation for Watch { client: &'a TestClient, ) -> BoxFuture<'a, Result>> { async move { - client.watch(None, None).await?; + client.watch().await?; Ok(None) } .boxed() @@ -1601,8 +1421,12 @@ impl TestOperation for WithTransaction { ) -> BoxFuture<'a, Result>> { async move { let session = sessions.session0.unwrap(); + // `and_run2` runs afoul of a rustc bug here: https://github.com/rust-lang/rust/issues/64552 + #[allow(deprecated)] session - .with_transaction( + .start_transaction() + .with_options(self.options.clone()) + .and_run( (runner, &self.callback.operations, sessions.session1), |session, (runner, operations, session1)| { async move { @@ -1626,7 +1450,6 @@ impl TestOperation for WithTransaction { } .boxed() }, - self.options.clone(), ) .await?; Ok(None) @@ -1635,6 +1458,21 @@ impl TestOperation for WithTransaction { } } +#[derive(Debug, Deserialize)] +struct Wait { + ms: u64, +} + +impl TestOperation for Wait { + fn execute(&self) -> BoxFuture<'_, ()> { + async move { + let duration = Duration::from_millis(self.ms); + tokio::time::sleep(duration).await; + } + .boxed() + } +} + #[derive(Debug, Deserialize)] pub(super) struct UnimplementedOperation; diff --git a/src/test/spec/v2_runner/test_event.rs b/src/test/spec/v2_runner/test_event.rs index f3f620da1..9b7b15c73 100644 --- a/src/test/spec/v2_runner/test_event.rs +++ b/src/test/spec/v2_runner/test_event.rs @@ -1,5 +1,8 @@ -use crate::{bson::Document, event, test::Matchable}; -use bson::Bson; +use crate::{ + bson::{Bson, Document}, + event, + test::Matchable, +}; use serde::Deserialize; #[derive(Debug, Deserialize)] diff --git a/src/test/spec/v2_runner/test_file.rs b/src/test/spec/v2_runner/test_file.rs index 6e419ea1e..40ccccee3 100644 --- a/src/test/spec/v2_runner/test_file.rs +++ b/src/test/spec/v2_runner/test_file.rs @@ -1,20 +1,23 @@ use std::collections::HashMap; -use bson::{doc, from_document, Bson}; +use crate::{ + bson::{doc, Bson}, + bson_compat::deserialize_from_document, +}; use futures::TryStreamExt; -use semver::VersionReq; use serde::{Deserialize, Deserializer}; use crate::{ bson::Document, - options::{FindOptions, ReadPreference, SelectionCriteria, SessionOptions}, + options::{ReadPreference, SelectionCriteria, SessionOptions}, test::{ + get_topology, log_uncaptured, + server_version_matches, spec::merge_uri_options, - util::is_expected_type, - FailPoint, + util::{fail_point::FailPoint, is_expected_type}, Serverless, - TestClient, + Topology, DEFAULT_URI, }, Client, @@ -32,11 +35,11 @@ pub(crate) struct TestFile { #[allow(unused)] pub(crate) bucket_name: Option, pub(crate) data: Option, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) json_schema: Option, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) encrypted_fields: Option, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) key_vault_data: Option>, pub(crate) tests: Vec, } @@ -46,38 +49,34 @@ pub(crate) struct TestFile { pub(crate) struct RunOn { pub(crate) min_server_version: Option, pub(crate) max_server_version: Option, - pub(crate) topology: Option>, + pub(crate) topology: Option>, pub(crate) serverless: Option, } impl RunOn { - pub(crate) fn can_run_on(&self, client: &TestClient) -> bool { + pub(crate) async fn can_run_on(&self) -> bool { if let Some(ref min_version) = self.min_server_version { - let req = VersionReq::parse(&format!(">= {}", &min_version)).unwrap(); - if !req.matches(&client.server_version) { + if !server_version_matches(&format!(">= {min_version}")).await { log_uncaptured(format!( - "runOn mismatch: required server version >= {}, got {}", - min_version, client.server_version + "runOn mismatch: required server version >= {min_version}", )); return false; } } if let Some(ref max_version) = self.max_server_version { - let req = VersionReq::parse(&format!("<= {}", &max_version)).unwrap(); - if !req.matches(&client.server_version) { + if !server_version_matches(&format!("<= {max_version}")).await { log_uncaptured(format!( - "runOn mismatch: required server version <= {}, got {}", - max_version, client.server_version + "runOn mismatch: required server version <= {max_version}", )); return false; } } if let Some(ref topology) = self.topology { - if !topology.contains(&client.topology_string()) { + let actual_topology = get_topology().await; + if !topology.contains(actual_topology) { log_uncaptured(format!( "runOn mismatch: required topology in {:?}, got {:?}", - topology, - client.topology_string() + topology, actual_topology )); return false; } @@ -99,6 +98,7 @@ impl RunOn { #[serde(untagged)] pub(crate) enum TestData { Single(Vec), + #[allow(dead_code)] Many(HashMap>), } @@ -121,7 +121,7 @@ pub(crate) struct Test { #[derive(Debug)] pub(crate) struct ClientOptions { pub(crate) uri: String, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) auto_encrypt_opts: Option, } @@ -130,20 +130,20 @@ impl<'de> Deserialize<'de> for ClientOptions { where D: Deserializer<'de>, { - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] use serde::de::Error; #[allow(unused_mut)] let mut uri_options = Document::deserialize(deserializer)?; - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] let auto_encrypt_opts = uri_options .remove("autoEncryptOpts") - .map(bson::from_bson) + .map(crate::bson_compat::deserialize_from_bson) .transpose() .map_err(D::Error::custom)?; let uri = merge_uri_options(&DEFAULT_URI, Some(&uri_options), true); Ok(Self { uri, - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] auto_encrypt_opts, }) } @@ -167,22 +167,20 @@ impl Outcome { Some(name) => name, None => coll_name, }; - #[cfg(not(feature = "in-use-encryption-unstable"))] + #[cfg(not(feature = "in-use-encryption"))] let coll_opts = CollectionOptions::default(); - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] let coll_opts = CollectionOptions::builder() - .read_concern(crate::options::ReadConcern::LOCAL) + .read_concern(crate::options::ReadConcern::local()) .build(); let coll = client .database(db_name) .collection_with_options(coll_name, coll_opts); let selection_criteria = SelectionCriteria::ReadPreference(ReadPreference::Primary); - let options = FindOptions::builder() + let actual_data: Vec = coll + .find(doc! {}) .sort(doc! { "_id": 1 }) .selection_criteria(selection_criteria) - .build(); - let actual_data: Vec = coll - .find(None, options) .await .unwrap() .try_collect() @@ -250,7 +248,7 @@ where docs.iter() .map(|doc| { let event = doc.get_document("command_started_event").unwrap(); - from_document(event.clone()).unwrap() + deserialize_from_document(event.clone()).unwrap() }) .collect(), )) diff --git a/src/test/spec/versioned_api.rs b/src/test/spec/versioned_api.rs index c7c3f7c48..53a94c9b5 100644 --- a/src/test/spec/versioned_api.rs +++ b/src/test/spec/versioned_api.rs @@ -1,10 +1,6 @@ -use tokio::sync::RwLockWriteGuard; +use crate::test::spec::unified_runner::run_unified_tests; -use crate::test::{spec::unified_runner::run_unified_tests, LOCK}; - -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn run_unified() { - let _guard: RwLockWriteGuard<_> = LOCK.run_exclusively().await; run_unified_tests(&["versioned-api"]).await; } diff --git a/src/test/spec/write_error.rs b/src/test/spec/write_error.rs index 9bf77fd43..77ff395ef 100644 --- a/src/test/spec/write_error.rs +++ b/src/test/spec/write_error.rs @@ -1,42 +1,36 @@ use crate::{ bson::{doc, Document}, error::{ErrorKind, WriteFailure}, - options::CreateCollectionOptions, - test::{log_uncaptured, EventClient, LOCK}, + test::{log_uncaptured, server_version_lt}, + Client, Collection, }; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn details() { - let _guard = LOCK.run_concurrently().await; - let client = EventClient::new().await; - - if client.server_version_lt(5, 0) { + if server_version_lt(5, 0).await { // SERVER-58399 log_uncaptured("skipping write_error_details test due to server version"); return; } + let client = Client::for_test().monitor_events().await; + let db = client.database("write_error_details"); - db.drop(None).await.unwrap(); - db.create_collection( - "test", - CreateCollectionOptions::builder() - .validator(doc! { - "x": { "$type": "string" } - }) - .build(), - ) - .await - .unwrap(); + db.drop().await.unwrap(); + db.create_collection("test") + .validator(doc! { + "x": { "$type": "string" } + }) + .await + .unwrap(); let coll: Collection = db.collection("test"); - let err = coll.insert_one(doc! { "x": 1 }, None).await.unwrap_err(); + let err = coll.insert_one(doc! { "x": 1 }).await.unwrap_err(); let write_err = match *err.kind { ErrorKind::Write(WriteFailure::WriteError(e)) => e, _ => panic!("expected WriteError, got {:?}", err.kind), }; - let (_, event) = client.get_successful_command_execution("insert"); + let (_, event) = client.events.get_successful_command_execution("insert"); assert_eq!(write_err.code, 121 /* DocumentValidationFailure */); assert_eq!( &write_err.details.unwrap(), @@ -44,7 +38,7 @@ async fn details() { .reply .get_array("writeErrors") .unwrap() - .get(0) + .first() .unwrap() .as_document() .unwrap() diff --git a/src/test/timeseries.rs b/src/test/timeseries.rs index a79717972..81b5bab94 100644 --- a/src/test/timeseries.rs +++ b/src/test/timeseries.rs @@ -1,43 +1,36 @@ -use bson::doc; +use crate::bson::doc; use futures::TryStreamExt; use crate::{ - db::options::{CreateCollectionOptions, TimeseriesOptions}, - test::log_uncaptured, + db::options::TimeseriesOptions, + test::{log_uncaptured, server_version_lt}, Client, }; -use super::LOCK; - type Result = anyhow::Result; -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn list_collections_timeseries() -> Result<()> { - let _guard = LOCK.run_exclusively(); - - let client = Client::test_builder().build().await; - if client.server_version_lt(5, 0) { + if server_version_lt(5, 0).await { log_uncaptured("Skipping list_collections_timeseries: timeseries require server >= 5.0"); return Ok(()); } + + let client = Client::for_test().await; let db = client.database("list_collections_timeseries"); - db.drop(None).await?; - db.create_collection( - "test", - CreateCollectionOptions::builder() - .timeseries( - TimeseriesOptions::builder() - .time_field("timestamp".to_string()) - .meta_field(None) - .granularity(None) - .build(), - ) - .build(), - ) - .await?; + db.drop().await?; + db.create_collection("test") + .timeseries( + TimeseriesOptions::builder() + .time_field("timestamp".to_string()) + .meta_field(None) + .granularity(None) + .build(), + ) + .await?; let results: Vec<_> = db - .list_collections(doc! { "name": "test" }, None) + .list_collections() + .filter(doc! { "name": "test" }) .await? .try_collect() .await?; diff --git a/src/test/util.rs b/src/test/util.rs new file mode 100644 index 000000000..4bfe151da --- /dev/null +++ b/src/test/util.rs @@ -0,0 +1,294 @@ +mod event; +pub(crate) mod event_buffer; +pub(crate) mod fail_point; +mod matchable; +#[cfg(feature = "tracing-unstable")] +mod trace; + +use std::{env, fmt::Debug, fs::File, future::IntoFuture, io::Write, time::Duration}; + +use futures::FutureExt; +use serde::{de::DeserializeOwned, Serialize}; + +#[cfg(feature = "in-use-encryption")] +use crate::client::EncryptedClientBuilder; +use crate::{ + bson::{doc, Bson, Document}, + error::Result, + hello::{hello_command, HelloCommandResponse}, + options::{AuthMechanism, ClientOptions, CollectionOptions, CreateCollectionOptions}, + test::{get_client_options, server_version_gte, topology_is_sharded}, + BoxFuture, + Client, + Collection, +}; + +#[cfg(feature = "tracing-unstable")] +pub(crate) use self::trace::{ + max_verbosity_levels_for_test_case, + TracingEvent, + TracingEventValue, + TracingHandler, +}; +pub(crate) use self::{ + event::{Event, EventClient}, + matchable::{assert_matches, eq_matches, is_expected_type, MatchErrExt, Matchable}, +}; + +#[derive(Clone, Debug)] +pub(crate) struct TestClient { + client: Client, +} + +impl std::ops::Deref for TestClient { + type Target = Client; + + fn deref(&self) -> &Self::Target { + &self.client + } +} + +impl Client { + pub(crate) fn for_test() -> TestClientBuilder { + TestClientBuilder { + options: None, + min_heartbeat_freq: None, + #[cfg(feature = "in-use-encryption")] + encrypted: None, + use_single_mongos: false, + } + } +} + +pub(crate) struct TestClientBuilder { + options: Option, + min_heartbeat_freq: Option, + #[cfg(feature = "in-use-encryption")] + encrypted: Option, + use_single_mongos: bool, +} + +impl TestClientBuilder { + pub(crate) fn options(mut self, options: impl Into>) -> Self { + let options = options.into(); + assert!(self.options.is_none() || options.is_none()); + self.options = options; + self + } + + /// When running against a sharded topology, only use the first configured host. + pub(crate) fn use_single_mongos(mut self) -> Self { + self.use_single_mongos = true; + self + } + + #[cfg(feature = "in-use-encryption")] + pub(crate) fn encrypted_options( + mut self, + encrypted: crate::client::csfle::options::AutoEncryptionOptions, + ) -> Self { + assert!(self.encrypted.is_none()); + self.encrypted = Some(encrypted); + self + } + + pub(crate) fn min_heartbeat_freq( + mut self, + min_heartbeat_freq: impl Into>, + ) -> Self { + let min_heartbeat_freq = min_heartbeat_freq.into(); + assert!(self.min_heartbeat_freq.is_none() || min_heartbeat_freq.is_none()); + self.min_heartbeat_freq = min_heartbeat_freq; + self + } +} + +impl IntoFuture for TestClientBuilder { + type Output = TestClient; + + type IntoFuture = BoxFuture<'static, Self::Output>; + + fn into_future(self) -> Self::IntoFuture { + async move { + let mut options = match self.options { + Some(options) => options, + None => get_client_options().await.clone(), + }; + + if let Some(freq) = self.min_heartbeat_freq { + options.test_options_mut().min_heartbeat_freq = Some(freq); + } + + if self.use_single_mongos && topology_is_sharded().await { + options.hosts = options.hosts.iter().take(1).cloned().collect(); + } + + #[cfg(feature = "in-use-encryption")] + let client = match self.encrypted { + None => Client::with_options(options).unwrap(), + Some(aeo) => EncryptedClientBuilder::new(options, aeo) + .build() + .await + .unwrap(), + }; + #[cfg(not(feature = "in-use-encryption"))] + let client = Client::with_options(options).unwrap(); + + TestClient { client } + } + .boxed() + } +} + +impl TestClient { + pub(crate) async fn create_user( + &self, + user: &str, + pwd: impl Into>, + roles: &[Bson], + mechanisms: &[AuthMechanism], + db: impl Into>, + ) -> Result<()> { + let mut cmd = doc! { "createUser": user, "roles": roles }; + + if let Some(pwd) = pwd.into() { + cmd.insert("pwd", pwd); + } + + if server_version_gte(4, 0).await && !mechanisms.is_empty() { + let ms: crate::bson::Array = + mechanisms.iter().map(|s| Bson::from(s.as_str())).collect(); + cmd.insert("mechanisms", ms); + } + self.database(db.into().unwrap_or("admin")) + .run_command(cmd) + .await?; + Ok(()) + } + + pub(crate) fn get_coll(&self, db_name: &str, coll_name: &str) -> Collection { + self.database(db_name).collection(coll_name) + } + + pub(crate) async fn init_db_and_coll( + &self, + db_name: &str, + coll_name: &str, + ) -> Collection { + let coll = self.get_coll(db_name, coll_name); + coll.drop().await.unwrap(); + coll + } + + pub(crate) async fn init_db_and_typed_coll( + &self, + db_name: &str, + coll_name: &str, + ) -> Collection + where + T: Serialize + DeserializeOwned + Unpin + Debug + Send + Sync, + { + let coll = self.database(db_name).collection(coll_name); + coll.drop().await.unwrap(); + coll + } + + pub(crate) fn get_coll_with_options( + &self, + db_name: &str, + coll_name: &str, + options: CollectionOptions, + ) -> Collection { + self.database(db_name) + .collection_with_options(coll_name, options) + } + + pub(crate) async fn create_fresh_collection( + &self, + db_name: &str, + coll_name: &str, + options: impl Into>, + ) -> Collection { + self.drop_collection(db_name, coll_name).await; + self.database(db_name) + .create_collection(coll_name) + .with_options(options) + .await + .unwrap(); + + self.get_coll(db_name, coll_name) + } + + pub(crate) async fn drop_collection(&self, db_name: &str, coll_name: &str) { + let coll = self.get_coll(db_name, coll_name); + coll.drop().await.unwrap(); + } + + #[allow(dead_code)] + pub(crate) fn into_client(self) -> Client { + self.client + } + + pub(crate) async fn hello(&self) -> Result { + let hello = hello_command( + self.options().server_api.as_ref(), + self.options().load_balanced, + None, + None, + ); + let hello_response_doc = self + .database("admin") + .run_command(hello.body.try_into()?) + .await?; + Ok(crate::bson_compat::deserialize_from_document( + hello_response_doc, + )?) + } +} + +pub(crate) fn get_default_name(description: &str) -> String { + let mut db_name = description.replace('$', "%").replace([' ', '.'], "_"); + // database names must have fewer than 38 characters + db_name.truncate(37); + db_name +} + +#[test] +fn get_exe_name_skip_ci() { + let mut file = File::create("exe_name.txt").expect("Failed to create file"); + let exe_name = env::current_exe() + .expect("Failed to determine name of test executable") + .into_os_string() + .into_string() + .expect("Failed to convert OS string to string"); + write!(file, "{}", exe_name).expect("Failed to write executable name to file"); +} + +/// Log a message on stderr that won't be captured by `cargo test`. Panics if the write fails. +pub(crate) fn log_uncaptured>(text: S) { + use std::io::Write; + + let mut stderr = std::io::stderr(); + let mut sinks = vec![&mut stderr as &mut dyn Write]; + let mut other; + let other_path = std::env::var("LOG_UNCAPTURED").unwrap_or("/dev/tty".to_string()); + if let Ok(f) = std::fs::OpenOptions::new().append(true).open(other_path) { + other = f; + sinks.push(&mut other); + } + + for sink in sinks { + sink.write_all(text.as_ref().as_bytes()).unwrap(); + sink.write_all(b"\n").unwrap(); + } +} + +#[test] +fn log_uncaptured_example() { + println!("this is captured"); + log_uncaptured("this is not captured"); +} + +pub(crate) fn file_level_log(message: impl AsRef) { + log_uncaptured(format!("\n------------\n{}\n", message.as_ref())); +} diff --git a/src/test/util/event.rs b/src/test/util/event.rs index 14268dc21..a6e45975e 100644 --- a/src/test/util/event.rs +++ b/src/test/util/event.rs @@ -1,71 +1,26 @@ -use std::{ - collections::VecDeque, - fs::File, - io::{BufWriter, Write}, - sync::{Arc, Mutex, RwLock}, - time::Duration, -}; +use std::future::IntoFuture; use derive_more::From; +use futures::{future::BoxFuture, FutureExt}; use serde::Serialize; -use time::OffsetDateTime; -use tokio::sync::{broadcast::error::SendError, RwLockReadGuard}; -use super::{subscriber::EventSubscriber, TestClient, TestClientBuilder}; +use super::{event_buffer::EventBuffer, TestClient, TestClientBuilder}; use crate::{ - bson::{doc, to_document, Document}, + bson::doc, event::{ - cmap::{ - CmapEvent, - CmapEventHandler, - ConnectionCheckedInEvent, - ConnectionCheckedOutEvent, - ConnectionCheckoutFailedEvent, - ConnectionCheckoutStartedEvent, - ConnectionClosedEvent, - ConnectionCreatedEvent, - ConnectionReadyEvent, - PoolClearedEvent, - PoolClosedEvent, - PoolCreatedEvent, - PoolReadyEvent, - }, - command::{ - CommandEvent, - CommandEventHandler, - CommandFailedEvent, - CommandStartedEvent, - CommandSucceededEvent, - }, - sdam::{ - SdamEventHandler, - ServerClosedEvent, - ServerDescriptionChangedEvent, - ServerHeartbeatFailedEvent, - ServerHeartbeatStartedEvent, - ServerHeartbeatSucceededEvent, - ServerOpeningEvent, - TopologyClosedEvent, - TopologyDescriptionChangedEvent, - TopologyOpeningEvent, - }, + cmap::CmapEvent, + command::{CommandEvent, CommandFailedEvent, CommandSucceededEvent}, + sdam::SdamEvent, }, - options::ClientOptions, - runtime, - test::{spec::ExpectedEventType, LOCK}, + test::get_client_options, Client, }; -pub(crate) type EventQueue = Arc>>; - -fn add_event_to_queue(event_queue: &EventQueue, event: T) { - event_queue - .write() - .unwrap() - .push_back((event, OffsetDateTime::now_utc())) -} +#[cfg(feature = "in-use-encryption")] +use crate::event::command::CommandStartedEvent; -#[derive(Clone, Debug, From)] +#[derive(Clone, Debug, From, Serialize)] +#[serde(untagged)] #[allow(clippy::large_enum_variant)] pub(crate) enum Event { Cmap(CmapEvent), @@ -82,7 +37,15 @@ impl Event { } } - #[cfg(feature = "in-use-encryption-unstable")] + pub(crate) fn as_command_event(&self) -> Option<&CommandEvent> { + if let Event::Command(e) = self { + Some(e) + } else { + None + } + } + + #[cfg(feature = "in-use-encryption")] pub(crate) fn as_command_started_event(&self) -> Option<&CommandStartedEvent> { match self { Event::Command(CommandEvent::Started(e)) => Some(e), @@ -90,7 +53,7 @@ impl Event { } } - #[cfg(feature = "in-use-encryption-unstable")] + #[cfg(feature = "in-use-encryption")] pub(crate) fn into_command_started_event(self) -> Option { match self { Self::Command(CommandEvent::Started(ev)) => Some(ev), @@ -99,45 +62,7 @@ impl Event { } } -#[derive(Clone, Debug, Serialize)] -#[serde(untagged)] -pub(crate) enum SdamEvent { - ServerDescriptionChanged(Box), - ServerOpening(ServerOpeningEvent), - ServerClosed(ServerClosedEvent), - TopologyDescriptionChanged(Box), - TopologyOpening(TopologyOpeningEvent), - TopologyClosed(TopologyClosedEvent), - ServerHeartbeatStarted(ServerHeartbeatStartedEvent), - ServerHeartbeatSucceeded(ServerHeartbeatSucceededEvent), - ServerHeartbeatFailed(ServerHeartbeatFailedEvent), -} - -impl SdamEvent { - pub fn name(&self) -> &str { - match self { - Self::ServerDescriptionChanged(_) => "ServerDescriptionChangedEvent", - Self::ServerOpening(_) => "ServerOpeningEvent", - Self::ServerClosed(_) => "ServerClosedEvent", - Self::TopologyDescriptionChanged(_) => "TopologyDescriptionChanged", - Self::TopologyOpening(_) => "TopologyOpeningEvent", - Self::TopologyClosed(_) => "TopologyClosedEvent", - Self::ServerHeartbeatStarted(_) => "ServerHeartbeatStartedEvent", - Self::ServerHeartbeatSucceeded(_) => "ServerHeartbeatSucceededEvent", - Self::ServerHeartbeatFailed(_) => "ServerHeartbeatFailedEvent", - } - } -} - impl CommandEvent { - pub fn name(&self) -> &str { - match self { - Self::Started(_) => "CommandStartedEvent", - Self::Succeeded(_) => "CommandSucceededEvent", - Self::Failed(_) => "CommandFailedEvent", - } - } - pub(crate) fn command_name(&self) -> &str { match self { CommandEvent::Started(event) => event.command_name.as_str(), @@ -146,382 +71,25 @@ impl CommandEvent { } } - fn request_id(&self) -> i32 { - match self { - CommandEvent::Started(event) => event.request_id, - CommandEvent::Failed(event) => event.request_id, - CommandEvent::Succeeded(event) => event.request_id, - } - } - - pub(crate) fn as_command_started(&self) -> Option<&CommandStartedEvent> { + pub(crate) fn as_command_succeeded(&self) -> Option<&CommandSucceededEvent> { match self { - CommandEvent::Started(e) => Some(e), + CommandEvent::Succeeded(e) => Some(e), _ => None, } } - pub(crate) fn as_command_succeeded(&self) -> Option<&CommandSucceededEvent> { + pub(crate) fn as_command_failed(&self) -> Option<&CommandFailedEvent> { match self { - CommandEvent::Succeeded(e) => Some(e), + CommandEvent::Failed(e) => Some(e), _ => None, } } } -#[derive(Clone, Debug)] -pub(crate) struct EventHandler { - command_events: EventQueue, - sdam_events: EventQueue, - cmap_events: EventQueue, - event_broadcaster: tokio::sync::broadcast::Sender, - connections_checked_out: Arc>, -} - -impl EventHandler { - pub(crate) fn new() -> Self { - let (event_broadcaster, _) = tokio::sync::broadcast::channel(10_000); - Self { - command_events: Default::default(), - sdam_events: Default::default(), - cmap_events: Default::default(), - event_broadcaster, - connections_checked_out: Arc::new(Mutex::new(0)), - } - } - - fn handle(&self, event: impl Into) { - // this only errors if no receivers are listening which isn't a concern here. - let _: std::result::Result> = - self.event_broadcaster.send(event.into()); - } - - pub(crate) fn subscribe(&self) -> EventSubscriber { - EventSubscriber::new(self, self.event_broadcaster.subscribe()) - } - - pub(crate) fn broadcaster(&self) -> &tokio::sync::broadcast::Sender { - &self.event_broadcaster - } - - /// Gets all of the command started events for the specified command names. - pub(crate) fn get_command_started_events( - &self, - command_names: &[&str], - ) -> Vec { - let events = self.command_events.read().unwrap(); - events - .iter() - .filter_map(|(event, _)| match event { - CommandEvent::Started(event) => { - if command_names.contains(&event.command_name.as_str()) { - Some(event.clone()) - } else { - None - } - } - _ => None, - }) - .collect() - } - - /// Gets all of the command started events, excluding configureFailPoint events. - pub(crate) fn get_all_command_started_events(&self) -> Vec { - let events = self.command_events.read().unwrap(); - events - .iter() - .filter_map(|(event, _)| match event { - CommandEvent::Started(event) if event.command_name != "configureFailPoint" => { - Some(event.clone()) - } - _ => None, - }) - .collect() - } - - pub(crate) fn get_filtered_events( - &self, - event_type: ExpectedEventType, - filter: F, - ) -> Vec - where - F: Fn(&Event) -> bool, - { - match event_type { - ExpectedEventType::Command => { - let events = self.command_events.read().unwrap(); - events - .iter() - .cloned() - .map(|(event, _)| Event::Command(event)) - .filter(|e| filter(e)) - .collect() - } - ExpectedEventType::Cmap => { - let events = self.cmap_events.read().unwrap(); - events - .iter() - .cloned() - .map(|(event, _)| Event::Cmap(event)) - .filter(|e| filter(e)) - .collect() - } - ExpectedEventType::CmapWithoutConnectionReady => { - let mut events = self.get_filtered_events(ExpectedEventType::Cmap, filter); - events.retain(|ev| !matches!(ev, Event::Cmap(CmapEvent::ConnectionReady(_)))); - events - } - } - } - - pub(crate) fn write_events_list_to_file(&self, names: &[&str], writer: &mut BufWriter) { - let mut add_comma = false; - let mut write_json = |mut event: Document, name: &str, time: &OffsetDateTime| { - event.insert("name", name); - event.insert("observedAt", time.unix_timestamp()); - let mut json_string = serde_json::to_string(&event).unwrap(); - if add_comma { - json_string.insert(0, ','); - } else { - add_comma = true; - } - write!(writer, "{}", json_string).unwrap(); - }; - - for (command_event, time) in self.command_events.read().unwrap().iter() { - let name = command_event.name(); - if names.contains(&name) { - let event = to_document(&command_event).unwrap(); - write_json(event, name, time); - } - } - for (sdam_event, time) in self.sdam_events.read().unwrap().iter() { - let name = sdam_event.name(); - if names.contains(&name) { - let event = to_document(&sdam_event).unwrap(); - write_json(event, name, time); - } - } - for (cmap_event, time) in self.cmap_events.read().unwrap().iter() { - let name = cmap_event.planned_maintenance_testing_name(); - if names.contains(&name) { - let event = to_document(&cmap_event).unwrap(); - write_json(event, name, time); - } - } - } - - pub(crate) fn connections_checked_out(&self) -> u32 { - *self.connections_checked_out.lock().unwrap() - } - - pub(crate) fn clear_cached_events(&self) { - self.command_events.write().unwrap().clear(); - self.cmap_events.write().unwrap().clear(); - self.sdam_events.write().unwrap().clear(); - } -} - -impl CmapEventHandler for EventHandler { - fn handle_connection_checked_out_event(&self, event: ConnectionCheckedOutEvent) { - *self.connections_checked_out.lock().unwrap() += 1; - let event = CmapEvent::ConnectionCheckedOut(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_checkout_failed_event(&self, event: ConnectionCheckoutFailedEvent) { - let event = CmapEvent::ConnectionCheckoutFailed(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_pool_cleared_event(&self, pool_cleared_event: PoolClearedEvent) { - let event = CmapEvent::PoolCleared(pool_cleared_event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_pool_ready_event(&self, event: PoolReadyEvent) { - let event = CmapEvent::PoolReady(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_pool_created_event(&self, event: PoolCreatedEvent) { - let event = CmapEvent::PoolCreated(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_pool_closed_event(&self, event: PoolClosedEvent) { - let event = CmapEvent::PoolClosed(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_created_event(&self, event: ConnectionCreatedEvent) { - let event = CmapEvent::ConnectionCreated(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_ready_event(&self, event: ConnectionReadyEvent) { - let event = CmapEvent::ConnectionReady(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_closed_event(&self, event: ConnectionClosedEvent) { - let event = CmapEvent::ConnectionClosed(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_checkout_started_event(&self, event: ConnectionCheckoutStartedEvent) { - let event = CmapEvent::ConnectionCheckoutStarted(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } - - fn handle_connection_checked_in_event(&self, event: ConnectionCheckedInEvent) { - *self.connections_checked_out.lock().unwrap() -= 1; - let event = CmapEvent::ConnectionCheckedIn(event); - self.handle(event.clone()); - add_event_to_queue(&self.cmap_events, event); - } -} - -impl SdamEventHandler for EventHandler { - fn handle_server_description_changed_event(&self, event: ServerDescriptionChangedEvent) { - let event = SdamEvent::ServerDescriptionChanged(Box::new(event)); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_server_opening_event(&self, event: ServerOpeningEvent) { - let event = SdamEvent::ServerOpening(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_server_closed_event(&self, event: ServerClosedEvent) { - let event = SdamEvent::ServerClosed(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_topology_description_changed_event(&self, event: TopologyDescriptionChangedEvent) { - let event = SdamEvent::TopologyDescriptionChanged(Box::new(event)); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_topology_opening_event(&self, event: TopologyOpeningEvent) { - let event = SdamEvent::TopologyOpening(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_topology_closed_event(&self, event: TopologyClosedEvent) { - let event = SdamEvent::TopologyClosed(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_server_heartbeat_started_event(&self, event: ServerHeartbeatStartedEvent) { - let event = SdamEvent::ServerHeartbeatStarted(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_server_heartbeat_succeeded_event(&self, event: ServerHeartbeatSucceededEvent) { - let event = SdamEvent::ServerHeartbeatSucceeded(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } - - fn handle_server_heartbeat_failed_event(&self, event: ServerHeartbeatFailedEvent) { - let event = SdamEvent::ServerHeartbeatFailed(event); - self.handle(event.clone()); - add_event_to_queue(&self.sdam_events, event); - } -} - -impl CommandEventHandler for EventHandler { - fn handle_command_started_event(&self, event: CommandStartedEvent) { - let event = CommandEvent::Started(event); - self.handle(event.clone()); - add_event_to_queue(&self.command_events, event); - } - - fn handle_command_failed_event(&self, event: CommandFailedEvent) { - let event = CommandEvent::Failed(event); - self.handle(event.clone()); - add_event_to_queue(&self.command_events, event); - } - - fn handle_command_succeeded_event(&self, event: CommandSucceededEvent) { - let event = CommandEvent::Succeeded(event); - self.handle(event.clone()); - add_event_to_queue(&self.command_events, event); - } -} - -impl EventSubscriber<'_, EventHandler, Event> { - /// Waits for the next CommandStartedEvent/CommandFailedEvent pair. - /// If the next CommandStartedEvent is associated with a CommandFailedEvent, this method will - /// panic. - pub(crate) async fn wait_for_successful_command_execution( - &mut self, - timeout: Duration, - command_name: impl AsRef, - ) -> Option<(CommandStartedEvent, CommandSucceededEvent)> { - runtime::timeout(timeout, async { - let started = self - .filter_map_event(Duration::MAX, |event| match event { - Event::Command(CommandEvent::Started(s)) - if s.command_name == command_name.as_ref() => - { - Some(s) - } - _ => None, - }) - .await - .unwrap(); - - let succeeded = self - .filter_map_event(Duration::MAX, |event| match event { - Event::Command(CommandEvent::Succeeded(s)) - if s.request_id == started.request_id => - { - Some(s) - } - Event::Command(CommandEvent::Failed(f)) - if f.request_id == started.request_id => - { - panic!( - "expected {} to succeed but it failed: {:#?}", - command_name.as_ref(), - f - ) - } - _ => None, - }) - .await - .unwrap(); - - (started, succeeded) - }) - .await - .ok() - } -} - #[derive(Clone, Debug)] pub(crate) struct EventClient { client: TestClient, - pub(crate) handler: Arc, + pub(crate) events: EventBuffer, } impl std::ops::Deref for EventClient { @@ -539,168 +107,72 @@ impl std::ops::DerefMut for EventClient { } impl TestClientBuilder { - pub(crate) fn event_client(self) -> EventClientBuilder { - EventClientBuilder { inner: self } + pub(crate) fn monitor_events(self) -> EventClientBuilder { + EventClientBuilder { + inner: self, + retain_startup: false, + } } } pub(crate) struct EventClientBuilder { inner: TestClientBuilder, + retain_startup: bool, } impl EventClientBuilder { - pub(crate) async fn build(self) -> EventClient { - let mut inner = self.inner; - if inner.handler.is_none() { - inner = inner.event_handler(Arc::new(EventHandler::new())); - } - let handler = inner.handler().unwrap().clone(); - let client = inner.build().await; - - // clear events from commands used to set up client. - handler.command_events.write().unwrap().clear(); - - EventClient { client, handler } + pub(crate) fn retain_startup_events(mut self) -> Self { + self.retain_startup = true; + self } } -impl EventClient { - pub(crate) async fn new() -> Self { - EventClient::with_options(None).await - } - - async fn with_options_and_handler( - options: impl Into>, - handler: impl Into>, - ) -> Self { - Client::test_builder() - .options(options) - .event_handler(handler.into().map(Arc::new)) - .event_client() - .build() - .await - } - - pub(crate) async fn with_options(options: impl Into>) -> Self { - Self::with_options_and_handler(options, None).await - } - - pub(crate) async fn with_additional_options( - options: impl Into>, - min_heartbeat_freq: Option, - use_multiple_mongoses: Option, - event_handler: impl Into>, - ) -> Self { - Client::test_builder() - .additional_options(options, use_multiple_mongoses.unwrap_or(false)) - .await - .min_heartbeat_freq(min_heartbeat_freq) - .event_handler(event_handler.into().map(Arc::new)) - .event_client() - .build() - .await - } +impl IntoFuture for EventClientBuilder { + type Output = EventClient; - /// Gets the first started/succeeded pair of events for the given command name, popping off all - /// events before and between them. - /// - /// Panics if the command failed or could not be found in the events. - pub(crate) fn get_successful_command_execution( - &self, - command_name: &str, - ) -> (CommandStartedEvent, CommandSucceededEvent) { - let mut command_events = self.handler.command_events.write().unwrap(); + type IntoFuture = BoxFuture<'static, Self::Output>; - let mut started: Option = None; + fn into_future(self) -> Self::IntoFuture { + async move { + let mut inner = self.inner; + let mut options = match inner.options.take() { + Some(options) => options, + None => get_client_options().await.clone(), + }; + let mut events = EventBuffer::new(); + events.register(&mut options); + inner.options = Some(options); - while let Some((event, _)) = command_events.pop_front() { - if event.command_name() == command_name { - match started { - None => { - let event = event - .as_command_started() - .unwrap_or_else(|| { - panic!("first event not a command started event {:?}", event) - }) - .clone(); - started = Some(event); - continue; - } - Some(started) if event.request_id() == started.request_id => { - let succeeded = event - .as_command_succeeded() - .expect("second event not a command succeeded event") - .clone(); + let client = inner.await; - return (started, succeeded); - } - _ => continue, - } + if !self.retain_startup { + // clear events from commands used to set up client. + events.retain(|ev| !matches!(ev, Event::Command(_))); } - } - panic!("could not find event for {} command", command_name); - } - - /// Gets all of the command started events for the specified command names. - pub(crate) fn get_command_started_events( - &self, - command_names: &[&str], - ) -> Vec { - self.handler.get_command_started_events(command_names) - } - - /// Gets all command started events, excluding configureFailPoint events. - pub(crate) fn get_all_command_started_events(&self) -> Vec { - self.handler.get_all_command_started_events() - } - - pub(crate) fn get_command_events(&self, command_names: &[&str]) -> Vec { - self.handler - .command_events - .write() - .unwrap() - .drain(..) - .map(|(event, _)| event) - .filter(|event| command_names.contains(&event.command_name())) - .collect() - } - pub(crate) fn count_pool_cleared_events(&self) -> usize { - let mut out = 0; - for (event, _) in self.handler.cmap_events.read().unwrap().iter() { - if matches!(event, CmapEvent::PoolCleared(_)) { - out += 1; - } + EventClient { client, events } } - out - } - - #[allow(dead_code)] - pub(crate) fn subscribe_to_events(&self) -> EventSubscriber<'_, EventHandler, Event> { - self.handler.subscribe() - } - - pub(crate) fn clear_cached_events(&self) { - self.handler.clear_cached_events() + .boxed() } +} - #[allow(dead_code)] +impl EventClient { pub(crate) fn into_client(self) -> crate::Client { self.client.into_client() } } -#[cfg_attr(feature = "tokio-runtime", tokio::test)] -#[cfg_attr(feature = "async-std-runtime", async_std::test)] +#[tokio::test] async fn command_started_event_count() { - let _guard: RwLockReadGuard<()> = LOCK.run_concurrently().await; - - let client = EventClient::new().await; + let client = Client::for_test().monitor_events().await; let coll = client.database("foo").collection("bar"); for i in 0..10 { - coll.insert_one(doc! { "x": i }, None).await.unwrap(); + coll.insert_one(doc! { "x": i }).await.unwrap(); } - assert_eq!(client.get_command_started_events(&["insert"]).len(), 10); + assert_eq!( + client.events.get_command_started_events(&["insert"]).len(), + 10 + ); } diff --git a/src/test/util/event_buffer.rs b/src/test/util/event_buffer.rs new file mode 100644 index 000000000..37c85d978 --- /dev/null +++ b/src/test/util/event_buffer.rs @@ -0,0 +1,442 @@ +use std::{ + sync::{Arc, Mutex}, + time::Duration, +}; + +use time::OffsetDateTime; +use tokio::sync::Notify; + +use crate::{ + client::options::ClientOptions, + event::{ + cmap::CmapEvent, + command::{CommandEvent, CommandStartedEvent, CommandSucceededEvent}, + }, + runtime, +}; + +use super::Event; + +/// A buffer of events that provides utility methods for querying the buffer and awaiting new event +/// arrival. +/// +/// If working with events one at a time as they arrive is more convenient for a particular test, +/// register an `EventBuffer` with the `Client` and then use `EventBuffer::stream` or +/// `EventBuffer::stream_all`. +#[derive(Clone, Debug)] +pub(crate) struct EventBuffer { + inner: Arc>, +} + +#[derive(Debug)] +struct EventBufferInner { + events: Mutex>, + event_received: Notify, +} + +#[derive(Debug)] +struct GenVec { + data: Vec, + generation: Generation, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +struct Generation(usize); + +impl GenVec { + fn new() -> Self { + Self { + data: vec![], + generation: Generation(0), + } + } +} + +impl EventBuffer { + pub(crate) fn new() -> Self { + Self { + inner: Arc::new(EventBufferInner { + events: Mutex::new(GenVec::new()), + event_received: Notify::new(), + }), + } + } + + pub(crate) fn filter_map(&self, f: impl Fn(&T) -> Option) -> Vec { + self.inner + .events + .lock() + .unwrap() + .data + .iter() + .map(|(ev, _)| ev) + .filter_map(f) + .collect() + } + + /// Create a new stream view of events buffered after the point of this call. + pub(crate) fn stream(&self) -> EventStream<'_, T> { + let (index, generation) = { + let events = self.inner.events.lock().unwrap(); + (events.data.len(), events.generation) + }; + EventStream { + buffer: self, + index, + generation, + } + } + + /// Create a new stream view, starting with events already contained in the buffer. + pub(crate) fn stream_all(&self) -> EventStream<'_, T> { + EventStream { + buffer: self, + index: 0, + generation: self.inner.events.lock().unwrap().generation, + } + } + + // The `mut` isn't necessary on `self` here, but it serves as a useful lint on those + // methods that modify; if the caller only has a `&EventHandler` it can at worst case + // `clone` to get a `mut` one. + fn invalidate(&mut self, f: impl FnOnce(&mut Vec<(T, OffsetDateTime)>) -> R) -> R { + let mut events = self.inner.events.lock().unwrap(); + events.generation = Generation(events.generation.0 + 1); + let out = f(&mut events.data); + self.inner.event_received.notify_waiters(); + out + } + + /// Clear all cached events. This will cause any derived `EventStream`s to error. + pub(crate) fn clear_cached_events(&mut self) { + self.invalidate(|data| data.clear()); + } + + /// Remove all cached events that don't match the predicate. This will cause any derived + /// `EventStream`s to error. + pub(crate) fn retain(&mut self, mut f: impl FnMut(&T) -> bool) { + self.invalidate(|data| data.retain(|(ev, _)| f(ev))); + } + + pub(crate) fn push_event(&self, ev: T) { + self.inner + .events + .lock() + .unwrap() + .data + .push((ev, OffsetDateTime::now_utc())); + self.inner.event_received.notify_waiters(); + } +} + +impl EventBuffer { + /// Returns a list of current events. + pub(crate) fn all(&self) -> Vec { + self.all_timed().into_iter().map(|(ev, _)| ev).collect() + } + + pub(crate) fn all_timed(&self) -> Vec<(T, OffsetDateTime)> { + self.inner.events.lock().unwrap().data.clone() + } +} + +impl EventBuffer { + pub(crate) fn handler + Send + Sync + 'static>( + &self, + ) -> crate::event::EventHandler { + let this = self.clone(); + crate::event::EventHandler::callback(move |ev: V| this.push_event(ev.into())) + } +} + +impl EventBuffer { + pub(crate) fn register(&self, client_options: &mut ClientOptions) { + client_options.command_event_handler = Some(self.handler()); + client_options.sdam_event_handler = Some(self.handler()); + client_options.cmap_event_handler = Some(self.handler()); + } + + pub(crate) fn connections_checked_out(&self) -> u32 { + let mut count = 0; + for (ev, _) in self.inner.events.lock().unwrap().data.iter() { + match ev { + Event::Cmap(CmapEvent::ConnectionCheckedOut(_)) => count += 1, + Event::Cmap(CmapEvent::ConnectionCheckedIn(_)) => count -= 1, + _ => (), + } + } + count + } + + /// Gets all of the command started events for the specified command names. + pub(crate) fn get_command_started_events( + &self, + command_names: &[&str], + ) -> Vec { + self.inner + .events + .lock() + .unwrap() + .data + .iter() + .filter_map(|(event, _)| match event { + Event::Command(CommandEvent::Started(event)) => { + if command_names.contains(&event.command_name.as_str()) { + Some(event.clone()) + } else { + None + } + } + _ => None, + }) + .collect() + } + + /// Gets all of the command started events, excluding configureFailPoint events. + pub(crate) fn get_all_command_started_events(&self) -> Vec { + self.inner + .events + .lock() + .unwrap() + .data + .iter() + .filter_map(|(event, _)| match event { + Event::Command(CommandEvent::Started(event)) + if event.command_name != "configureFailPoint" => + { + Some(event.clone()) + } + _ => None, + }) + .collect() + } + + /// Gets all command events matching any of the command names. + pub(crate) fn get_command_events(&self, command_names: &[&str]) -> Vec { + self.filter_map(|e| match e { + Event::Command(cev) if command_names.iter().any(|&n| n == cev.command_name()) => { + Some(cev.clone()) + } + _ => None, + }) + } + + /// Gets the first started/succeeded pair of events for the given command name. + /// + /// Panics if the command failed or could not be found in the events. + pub(crate) fn get_successful_command_execution( + &self, + command_name: &str, + ) -> (CommandStartedEvent, CommandSucceededEvent) { + let cevs = self.get_command_events(&[command_name]); + if cevs.len() < 2 { + panic!("too few command events for {:?}: {:?}", command_name, cevs); + } + match &cevs[0..2] { + [CommandEvent::Started(started), CommandEvent::Succeeded(succeeded)] => { + (started.clone(), succeeded.clone()) + } + pair => panic!( + "First event pair for {:?} not (Started, Succeded): {:?}", + command_name, pair + ), + } + } + + pub(crate) fn count_pool_cleared_events(&self) -> usize { + let mut out = 0; + for event in self.all().iter() { + if matches!(event, Event::Cmap(CmapEvent::PoolCleared(_))) { + out += 1; + } + } + out + } +} + +/// Iterate one at a time over events in an `EventBuffer`. +pub(crate) struct EventStream<'a, T> { + buffer: &'a EventBuffer, + index: usize, + generation: Generation, +} + +impl EventStream<'_, T> { + fn try_next(&mut self) -> Option { + let events = self.buffer.inner.events.lock().unwrap(); + if events.generation != self.generation { + panic!("EventBuffer cleared during EventStream iteration"); + } + if events.data.len() > self.index { + let event = events.data[self.index].0.clone(); + self.index += 1; + return Some(event); + } + None + } + + /// Get the next unread event from the underlying buffer, waiting for a new one to arrive if all + /// current ones have been read. + pub(crate) async fn next(&mut self, timeout: Duration) -> Option { + crate::runtime::timeout(timeout, async move { + loop { + let notified = self.buffer.inner.event_received.notified(); + if let Some(next) = self.try_next() { + return Some(next); + } + notified.await; + } + }) + .await + .unwrap_or(None) + } + + /// Get the next unread event for which the provided closure returnes `Some`, waiting for new + /// events to arrive if all current ones have been read. + pub(crate) async fn next_map(&mut self, timeout: Duration, mut filter_map: F) -> Option + where + F: FnMut(T) -> Option, + { + runtime::timeout(timeout, async move { + loop { + let ev = self.next(timeout).await?; + if let Some(r) = filter_map(ev) { + return Some(r); + } + } + }) + .await + .unwrap_or(None) + } + + /// Get the next unread event for which the provided closure returnes `true`, waiting for new + /// events to arrive if all current ones have been read. + pub(crate) async fn next_match(&mut self, timeout: Duration, mut filter: F) -> Option + where + F: FnMut(&T) -> bool, + { + self.next_map(timeout, |e| if filter(&e) { Some(e) } else { None }) + .await + } + + /// Collect all unread events matching a predicate, waiting up to a timeout for additional ones + /// to arrive. + pub(crate) async fn collect(&mut self, timeout: Duration, mut filter: F) -> Vec + where + F: FnMut(&T) -> bool, + { + let mut events = Vec::new(); + let _ = runtime::timeout(timeout, async { + while let Some(event) = self.next_match(timeout, &mut filter).await { + events.push(event); + } + }) + .await; + events + } + + #[cfg(feature = "in-use-encryption")] + pub(crate) async fn collect_map(&mut self, timeout: Duration, mut filter: F) -> Vec + where + F: FnMut(T) -> Option, + { + let mut events = Vec::new(); + let _ = runtime::timeout(timeout, async { + while let Some(event) = self.next_map(timeout, &mut filter).await { + events.push(event); + } + }) + .await; + events + } + + /// Collects all unread events matching a predicate without waiting for any more. + pub(crate) fn collect_now(&mut self, filter: F) -> Vec + where + F: Fn(&T) -> bool, + { + let events = self.buffer.inner.events.lock().unwrap(); + if events.generation != self.generation { + panic!("EventBuffer cleared during EventStream iteration"); + } + let out = events + .data + .iter() + .skip(self.index) + .map(|(e, _)| e) + .filter(|e| filter(*e)) + .cloned() + .collect(); + self.index = events.data.len(); + out + } +} + +impl EventStream<'_, Event> { + /// Gets the next unread CommandStartedEvent/CommandFailedEvent pair. + /// If the next CommandStartedEvent is associated with a CommandFailedEvent, this method will + /// panic. + pub(crate) async fn next_successful_command_execution( + &mut self, + timeout: Duration, + command_name: impl AsRef, + ) -> Option<(CommandStartedEvent, CommandSucceededEvent)> { + runtime::timeout(timeout, async { + let started = self + .next_map(Duration::MAX, |event| match event { + Event::Command(CommandEvent::Started(s)) + if s.command_name == command_name.as_ref() => + { + Some(s) + } + _ => None, + }) + .await + .unwrap(); + + let succeeded = self + .next_map(Duration::MAX, |event| match event { + Event::Command(CommandEvent::Succeeded(s)) + if s.request_id == started.request_id => + { + Some(s) + } + Event::Command(CommandEvent::Failed(f)) + if f.request_id == started.request_id => + { + panic!( + "expected {} to succeed but it failed: {:#?}", + command_name.as_ref(), + f + ) + } + _ => None, + }) + .await + .unwrap(); + + (started, succeeded) + }) + .await + .ok() + } + + pub(crate) async fn collect_successful_command_execution( + &mut self, + timeout: Duration, + command_name: impl AsRef, + ) -> Vec<(CommandStartedEvent, CommandSucceededEvent)> { + let mut event_pairs = Vec::new(); + let command_name = command_name.as_ref(); + let _ = runtime::timeout(timeout, async { + while let Some(next_pair) = self + .next_successful_command_execution(timeout, command_name) + .await + { + event_pairs.push(next_pair); + } + }) + .await; + event_pairs + } +} diff --git a/src/test/util/fail_point.rs b/src/test/util/fail_point.rs new file mode 100644 index 000000000..f4ba63e30 --- /dev/null +++ b/src/test/util/fail_point.rs @@ -0,0 +1,174 @@ +use std::time::Duration; + +use serde::{Deserialize, Serialize}; + +use crate::{ + bson::{doc, Document}, + error::Result, + selection_criteria::{ReadPreference, SelectionCriteria}, + test::log_uncaptured, + Client, +}; + +impl Client { + /// Configure a fail point on this client. Any test that calls this method must use the + /// #[tokio::test(flavor = "multi_thread")] test annotation. The guard returned from this + /// method should remain in scope while the fail point is intended for use. Upon drop, the + /// guard will disable the fail point on the server. + pub(crate) async fn enable_fail_point(&self, fail_point: FailPoint) -> Result { + let command = crate::bson_compat::serialize_to_document(&fail_point)?; + self.database("admin") + .run_command(command) + .selection_criteria(fail_point.selection_criteria.clone()) + .await?; + + Ok(FailPointGuard { + client: self.clone(), + failure_type: fail_point.failure_type, + selection_criteria: fail_point.selection_criteria, + }) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct FailPoint { + /// The type of failure to configure. The current valid values are "failCommand" and + /// "failGetMoreAfterCursorCheckout". + #[serde(rename = "configureFailPoint")] + failure_type: String, + + /// The fail point's mode. + mode: FailPointMode, + + /// The data associated with the fail point. This includes the commands that should fail and + /// the error information that should be returned. + #[serde(default)] + data: Document, + + /// The selection criteria to use when configuring this fail point. + #[serde(skip, default = "primary_selection_criteria")] + selection_criteria: SelectionCriteria, +} + +fn primary_selection_criteria() -> SelectionCriteria { + ReadPreference::Primary.into() +} + +impl FailPoint { + /// Creates a new failCommand FailPoint. Call the various builder methods on the returned + /// FailPoint to configure the type of failure that should occur. + pub(crate) fn fail_command(command_names: &[&str], mode: FailPointMode) -> Self { + let data = doc! { "failCommands": command_names }; + Self { + failure_type: "failCommand".to_string(), + mode, + data, + selection_criteria: ReadPreference::Primary.into(), + } + } + + /// The appName that a client must use to hit this fail point. + pub(crate) fn app_name(mut self, app_name: impl Into) -> Self { + self.data.insert("appName", app_name.into()); + self + } + + /// How long the server should block the affected commands. Only available on 4.2.9+ servers. + pub(crate) fn block_connection(mut self, block_connection_duration: Duration) -> Self { + self.data.insert("blockConnection", true); + self.data + .insert("blockTimeMS", block_connection_duration.as_millis() as i64); + self + } + + /// Whether the server should close the connection when the client sends an affected command. + /// Defaults to false. + pub(crate) fn close_connection(mut self, close_connection: bool) -> Self { + self.data.insert("closeConnection", close_connection); + self + } + + /// The error code to include in the server's reply to an affected command. + pub(crate) fn error_code(mut self, error_code: i64) -> Self { + self.data.insert("errorCode", error_code); + self + } + + /// The error labels to include in the server's reply to an affected command. Note that the + /// value passed to this method will completely override the labels that the server would + /// otherwise return. Only available on 4.4+ servers. + pub(crate) fn error_labels( + mut self, + error_labels: impl IntoIterator>, + ) -> Self { + let error_labels: Vec = error_labels.into_iter().map(Into::into).collect(); + self.data.insert("errorLabels", error_labels); + self + } + + /// The write concern error to include in the server's reply to an affected command. + pub(crate) fn write_concern_error(mut self, write_concern_error: Document) -> Self { + self.data.insert("writeConcernError", write_concern_error); + self + } + + /// The selection criteria to use when enabling this fail point. Defaults to a primary read + /// preference if unspecified. + pub(crate) fn selection_criteria(mut self, selection_criteria: SelectionCriteria) -> Self { + self.selection_criteria = selection_criteria; + self + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) enum FailPointMode { + AlwaysOn, + Times(i32), + Skip(i32), + Off, + ActivationProbability(f32), +} + +#[derive(Debug)] +#[must_use] +pub(crate) struct FailPointGuard { + client: Client, + failure_type: String, + selection_criteria: SelectionCriteria, +} + +impl Drop for FailPointGuard { + fn drop(&mut self) { + let client = self.client.clone(); + + // This forces the Tokio runtime to not finish shutdown until this future has completed. + // Unfortunately, this also means that tests using FailPointGuards have to use the + // multi-threaded runtime. + let result = tokio::task::block_in_place(|| { + futures::executor::block_on(async move { + let client = if client.options().app_name.is_some() { + // Create a fresh client with no app name to avoid issues when disabling a + // failpoint configured on the "hello" command. + let mut options = client.options().clone(); + options.app_name = None; + Client::for_test().options(options).await.into_client() + } else { + client + }; + + client + .database("admin") + .run_command( + doc! { "configureFailPoint": self.failure_type.clone(), "mode": "off" }, + ) + .selection_criteria(self.selection_criteria.clone()) + .await + }) + }); + + if let Err(error) = result { + log_uncaptured(format!("failed disabling failpoint: {:?}", error)); + } + } +} diff --git a/src/test/util/failpoint.rs b/src/test/util/failpoint.rs deleted file mode 100644 index 48c61db93..000000000 --- a/src/test/util/failpoint.rs +++ /dev/null @@ -1,147 +0,0 @@ -use bson::{doc, Document}; -use serde::{Deserialize, Serialize, Serializer}; -use std::time::Duration; -use typed_builder::TypedBuilder; - -use crate::{ - error::Result, - operation::append_options, - runtime, - selection_criteria::SelectionCriteria, - Client, -}; - -// If you write a tokio test that uses this, make sure to annotate it with -// tokio::test(flavor = "multi_thread"). -// TODO RUST-1530 Make the error message here better. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct FailPoint { - #[serde(flatten)] - command: Document, -} - -impl FailPoint { - fn name(&self) -> &str { - self.command.get_str("configureFailPoint").unwrap() - } - - /// Create a failCommand failpoint. - /// See for more info. - pub fn fail_command( - fail_commands: &[&str], - mode: FailPointMode, - options: impl Into>, - ) -> FailPoint { - let options = options.into(); - let mut data = doc! { - "failCommands": fail_commands.iter().map(|s| s.to_string()).collect::>(), - }; - append_options(&mut data, options.as_ref()).unwrap(); - - let command = doc! { - "configureFailPoint": "failCommand", - "mode": bson::to_bson(&mode).unwrap(), - "data": data, - }; - FailPoint { command } - } - - pub async fn enable( - &self, - client: &Client, - criteria: impl Into>, - ) -> Result { - let criteria = criteria.into(); - client - .database("admin") - .run_command(self.command.clone(), criteria.clone()) - .await?; - Ok(FailPointGuard { - failpoint_name: self.name().to_string(), - client: client.clone(), - criteria, - }) - } -} - -pub struct FailPointGuard { - client: Client, - failpoint_name: String, - criteria: Option, -} - -impl Drop for FailPointGuard { - fn drop(&mut self) { - let client = self.client.clone(); - let name = self.failpoint_name.clone(); - - let result = runtime::block_on(async move { - client - .database("admin") - .run_command( - doc! { "configureFailPoint": name, "mode": "off" }, - self.criteria.clone(), - ) - .await - }); - - if let Err(e) = result { - println!("failed disabling failpoint: {:?}", e); - } - } -} - -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -#[allow(unused)] -pub enum FailPointMode { - AlwaysOn, - Times(i32), - Skip(i32), - Off, - ActivationProbability(f32), -} - -#[serde_with::skip_serializing_none] -#[derive(Debug, TypedBuilder, Serialize)] -#[builder(field_defaults(default, setter(into)))] -#[serde(rename_all = "camelCase")] -pub struct FailCommandOptions { - /// The appName that a client must use in order to hit this fail point. - app_name: Option, - - /// If non-null, how long the server should block the affected commands. - /// Only available in 4.2.9+. - #[serde(serialize_with = "serialize_block_connection")] - #[serde(flatten)] - block_connection: Option, - - /// Whether the server should hang up when the client sends an affected command - close_connection: Option, - - /// The error code to include in the server's reply to an affected command. - error_code: Option, - - /// Array of error labels to be included in the server's reply to an affected command. Passing - /// in an empty array suppresses all error labels that would otherwise be returned by the - /// server. The existence of the "errorLabels" field in the failCommand failpoint completely - /// overrides the server's normal error labels adding behaviors for the affected commands. - /// Only available in 4.4+. - error_labels: Option>, - - /// Document to be returned as a write concern error. - write_concern_error: Option, -} - -fn serialize_block_connection( - val: &Option, - serializer: S, -) -> std::result::Result { - match val { - Some(duration) => { - (doc! { "blockConnection": true, "blockTimeMS": duration.as_millis() as i64}) - .serialize(serializer) - } - None => serializer.serialize_none(), - } -} diff --git a/src/test/util/lock.rs b/src/test/util/lock.rs deleted file mode 100644 index c1ef308af..000000000 --- a/src/test/util/lock.rs +++ /dev/null @@ -1,20 +0,0 @@ -use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; - -#[derive(Default)] -pub struct TestLock { - inner: RwLock<()>, -} - -impl TestLock { - pub fn new() -> Self { - Default::default() - } - - pub async fn run_concurrently(&self) -> RwLockReadGuard<'_, ()> { - self.inner.read().await - } - - pub async fn run_exclusively(&self) -> RwLockWriteGuard<'_, ()> { - self.inner.write().await - } -} diff --git a/src/test/util/matchable.rs b/src/test/util/matchable.rs index 63c02c4b0..65070d140 100644 --- a/src/test/util/matchable.rs +++ b/src/test/util/matchable.rs @@ -1,6 +1,6 @@ use std::{any::Any, fmt::Debug, time::Duration}; -use bson::spec::ElementType; +use crate::bson::spec::ElementType; use crate::{ bson::{Bson, Document}, @@ -157,21 +157,9 @@ impl Matchable for Document { if k == "upsertedCount" { continue; } - // TODO RUST-48: Remove this logic to bypass read concerns with an afterClusterTime - // field - if k == "afterClusterTime" { - continue; - } if k == "recoveryToken" && v.is_placeholder() && self.get_document(k).is_ok() { continue; } - if k == "readConcern" { - if let Some(doc) = v.as_document() { - if doc.len() == 1 && doc.get_i32("afterClusterTime") == Ok(42) { - continue; - } - } - } match self.get(k) { Some(actual_v) => actual_v.matches(v).prefix(k)?, None => { diff --git a/src/test/util/mod.rs b/src/test/util/mod.rs deleted file mode 100644 index c7007870f..000000000 --- a/src/test/util/mod.rs +++ /dev/null @@ -1,531 +0,0 @@ -mod event; -mod failpoint; -mod lock; -mod matchable; -mod subscriber; -#[cfg(feature = "tracing-unstable")] -mod trace; - -pub(crate) use self::{ - event::{Event, EventClient, EventHandler, SdamEvent}, - failpoint::{FailCommandOptions, FailPoint, FailPointGuard, FailPointMode}, - lock::TestLock, - matchable::{assert_matches, eq_matches, is_expected_type, MatchErrExt, Matchable}, - subscriber::EventSubscriber, -}; - -#[cfg(feature = "tracing-unstable")] -pub(crate) use self::trace::{ - max_verbosity_levels_for_test_case, - TracingEvent, - TracingEventValue, - TracingHandler, -}; - -use std::{fmt::Debug, sync::Arc, time::Duration}; - -#[cfg(feature = "in-use-encryption-unstable")] -use crate::client::EncryptedClientBuilder; -use crate::{ - bson::{doc, Bson}, - client::options::ServerAddress, - hello::{hello_command, HelloCommandResponse}, - selection_criteria::SelectionCriteria, -}; -use bson::Document; -use semver::{Version, VersionReq}; -use serde::{de::DeserializeOwned, Serialize}; - -use super::CLIENT_OPTIONS; -use crate::{ - error::{CommandError, ErrorKind, Result}, - options::{AuthMechanism, ClientOptions, CollectionOptions, CreateCollectionOptions}, - test::{ - update_options_for_testing, - Topology, - LOAD_BALANCED_MULTIPLE_URI, - LOAD_BALANCED_SINGLE_URI, - SERVERLESS, - }, - Client, - Collection, -}; - -#[derive(Clone, Debug)] -pub(crate) struct TestClient { - client: Client, - pub(crate) server_info: HelloCommandResponse, - pub(crate) server_version: Version, - pub(crate) server_parameters: Document, -} - -impl std::ops::Deref for TestClient { - type Target = Client; - - fn deref(&self) -> &Self::Target { - &self.client - } -} - -impl Client { - pub(crate) fn test_builder() -> TestClientBuilder { - TestClientBuilder { - options: None, - handler: None, - min_heartbeat_freq: None, - #[cfg(feature = "in-use-encryption-unstable")] - encrypted: None, - } - } -} - -pub(crate) struct TestClientBuilder { - options: Option, - handler: Option>, - min_heartbeat_freq: Option, - #[cfg(feature = "in-use-encryption-unstable")] - encrypted: Option, -} - -impl TestClientBuilder { - pub(crate) fn options(mut self, options: impl Into>) -> Self { - let options = options.into(); - assert!(self.options.is_none() || options.is_none()); - self.options = options; - self - } - - /// Modify options via `TestClient::options_for_multiple_mongoses` before setting them. - // TODO RUST-1449 Simplify or remove this entirely. - pub(crate) async fn additional_options( - mut self, - options: impl Into>, - use_multiple_mongoses: bool, - ) -> Self { - let options = options.into(); - assert!(self.options.is_none() || options.is_none()); - self.options = - Some(TestClient::options_for_multiple_mongoses(options, use_multiple_mongoses).await); - self - } - - pub(crate) fn event_handler(mut self, handler: impl Into>>) -> Self { - let handler = handler.into(); - assert!(self.handler.is_none() || handler.is_none()); - self.handler = handler; - self - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) fn encrypted_options( - mut self, - encrypted: crate::client::csfle::options::AutoEncryptionOptions, - ) -> Self { - assert!(self.encrypted.is_none()); - self.encrypted = Some(encrypted); - self - } - - pub(crate) fn min_heartbeat_freq( - mut self, - min_heartbeat_freq: impl Into>, - ) -> Self { - let min_heartbeat_freq = min_heartbeat_freq.into(); - assert!(self.min_heartbeat_freq.is_none() || min_heartbeat_freq.is_none()); - self.min_heartbeat_freq = min_heartbeat_freq; - self - } - - pub(crate) async fn build(self) -> TestClient { - let mut options = match self.options { - Some(options) => options, - None => CLIENT_OPTIONS.get().await.clone(), - }; - - if let Some(handler) = self.handler { - options.command_event_handler = Some(handler.clone()); - options.cmap_event_handler = Some(handler.clone()); - options.sdam_event_handler = Some(handler); - } - - if let Some(freq) = self.min_heartbeat_freq { - options.test_options_mut().min_heartbeat_freq = Some(freq); - } - - #[cfg(feature = "in-use-encryption-unstable")] - let client = match self.encrypted { - None => Client::with_options(options).unwrap(), - Some(aeo) => EncryptedClientBuilder::new(options, aeo) - .build() - .await - .unwrap(), - }; - #[cfg(not(feature = "in-use-encryption-unstable"))] - let client = Client::with_options(options).unwrap(); - - TestClient::from_client(client).await - } - - pub(crate) fn handler(&self) -> Option<&Arc> { - self.handler.as_ref() - } -} - -impl TestClient { - // TODO RUST-1449 Remove uses of direct constructors in favor of `TestClientBuilder`. - pub(crate) async fn new() -> Self { - Self::with_options(None).await - } - - pub(crate) async fn with_options(options: impl Into>) -> Self { - Self::with_handler(None, options).await - } - - pub(crate) async fn with_handler( - event_handler: Option>, - options: impl Into>, - ) -> Self { - Client::test_builder() - .options(options) - .event_handler(event_handler) - .build() - .await - } - - async fn from_client(client: Client) -> Self { - let hello = hello_command( - client.options().server_api.as_ref(), - client.options().load_balanced, - None, - None, - ); - let server_info_doc = client - .database("admin") - .run_command(hello.body, None) - .await - .unwrap(); - let server_info = bson::from_document(server_info_doc).unwrap(); - - let build_info = client - .database("test") - .run_command(doc! { "buildInfo": 1 }, None) - .await - .unwrap(); - let mut server_version = Version::parse(build_info.get_str("version").unwrap()).unwrap(); - // Clear prerelease tag to allow version comparisons. - server_version.pre = semver::Prerelease::EMPTY; - - let server_parameters = client - .database("admin") - .run_command(doc! { "getParameter": "*" }, None) - .await - .unwrap_or_default(); - - Self { - client, - server_info, - server_version, - server_parameters, - } - } - - pub(crate) async fn with_additional_options(options: Option) -> Self { - Client::test_builder() - .additional_options(options, false) - .await - .build() - .await - } - - pub(crate) async fn create_user( - &self, - user: &str, - pwd: impl Into>, - roles: &[Bson], - mechanisms: &[AuthMechanism], - db: impl Into>, - ) -> Result<()> { - let mut cmd = doc! { "createUser": user, "roles": roles }; - - if let Some(pwd) = pwd.into() { - cmd.insert("pwd", pwd); - } - - if self.server_version_gte(4, 0) && !mechanisms.is_empty() { - let ms: bson::Array = mechanisms.iter().map(|s| Bson::from(s.as_str())).collect(); - cmd.insert("mechanisms", ms); - } - self.database(db.into().unwrap_or("admin")) - .run_command(cmd, None) - .await?; - Ok(()) - } - - pub(crate) fn get_coll(&self, db_name: &str, coll_name: &str) -> Collection { - self.database(db_name).collection(coll_name) - } - - pub(crate) async fn init_db_and_coll( - &self, - db_name: &str, - coll_name: &str, - ) -> Collection { - let coll = self.get_coll(db_name, coll_name); - drop_collection(&coll).await; - coll - } - - pub(crate) async fn init_db_and_typed_coll( - &self, - db_name: &str, - coll_name: &str, - ) -> Collection - where - T: Serialize + DeserializeOwned + Unpin + Debug, - { - let coll = self.database(db_name).collection(coll_name); - drop_collection(&coll).await; - coll - } - - pub(crate) fn get_coll_with_options( - &self, - db_name: &str, - coll_name: &str, - options: CollectionOptions, - ) -> Collection { - self.database(db_name) - .collection_with_options(coll_name, options) - } - - pub(crate) async fn init_db_and_coll_with_options( - &self, - db_name: &str, - coll_name: &str, - options: CollectionOptions, - ) -> Collection { - let coll = self.get_coll_with_options(db_name, coll_name, options); - drop_collection(&coll).await; - coll - } - - pub(crate) async fn create_fresh_collection( - &self, - db_name: &str, - coll_name: &str, - options: impl Into>, - ) -> Collection { - self.drop_collection(db_name, coll_name).await; - self.database(db_name) - .create_collection(coll_name, options) - .await - .unwrap(); - - self.get_coll(db_name, coll_name) - } - - pub(crate) fn supports_fail_command(&self) -> bool { - let version = if self.is_sharded() { - VersionReq::parse(">= 4.1.5").unwrap() - } else { - VersionReq::parse(">= 4.0").unwrap() - }; - version.matches(&self.server_version) - } - - pub(crate) fn supports_block_connection(&self) -> bool { - let version = VersionReq::parse(">= 4.2.9").unwrap(); - version.matches(&self.server_version) - } - - /// Whether the deployment supports failing the initial handshake - /// only when it uses a specified appName. - /// - /// See SERVER-49336 for more info. - pub(crate) fn supports_fail_command_appname_initial_handshake(&self) -> bool { - let requirements = [ - VersionReq::parse(">= 4.2.15, < 4.3.0").unwrap(), - VersionReq::parse(">= 4.4.7, < 4.5.0").unwrap(), - VersionReq::parse(">= 4.9.0").unwrap(), - ]; - requirements - .iter() - .any(|req| req.matches(&self.server_version)) - } - - pub(crate) fn supports_transactions(&self) -> bool { - self.is_replica_set() && self.server_version_gte(4, 0) - || self.is_sharded() && self.server_version_gte(4, 2) - } - - pub(crate) fn supports_streaming_monitoring_protocol(&self) -> bool { - self.server_info.topology_version.is_some() - } - - pub(crate) async fn enable_failpoint( - &self, - fp: FailPoint, - criteria: impl Into>, - ) -> Result { - fp.enable(self, criteria).await - } - - pub(crate) fn auth_enabled(&self) -> bool { - self.client.options().credential.is_some() - } - - pub(crate) fn is_standalone(&self) -> bool { - self.topology() == Topology::Single - } - - pub(crate) fn is_replica_set(&self) -> bool { - self.topology() == Topology::ReplicaSet - } - - pub(crate) fn is_sharded(&self) -> bool { - self.topology() == Topology::Sharded - } - - pub(crate) fn is_load_balanced(&self) -> bool { - self.topology() == Topology::LoadBalanced - } - - pub(crate) fn server_version_eq(&self, major: u64, minor: u64) -> bool { - self.server_version.major == major && self.server_version.minor == minor - } - - #[allow(dead_code)] - pub(crate) fn server_version_gt(&self, major: u64, minor: u64) -> bool { - self.server_version.major > major - || (self.server_version.major == major && self.server_version.minor > minor) - } - - pub(crate) fn server_version_gte(&self, major: u64, minor: u64) -> bool { - self.server_version.major > major - || (self.server_version.major == major && self.server_version.minor >= minor) - } - - pub(crate) fn server_version_lt(&self, major: u64, minor: u64) -> bool { - self.server_version.major < major - || (self.server_version.major == major && self.server_version.minor < minor) - } - - #[allow(dead_code)] - pub(crate) fn server_version_lte(&self, major: u64, minor: u64) -> bool { - self.server_version.major < major - || (self.server_version.major == major && self.server_version.minor <= minor) - } - - pub(crate) async fn drop_collection(&self, db_name: &str, coll_name: &str) { - let coll = self.get_coll(db_name, coll_name); - drop_collection(&coll).await; - } - - /// Returns the `Topology' that can be determined without a server query, i.e. all except - /// `Toplogy::ShardedReplicaSet`. - pub(crate) fn topology(&self) -> Topology { - if self.client.options().load_balanced.unwrap_or(false) { - return Topology::LoadBalanced; - } - if self.server_info.msg.as_deref() == Some("isdbgrid") { - return Topology::Sharded; - } - if self.server_info.set_name.is_some() { - return Topology::ReplicaSet; - } - Topology::Single - } - - pub(crate) fn topology_string(&self) -> String { - match self.topology() { - Topology::LoadBalanced => "load-balanced", - Topology::Sharded => "sharded", - Topology::ReplicaSet => "replicaset", - Topology::Single => "single", - } - .to_string() - } - - pub(crate) fn primary(&self) -> Option { - self.server_info - .primary - .as_ref() - .map(|s| ServerAddress::parse(s).unwrap()) - } - - pub(crate) async fn options_for_multiple_mongoses( - options: Option, - use_multiple_mongoses: bool, - ) -> ClientOptions { - let is_load_balanced = options - .as_ref() - .and_then(|o| o.load_balanced) - .or(CLIENT_OPTIONS.get().await.load_balanced) - .unwrap_or(false); - let default_options = if is_load_balanced { - // for serverless testing, ignore use_multiple_mongoses. - let uri = if use_multiple_mongoses && !*SERVERLESS { - LOAD_BALANCED_MULTIPLE_URI - .as_ref() - .expect("MULTI_MONGOS_LB_URI is required") - } else { - LOAD_BALANCED_SINGLE_URI - .as_ref() - .expect("SINGLE_MONGOS_LB_URI is required") - }; - let mut o = ClientOptions::parse_uri(uri, None).await.unwrap(); - update_options_for_testing(&mut o); - o - } else { - CLIENT_OPTIONS.get().await.clone() - }; - let mut options = match options { - Some(mut options) => { - options.merge(default_options); - options - } - None => default_options, - }; - if Self::new().await.is_sharded() && !use_multiple_mongoses { - options.hosts = options.hosts.iter().take(1).cloned().collect(); - } - options - } - - #[allow(dead_code)] - pub(crate) fn into_client(self) -> Client { - self.client - } -} - -pub(crate) async fn drop_collection(coll: &Collection) -where - T: Serialize + DeserializeOwned + Unpin + Debug, -{ - match coll.drop(None).await.map_err(|e| *e.kind) { - Err(ErrorKind::Command(CommandError { code: 26, .. })) | Ok(_) => {} - e @ Err(_) => { - e.unwrap(); - } - }; -} - -pub(crate) fn get_default_name(description: &str) -> String { - let mut db_name = description.replace('$', "%").replace([' ', '.'], "_"); - // database names must have fewer than 38 characters - db_name.truncate(37); - db_name -} - -/// Log a message on stderr that won't be captured by `cargo test`. Panics if the write fails. -pub(crate) fn log_uncaptured>(text: S) { - use std::io::Write; - - let mut stderr = std::io::stderr(); - stderr.write_all(text.as_ref().as_bytes()).unwrap(); - stderr.write_all(b"\n").unwrap(); -} - -pub(crate) fn file_level_log(message: impl AsRef) { - log_uncaptured(format!("\n------------\n{}\n", message.as_ref())); -} diff --git a/src/test/util/subscriber.rs b/src/test/util/subscriber.rs deleted file mode 100644 index 495aae006..000000000 --- a/src/test/util/subscriber.rs +++ /dev/null @@ -1,118 +0,0 @@ -use std::time::Duration; - -use crate::runtime; - -use tokio::sync::broadcast::{error::RecvError, Receiver}; - -/// A generic subscriber type that can be used to subscribe to events from any handler type -/// that publishes events to a broadcast channel. -#[derive(Debug)] -pub(crate) struct EventSubscriber<'a, H, E: Clone> { - /// A reference to the handler this subscriber is receiving events from. - /// Stored here to ensure this subscriber cannot outlive the handler that is generating its - /// events. - _handler: &'a H, - receiver: Receiver, -} - -impl EventSubscriber<'_, H, E> { - pub(crate) fn new(handler: &H, receiver: Receiver) -> EventSubscriber<'_, H, E> { - EventSubscriber { - _handler: handler, - receiver, - } - } - - /// Waits for an event to occur within the given duration that passes the given filter. - pub(crate) async fn wait_for_event(&mut self, timeout: Duration, mut filter: F) -> Option - where - F: FnMut(&E) -> bool, - { - self.filter_map_event(timeout, |e| if filter(&e) { Some(e) } else { None }) - .await - } - - pub(crate) async fn collect_events(&mut self, timeout: Duration, mut filter: F) -> Vec - where - F: FnMut(&E) -> bool, - { - let mut events = Vec::new(); - let _ = runtime::timeout(timeout, async { - while let Some(event) = self.wait_for_event(timeout, &mut filter).await { - events.push(event); - } - }) - .await; - events - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) async fn collect_events_map( - &mut self, - timeout: Duration, - mut filter: F, - ) -> Vec - where - F: FnMut(E) -> Option, - { - let mut events = Vec::new(); - let _ = runtime::timeout(timeout, async { - while let Some(event) = self.filter_map_event(timeout, &mut filter).await { - events.push(event); - } - }) - .await; - events - } - - #[cfg(feature = "in-use-encryption-unstable")] - pub(crate) async fn clear_events(&mut self, timeout: Duration) { - self.collect_events(timeout, |_| true).await; - } - - /// Consume and pass events to the provided closure until it returns Some or the timeout is hit. - pub(crate) async fn filter_map_event( - &mut self, - timeout: Duration, - mut filter_map: F, - ) -> Option - where - F: FnMut(E) -> Option, - { - runtime::timeout(timeout, async { - loop { - match self.receiver.recv().await { - Ok(event) => { - if let Some(e) = filter_map(event) { - return Some(e); - } else { - continue; - } - } - // the channel hit capacity and missed some events. - Err(RecvError::Lagged(amount_skipped)) => { - panic!("receiver lagged and skipped {} events", amount_skipped) - } - Err(_) => return None, - } - } - }) - .await - .ok() - .flatten() - } - - /// Returns the received events without waiting for any more. - pub(crate) fn all(&mut self, filter: F) -> Vec - where - F: Fn(&E) -> bool, - { - let mut events = Vec::new(); - while let Ok(event) = self.receiver.try_recv() { - if filter(&event) { - events.push(event); - } - } - events - } -} diff --git a/src/test/util/trace.rs b/src/test/util/trace.rs index 727afe34a..216ca892d 100644 --- a/src/test/util/trace.rs +++ b/src/test/util/trace.rs @@ -1,4 +1,3 @@ -use super::subscriber::EventSubscriber; use crate::test::spec::unified_runner::{test_file::TestCase, TestFileEntity}; use serde::Serialize; use std::{ @@ -6,9 +5,10 @@ use std::{ convert::TryInto, sync::{Arc, RwLock}, }; -use tokio::sync::broadcast; use tracing::{field::Field, span, subscriber::Interest, Level, Metadata}; +use super::event_buffer::{EventBuffer, EventStream}; + /// Models the data reported in a tracing event. #[derive(Debug, Clone)] pub struct TracingEvent { @@ -17,7 +17,7 @@ pub struct TracingEvent { /// The target, i.e. component the event corresponds to. pub target: String, /// Map of key/value pairs attached to the event. - pub fields: std::collections::HashMap, + pub fields: HashMap, } impl TracingEvent { @@ -100,12 +100,11 @@ impl Serialize for TracingEventValue { /// for the scope of a test using [`TracingHandler::set_levels`]. /// /// The handler will listen for tracing events for its associated components/levels and -/// publish them to a broadcast channel. To receive the broadcasted events, call -/// [`TracingHandler::subscribe`] to create a new [`TracingSubscriber`]. +/// buffer them. To stream the buffered events, call +/// [`TracingHandler::event_stream`] to create a new [`EventStream`]. #[derive(Clone, Debug)] pub(crate) struct TracingHandler { - /// Sender for the channel where events will be broadcast. - event_broadcaster: broadcast::Sender, + buffer: EventBuffer, /// Contains a map of (tracing component name, maximum verbosity level) which this handler /// will subscribe to messages for. This is stored in an Arc so that we are able @@ -122,9 +121,8 @@ impl TracingHandler { /// Initializes a new tracing handler with the specified components and corresponding maximum /// verbosity levels. pub(crate) fn new_with_levels(levels: HashMap) -> TracingHandler { - let (event_broadcaster, _) = tokio::sync::broadcast::channel(10_000); Self { - event_broadcaster, + buffer: EventBuffer::new(), levels: Arc::new(RwLock::new(levels)), } } @@ -139,9 +137,9 @@ impl TracingHandler { TracingLevelsGuard { handler: self } } - /// Returns a `TracingSubscriber` that will listen for tracing events broadcast by this handler. - pub(crate) fn subscribe(&self) -> EventSubscriber { - EventSubscriber::new(self, self.event_broadcaster.subscribe()) + /// Returns an `EventStream` that will yield events received by this handler. + pub(crate) fn event_stream(&self) -> EventStream { + self.buffer.stream() } } @@ -194,13 +192,13 @@ pub(crate) fn max_verbosity_levels_for_test_case( o.test_file_entities() .unwrap() .iter() - .for_each(|e| update_merged_levels(e)) + .for_each(&mut update_merged_levels) }); // test-file level entities. these might not all actually be used by this particular // test case but we include them for simplicity. if let Some(ref entities) = entities { - entities.iter().for_each(|e| update_merged_levels(e)); + entities.iter().for_each(update_merged_levels); }; merged_levels @@ -242,9 +240,7 @@ impl tracing::Subscriber for TracingHandler { ); let mut visitor = TracingEventVisitor::new(&mut test_event); event.record(&mut visitor); - // this only errors if no receivers are listening; we don't care if that is the case. - let _: std::result::Result> = - self.event_broadcaster.send(test_event); + self.buffer.push_event(test_event); } /// These methods all relate to spans. Since we don't create any spans ourselves or need diff --git a/src/trace.rs b/src/trace.rs index c32c17a6f..4bdbf9317 100644 --- a/src/trace.rs +++ b/src/trace.rs @@ -1,12 +1,19 @@ -use crate::client::options::{ServerAddress, DEFAULT_PORT}; +use crate::{ + bson::Bson, + client::options::{ServerAddress, DEFAULT_PORT}, +}; pub(crate) mod command; pub(crate) mod connection; pub(crate) mod server_selection; +pub(crate) mod topology; pub(crate) const COMMAND_TRACING_EVENT_TARGET: &str = "mongodb::command"; pub(crate) const CONNECTION_TRACING_EVENT_TARGET: &str = "mongodb::connection"; pub(crate) const SERVER_SELECTION_TRACING_EVENT_TARGET: &str = "mongodb::server_selection"; +pub(crate) const TOPOLOGY_TRACING_EVENT_TARGET: &str = "mongodb::topology"; + +pub(crate) const DEFAULT_MAX_DOCUMENT_LENGTH_BYTES: usize = 1000; pub(crate) trait TracingRepresentation { type Representation; @@ -14,7 +21,7 @@ pub(crate) trait TracingRepresentation { fn tracing_representation(&self) -> Self::Representation; } -impl TracingRepresentation for bson::oid::ObjectId { +impl TracingRepresentation for crate::bson::oid::ObjectId { type Representation = String; fn tracing_representation(&self) -> String { @@ -35,12 +42,44 @@ impl ServerAddress { pub(crate) fn port_tracing_representation(&self) -> Option { match self { Self::Tcp { port, .. } => Some(port.unwrap_or(DEFAULT_PORT)), - // TODO: RUST-802 For Unix domain sockets we should return None here, as ports + // For Unix domain sockets we should return None here, as ports // are not meaningful for those. + #[cfg(unix)] + Self::Unix { .. } => None, + } + } +} + +/// Truncates the given string at the closest UTF-8 character boundary >= the provided length. +/// If the new length is >= the current length, does nothing. +pub(crate) fn truncate_on_char_boundary(s: &mut String, new_len: usize) { + let original_len = s.len(); + if original_len > new_len { + // to avoid generating invalid UTF-8, find the first index >= max_length_bytes that is + // the end of a character. + // TODO: RUST-1496 we should use ceil_char_boundary here but it's currently nightly-only. + // see: https://doc.rust-lang.org/std/string/struct.String.html#method.ceil_char_boundary + let mut truncate_index = new_len; + // is_char_boundary returns true when the provided value == the length of the string, so + // if we reach the end of the string this loop will terminate. + while !s.is_char_boundary(truncate_index) { + truncate_index += 1; + } + s.truncate(truncate_index); + // due to the "rounding up" behavior we might not actually end up truncating anything. + // if we did, spec requires we add a trailing "...". + if truncate_index < original_len { + s.push_str("...") } } } +fn serialize_command_or_reply(doc: crate::bson::Document, max_length_bytes: usize) -> String { + let mut ext_json = Bson::Document(doc).into_relaxed_extjson().to_string(); + truncate_on_char_boundary(&mut ext_json, max_length_bytes); + ext_json +} + /// We don't currently use all of these levels but they are included for completeness. #[allow(dead_code)] pub(crate) enum TracingOrLogLevel { diff --git a/src/trace/command.rs b/src/trace/command.rs index 659e779e8..57d1f1d69 100644 --- a/src/trace/command.rs +++ b/src/trace/command.rs @@ -1,16 +1,11 @@ -use bson::{oid::ObjectId, Bson}; +use crate::bson::oid::ObjectId; use crate::{ - event::command::{ - CommandEventHandler, - CommandFailedEvent, - CommandStartedEvent, - CommandSucceededEvent, - }, - trace::{TracingRepresentation, COMMAND_TRACING_EVENT_TARGET}, + event::command::CommandEvent, + trace::{serialize_command_or_reply, TracingRepresentation, COMMAND_TRACING_EVENT_TARGET}, }; -pub(crate) const DEFAULT_MAX_DOCUMENT_LENGTH_BYTES: usize = 1000; +use super::DEFAULT_MAX_DOCUMENT_LENGTH_BYTES; /// Type responsible for listening for command monitoring events and converting them to /// and emitting them as tracing events. @@ -30,87 +25,57 @@ impl CommandTracingEventEmitter { topology_id, } } -} - -impl CommandEventHandler for CommandTracingEventEmitter { - fn handle_command_started_event(&self, event: CommandStartedEvent) { - tracing::debug!( - target: COMMAND_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - command = serialize_command_or_reply(event.command, self.max_document_length_bytes), - databaseName = event.db, - commandName = event.command_name, - requestId = event.request_id, - driverConnectionId = event.connection.id, - serverConnectionId = event.connection.server_id, - serverHost = event.connection.address.host(), - serverPort = event.connection.address.port_tracing_representation(), - serviceId = event.service_id.map(|id| id.tracing_representation()), - "Command started" - ); - } - fn handle_command_succeeded_event(&self, event: CommandSucceededEvent) { - tracing::debug!( - target: COMMAND_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - reply = serialize_command_or_reply(event.reply, self.max_document_length_bytes), - commandName = event.command_name, - requestId = event.request_id, - driverConnectionId = event.connection.id, - serverConnectionId = event.connection.server_id, - serverHost = event.connection.address.host(), - serverPort = event.connection.address.port_tracing_representation(), - serviceId = event.service_id.map(|id| id.tracing_representation()), - durationMS = event.duration.as_millis(), - "Command succeeded" - ); - } - - fn handle_command_failed_event(&self, event: CommandFailedEvent) { - tracing::debug!( - target: COMMAND_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - failure = event.failure.tracing_representation(), - commandName = event.command_name, - requestId = event.request_id, - driverConnectionId = event.connection.id, - serverConnectionId = event.connection.server_id, - serverHost = event.connection.address.host(), - serverPort = event.connection.address.port_tracing_representation(), - serviceId = event.service_id.map(|id| id.tracing_representation()), - durationMS = event.duration.as_millis(), - "Command failed" - ); - } -} - -fn serialize_command_or_reply(doc: bson::Document, max_length_bytes: usize) -> String { - let mut ext_json = Bson::Document(doc).into_relaxed_extjson().to_string(); - truncate_on_char_boundary(&mut ext_json, max_length_bytes); - ext_json -} - -/// Truncates the given string at the closest UTF-8 character boundary >= the provided length. -/// If the new length is >= the current length, does nothing. -pub(crate) fn truncate_on_char_boundary(s: &mut String, new_len: usize) { - let original_len = s.len(); - if original_len > new_len { - // to avoid generating invalid UTF-8, find the first index >= max_length_bytes that is - // the end of a character. - // TODO: RUST-1496 we should use ceil_char_boundary here but it's currently nightly-only. - // see: https://doc.rust-lang.org/std/string/struct.String.html#method.ceil_char_boundary - let mut truncate_index = new_len; - // is_char_boundary returns true when the provided value == the length of the string, so - // if we reach the end of the string this loop will terminate. - while !s.is_char_boundary(truncate_index) { - truncate_index += 1; - } - s.truncate(truncate_index); - // due to the "rounding up" behavior we might not actually end up truncating anything. - // if we did, spec requires we add a trailing "...". - if truncate_index < original_len { - s.push_str("...") + pub(crate) fn handle(&self, event: CommandEvent) { + match event { + CommandEvent::Started(event) => { + tracing::debug!( + target: COMMAND_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + command = serialize_command_or_reply(event.command, self.max_document_length_bytes), + databaseName = event.db, + commandName = event.command_name, + requestId = event.request_id, + driverConnectionId = event.connection.id, + serverConnectionId = event.connection.server_id, + serverHost = event.connection.address.host().as_ref(), + serverPort = event.connection.address.port_tracing_representation(), + serviceId = event.service_id.map(|id| id.tracing_representation()), + "Command started" + ); + } + CommandEvent::Succeeded(event) => { + tracing::debug!( + target: COMMAND_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + reply = serialize_command_or_reply(event.reply, self.max_document_length_bytes), + commandName = event.command_name, + requestId = event.request_id, + driverConnectionId = event.connection.id, + serverConnectionId = event.connection.server_id, + serverHost = event.connection.address.host().as_ref(), + serverPort = event.connection.address.port_tracing_representation(), + serviceId = event.service_id.map(|id| id.tracing_representation()), + durationMS = event.duration.as_millis(), + "Command succeeded" + ); + } + CommandEvent::Failed(event) => { + tracing::debug!( + target: COMMAND_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + failure = event.failure.tracing_representation(), + commandName = event.command_name, + requestId = event.request_id, + driverConnectionId = event.connection.id, + serverConnectionId = event.connection.server_id, + serverHost = event.connection.address.host().as_ref(), + serverPort = event.connection.address.port_tracing_representation(), + serviceId = event.service_id.map(|id| id.tracing_representation()), + durationMS = event.duration.as_millis(), + "Command failed" + ); + } } } } diff --git a/src/trace/connection.rs b/src/trace/connection.rs index 0d93a1157..6b8d6f811 100644 --- a/src/trace/connection.rs +++ b/src/trace/connection.rs @@ -1,22 +1,7 @@ -use bson::oid::ObjectId; +use crate::bson::oid::ObjectId; use crate::{ - event::cmap::{ - CmapEventHandler, - ConnectionCheckedInEvent, - ConnectionCheckedOutEvent, - ConnectionCheckoutFailedEvent, - ConnectionCheckoutFailedReason, - ConnectionCheckoutStartedEvent, - ConnectionClosedEvent, - ConnectionClosedReason, - ConnectionCreatedEvent, - ConnectionReadyEvent, - PoolClearedEvent, - PoolClosedEvent, - PoolCreatedEvent, - PoolReadyEvent, - }, + event::cmap::{CmapEvent, ConnectionCheckoutFailedReason, ConnectionClosedReason}, trace::{TracingRepresentation, CONNECTION_TRACING_EVENT_TARGET}, }; @@ -29,131 +14,127 @@ impl ConnectionTracingEventEmitter { pub(crate) fn new(topology_id: ObjectId) -> ConnectionTracingEventEmitter { Self { topology_id } } -} - -impl CmapEventHandler for ConnectionTracingEventEmitter { - fn handle_pool_created_event(&self, event: PoolCreatedEvent) { - let options_ref = event.options.as_ref(); - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - maxIdleTimeMS = options_ref.and_then(|o| o.max_idle_time.map(|m| m.as_millis())), - maxPoolSize = options_ref.and_then(|o| o.max_pool_size), - minPoolSize = options_ref.and_then(|o| o.min_pool_size), - "Connection pool created", - ); - } - - fn handle_pool_ready_event(&self, event: PoolReadyEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - "Connection pool ready", - ); - } - - fn handle_pool_cleared_event(&self, event: PoolClearedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - serviceId = event.service_id.map(|id| id.tracing_representation()), - "Connection pool cleared", - ); - } - - fn handle_pool_closed_event(&self, event: PoolClosedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - "Connection pool closed", - ); - } - fn handle_connection_created_event(&self, event: ConnectionCreatedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - driverConnectionId = event.connection_id, - "Connection created", - ); - } - - fn handle_connection_ready_event(&self, event: ConnectionReadyEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - driverConnectionId = event.connection_id, - "Connection ready", - ); - } - - fn handle_connection_closed_event(&self, event: ConnectionClosedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - driverConnectionId = event.connection_id, - reason = event.reason.tracing_representation(), - error = event.error.map(|e| e.tracing_representation()), - "Connection closed", - ); - } - - fn handle_connection_checkout_started_event(&self, event: ConnectionCheckoutStartedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - "Connection checkout started", - ); - } - - fn handle_connection_checkout_failed_event(&self, event: ConnectionCheckoutFailedEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - reason = event.reason.tracing_representation(), - error = event.error.map(|e| e.tracing_representation()), - "Connection checkout failed", - ); - } - - fn handle_connection_checked_out_event(&self, event: ConnectionCheckedOutEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - driverConnectionId = event.connection_id, - "Connection checked out", - ); - } - - fn handle_connection_checked_in_event(&self, event: ConnectionCheckedInEvent) { - tracing::debug!( - target: CONNECTION_TRACING_EVENT_TARGET, - topologyId = self.topology_id.tracing_representation(), - serverHost = event.address.host(), - serverPort = event.address.port_tracing_representation(), - driverConnectionId = event.connection_id, - "Connection checked in", - ); + pub(crate) fn handle(&self, event: CmapEvent) { + use CmapEvent::*; + match event { + PoolCreated(event) => { + let options_ref = event.options.as_ref(); + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + maxIdleTimeMS = options_ref.and_then(|o| o.max_idle_time.map(|m| m.as_millis())), + maxPoolSize = options_ref.and_then(|o| o.max_pool_size), + minPoolSize = options_ref.and_then(|o| o.min_pool_size), + "Connection pool created", + ); + } + PoolReady(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + "Connection pool ready", + ); + } + PoolCleared(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + serviceId = event.service_id.map(|id| id.tracing_representation()), + "Connection pool cleared", + ); + } + PoolClosed(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + "Connection pool closed", + ); + } + ConnectionCreated(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + driverConnectionId = event.connection_id, + "Connection created", + ); + } + ConnectionReady(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + driverConnectionId = event.connection_id, + durationMS = event.duration.as_millis(), + "Connection ready", + ); + } + ConnectionClosed(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + driverConnectionId = event.connection_id, + reason = event.reason.tracing_representation(), + error = event.error.map(|e| e.tracing_representation()), + "Connection closed", + ); + } + ConnectionCheckoutStarted(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + "Connection checkout started", + ); + } + ConnectionCheckoutFailed(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + reason = event.reason.tracing_representation(), + error = event.error.map(|e| e.tracing_representation()), + durationMS = event.duration.as_millis(), + "Connection checkout failed", + ); + } + ConnectionCheckedOut(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + driverConnectionId = event.connection_id, + durationMS = event.duration.as_millis(), + "Connection checked out", + ); + } + ConnectionCheckedIn(event) => { + tracing::debug!( + target: CONNECTION_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + driverConnectionId = event.connection_id, + "Connection checked in", + ); + } + } } } @@ -162,14 +143,16 @@ impl TracingRepresentation for ConnectionClosedReason { fn tracing_representation(&self) -> &'static str { match self { - ConnectionClosedReason::Stale => "Connection became stale because the pool was cleared", - ConnectionClosedReason::Idle => { + Self::Stale => "Connection became stale because the pool was cleared", + Self::Idle => { "Connection has been available but unused for longer than the configured max idle \ time" } - ConnectionClosedReason::Error => "An error occurred while using the connection", - ConnectionClosedReason::Dropped => "Connection was dropped during an operation", - ConnectionClosedReason::PoolClosed => "Connection pool was closed", + Self::Error => "An error occurred while using the connection", + Self::Dropped => "Connection was dropped during an operation", + Self::PoolClosed => "Connection pool was closed", + #[cfg(test)] + Self::Unset => "Unset", } } } @@ -179,12 +162,10 @@ impl TracingRepresentation for ConnectionCheckoutFailedReason { fn tracing_representation(&self) -> &'static str { match self { - ConnectionCheckoutFailedReason::Timeout => { - "Failed to establish a new connection within connectTimeoutMS" - } - ConnectionCheckoutFailedReason::ConnectionError => { - "An error occurred while trying to establish a new connection" - } + Self::Timeout => "Failed to establish a new connection within connectTimeoutMS", + Self::ConnectionError => "An error occurred while trying to establish a new connection", + #[cfg(test)] + Self::Unset => "Unset", } } } diff --git a/src/trace/server_selection.rs b/src/trace/server_selection.rs index bd4c47669..16a2f0258 100644 --- a/src/trace/server_selection.rs +++ b/src/trace/server_selection.rs @@ -5,11 +5,11 @@ use super::{ SERVER_SELECTION_TRACING_EVENT_TARGET, }; use crate::{ + bson::oid::ObjectId, error::Error, sdam::{SelectedServer, TopologyDescription}, selection_criteria::SelectionCriteria, }; -use bson::oid::ObjectId; use std::time::{Duration, Instant}; impl TracingRepresentation for SelectionCriteria { @@ -106,7 +106,7 @@ impl ServerSelectionTracingEventEmitter<'_> { operation = self.operation_name, selector = self.criteria.tracing_representation(), topologyDescription = topology_description.tracing_representation(), - serverHost = server.address().host(), + serverHost = server.address().host().as_ref(), serverPort = server.address().port_tracing_representation(), "Server selection succeeded" ); diff --git a/src/trace/topology.rs b/src/trace/topology.rs new file mode 100644 index 000000000..19cebdb00 --- /dev/null +++ b/src/trace/topology.rs @@ -0,0 +1,204 @@ +use crate::bson::oid::ObjectId; + +use crate::{ + event::sdam::{ + SdamEvent, + ServerClosedEvent, + ServerDescriptionChangedEvent, + ServerHeartbeatFailedEvent, + ServerHeartbeatStartedEvent, + ServerHeartbeatSucceededEvent, + ServerOpeningEvent, + TopologyClosedEvent, + TopologyDescription, + TopologyDescriptionChangedEvent, + TopologyOpeningEvent, + }, + trace::serialize_command_or_reply, +}; + +use super::{ + trace_or_log_enabled, + TracingOrLogLevel, + TracingRepresentation, + DEFAULT_MAX_DOCUMENT_LENGTH_BYTES, + TOPOLOGY_TRACING_EVENT_TARGET, +}; + +impl TracingRepresentation for TopologyDescription { + type Representation = String; + + fn tracing_representation(&self) -> Self::Representation { + self.to_string() + } +} + +pub(crate) struct TopologyTracingEventEmitter { + max_document_length_bytes: usize, + topology_id: ObjectId, +} + +impl TopologyTracingEventEmitter { + pub(crate) fn new( + max_document_length_bytes: Option, + topology_id: ObjectId, + ) -> TopologyTracingEventEmitter { + TopologyTracingEventEmitter { + max_document_length_bytes: max_document_length_bytes + .unwrap_or(DEFAULT_MAX_DOCUMENT_LENGTH_BYTES), + topology_id, + } + } +} + +impl TopologyTracingEventEmitter { + pub(crate) fn handle(&self, event: SdamEvent) { + use SdamEvent::*; + match event { + ServerDescriptionChanged(ev) => self.handle_server_description_changed_event(*ev), + ServerOpening(ev) => self.handle_server_opening_event(ev), + ServerClosed(ev) => self.handle_server_closed_event(ev), + TopologyDescriptionChanged(ev) => self.handle_topology_description_changed_event(*ev), + TopologyOpening(ev) => self.handle_topology_opening_event(ev), + TopologyClosed(ev) => self.handle_topology_closed_event(ev), + ServerHeartbeatStarted(ev) => self.handle_server_heartbeat_started_event(ev), + ServerHeartbeatSucceeded(ev) => self.handle_server_heartbeat_succeeded_event(ev), + ServerHeartbeatFailed(ev) => self.handle_server_heartbeat_failed_event(ev), + } + } + + fn handle_server_description_changed_event(&self, _event: ServerDescriptionChangedEvent) { + // this is tentatively a no-op based on my proposal to not do separate "topology changed" + // and "server changed" log messages due to the redundancy, but that could change + // based on the spec review process. + } + + fn handle_server_opening_event(&self, event: ServerOpeningEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + "Starting server monitoring" + ) + } + } + + fn handle_server_closed_event(&self, event: ServerClosedEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.address.host().as_ref(), + serverPort = event.address.port_tracing_representation(), + "Stopped server monitoring" + ) + } + } + + fn handle_topology_description_changed_event(&self, event: TopologyDescriptionChangedEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + previousDescription = event.previous_description.tracing_representation(), + newDescription = event.new_description.tracing_representation(), + "Topology description changed" + ) + } + } + + fn handle_topology_opening_event(&self, _event: TopologyOpeningEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + "Starting topology monitoring" + ) + } + } + + fn handle_topology_closed_event(&self, _event: TopologyClosedEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + "Stopped topology monitoring" + ) + } + } + + fn handle_server_heartbeat_started_event(&self, event: ServerHeartbeatStartedEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.server_address.host().as_ref(), + serverPort = event.server_address.port_tracing_representation(), + driverConnectionId = event.driver_connection_id, + serverConnectionId = event.server_connection_id, + awaited = event.awaited, + "Server heartbeat started" + ) + } + } + + fn handle_server_heartbeat_succeeded_event(&self, event: ServerHeartbeatSucceededEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.server_address.host().as_ref(), + serverPort = event.server_address.port_tracing_representation(), + driverConnectionId = event.driver_connection_id, + serverConnectionId = event.server_connection_id, + awaited = event.awaited, + reply = serialize_command_or_reply(event.reply, self.max_document_length_bytes), + durationMS = event.duration.as_millis(), + "Server heartbeat succeeded" + ) + } + } + + fn handle_server_heartbeat_failed_event(&self, event: ServerHeartbeatFailedEvent) { + if trace_or_log_enabled!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + TracingOrLogLevel::Debug + ) { + tracing::debug!( + target: TOPOLOGY_TRACING_EVENT_TARGET, + topologyId = self.topology_id.tracing_representation(), + serverHost = event.server_address.host().as_ref(), + serverPort = event.server_address.port_tracing_representation(), + driverConnectionId = event.driver_connection_id, + serverConnectionId = event.server_connection_id, + awaited = event.awaited, + failure = event.failure.tracing_representation(), + durationMS = event.duration.as_millis(), + "Server heartbeat failed" + ) + } + } +} diff --git a/src/tracking_arc.rs b/src/tracking_arc.rs new file mode 100644 index 000000000..9382b2573 --- /dev/null +++ b/src/tracking_arc.rs @@ -0,0 +1,145 @@ +#[cfg(all(test, mongodb_internal_tracking_arc))] +use crate::id_set::{self, IdSet}; +use std::sync::Arc; +#[cfg(all(test, mongodb_internal_tracking_arc))] +use std::sync::Mutex as SyncMutex; + +/// An `Arc` that records the backtraces of construction of live clones. When not compiled +/// with `cfg(mongodb_internal_tracking_arc)`, a zero-cost `Arc` wrapper. +#[derive(Debug)] +pub(crate) struct TrackingArc { + inner: Arc>, + #[cfg(all(test, mongodb_internal_tracking_arc))] + clone_id: Option, +} + +#[derive(Debug)] +struct Inner { + value: T, + #[cfg(all(test, mongodb_internal_tracking_arc))] + clones: SyncMutex>, +} + +impl TrackingArc { + pub(crate) fn new(value: T) -> Self { + Self { + inner: Arc::new(Inner { + value, + #[cfg(all(test, mongodb_internal_tracking_arc))] + clones: SyncMutex::new(IdSet::new()), + }), + #[cfg(all(test, mongodb_internal_tracking_arc))] + clone_id: None, + } + } + + #[allow(unused)] + pub(crate) fn try_unwrap(tracked: Self) -> Result { + let inner = tracked.inner.clone(); + #[cfg(all(test, mongodb_internal_tracking_arc))] + let clone_id = { + let mut tracked = tracked; + tracked.clone_id.take() + }; + match Arc::try_unwrap(inner) { + Ok(inner) => Ok(inner.value), + Err(inner) => Err(Self { + inner, + #[cfg(all(test, mongodb_internal_tracking_arc))] + clone_id, + }), + } + } + + pub(crate) fn downgrade(tracked: &Self) -> Weak { + Weak { + inner: Arc::downgrade(&tracked.inner), + } + } + + pub(crate) fn ptr_eq(this: &Self, other: &Self) -> bool { + Arc::ptr_eq(&this.inner, &other.inner) + } + + pub(crate) fn strong_count(this: &Self) -> usize { + Arc::strong_count(&this.inner) + } + + #[cfg(all(test, mongodb_internal_tracking_arc))] + #[allow(unused)] + pub(crate) fn print_live(tracked: &Self) { + let current: Vec<_> = tracked + .inner + .clones + .lock() + .unwrap() + .values() + .cloned() + .collect(); + for mut bt in current { + bt.resolve(); + println!("{:?}", bt); + } + } +} + +impl Clone for TrackingArc { + fn clone(&self) -> Self { + #[cfg(all(test, mongodb_internal_tracking_arc))] + let clone_id = { + let bt = backtrace::Backtrace::new_unresolved(); + Some(self.inner.clones.lock().unwrap().insert(bt)) + }; + Self { + inner: self.inner.clone(), + #[cfg(all(test, mongodb_internal_tracking_arc))] + clone_id, + } + } +} + +impl Drop for TrackingArc { + fn drop(&mut self) { + #[cfg(all(test, mongodb_internal_tracking_arc))] + if let Some(id) = &self.clone_id { + self.inner.clones.lock().unwrap().remove(id); + } + } +} + +impl std::ops::Deref for TrackingArc { + type Target = T; + fn deref(&self) -> &T { + &self.inner.value + } +} + +#[derive(Debug)] +pub(crate) struct Weak { + inner: std::sync::Weak>, +} + +impl Clone for Weak { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl Weak { + pub(crate) fn upgrade(&self) -> Option> { + self.inner.upgrade().map(|inner| { + #[cfg(all(test, mongodb_internal_tracking_arc))] + let clone_id = { + let bt = backtrace::Backtrace::new_unresolved(); + Some(inner.clones.lock().unwrap().insert(bt)) + }; + TrackingArc { + inner, + #[cfg(all(test, mongodb_internal_tracking_arc))] + clone_id, + } + }) + } +} diff --git a/tests/connection_snippets.rs b/tests/connection_snippets.rs index d82e470de..d18cdb270 100644 --- a/tests/connection_snippets.rs +++ b/tests/connection_snippets.rs @@ -4,7 +4,6 @@ extern crate mongodb; -#[cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] mod async_scram { // ASYNC SCRAM CONNECTION EXAMPLE STARTS HERE use mongodb::{options::ClientOptions, Client}; @@ -24,7 +23,6 @@ mod async_scram { // CONNECTION EXAMPLE ENDS HERE } -#[cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] mod async_x509 { // ASYNC X509 CONNECTION EXAMPLE STARTS HERE use mongodb::{ @@ -57,7 +55,7 @@ mod async_x509 { // CONNECTION EXAMPLE ENDS HERE } -#[cfg(any(feature = "sync", feature = "tokio-sync"))] +#[cfg(feature = "sync")] mod sync_scram { // SYNC SCRAM CONNECTION EXAMPLE STARTS HERE use mongodb::{options::ClientOptions, sync::Client}; @@ -65,7 +63,8 @@ mod sync_scram { fn main() -> mongodb::error::Result<()> { let client_options = ClientOptions::parse( "mongodb+srv://:@/?w=majority", - )?; + ) + .run()?; let client = Client::with_options(client_options)?; let database = client.database("test"); // do something with database @@ -75,7 +74,7 @@ mod sync_scram { // CONNECTION EXAMPLE ENDS HERE } -#[cfg(any(feature = "sync", feature = "tokio-sync"))] +#[cfg(feature = "sync")] mod sync_x509 { // SYNC X509 CONNECTION EXAMPLE STARTS HERE use mongodb::{ @@ -86,7 +85,7 @@ mod sync_x509 { fn main() -> mongodb::error::Result<()> { let mut client_options = - ClientOptions::parse("mongodb+srv:///?w=majority")?; + ClientOptions::parse("mongodb+srv:///?w=majority").run()?; client_options.credential = Some( Credential::builder() .mechanism(AuthMechanism::MongoDbX509) diff --git a/tests/readme_examples.rs b/tests/readme_examples.rs index e44620d6b..7ac94afdf 100644 --- a/tests/readme_examples.rs +++ b/tests/readme_examples.rs @@ -11,7 +11,6 @@ impl From for Err { #[allow(dead_code)] type Result = std::result::Result; -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] async fn _connecting() -> Result<()> { use mongodb::{options::ClientOptions, Client}; @@ -25,27 +24,25 @@ async fn _connecting() -> Result<()> { let client = Client::with_options(client_options)?; // List the names of the databases in that deployment. - for db_name in client.list_database_names(None, None).await? { + for db_name in client.list_database_names().await? { println!("{}", db_name); } Ok(()) } -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] async fn _getting_handle_to_database(client: mongodb::Client) -> Result<()> { // Get a handle to a database. let db = client.database("mydb"); // List the names of the collections in that database. - for collection_name in db.list_collection_names(None).await? { + for collection_name in db.list_collection_names().await? { println!("{}", collection_name); } Ok(()) } -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] async fn _inserting_documents_into_a_collection(db: mongodb::Database) -> Result<()> { use mongodb::bson::{doc, Document}; @@ -59,7 +56,7 @@ async fn _inserting_documents_into_a_collection(db: mongodb::Database) -> Result ]; // Insert some documents into the "mydb.books" collection. - collection.insert_many(docs, None).await?; + collection.insert_many(docs).await?; Ok(()) } @@ -71,7 +68,6 @@ struct Book { author: String, } -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] async fn _inserting_documents_into_a_typed_collection(db: mongodb::Database) -> Result<()> { // Get a handle to a collection of `Book`. let typed_collection = db.collection::("books"); @@ -88,23 +84,24 @@ async fn _inserting_documents_into_a_typed_collection(db: mongodb::Database) -> ]; // Insert the books into "mydb.books" collection, no manual conversion to BSON necessary. - typed_collection.insert_many(books, None).await?; + typed_collection.insert_many(books).await?; Ok(()) } -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] async fn _finding_documents_into_a_collection( typed_collection: mongodb::Collection, ) -> Result<()> { // This trait is required to use `try_next()` on the cursor use futures::stream::TryStreamExt; - use mongodb::{bson::doc, options::FindOptions}; + use mongodb::bson::doc; // Query the books in the collection with a filter and an option. let filter = doc! { "author": "George Orwell" }; - let find_options = FindOptions::builder().sort(doc! { "title": 1 }).build(); - let mut cursor = typed_collection.find(filter, find_options).await?; + let mut cursor = typed_collection + .find(filter) + .sort(doc! { "title": 1 }) + .await?; // Iterate over the results of the cursor. while let Some(book) = cursor.try_next().await? { @@ -114,7 +111,7 @@ async fn _finding_documents_into_a_collection( Ok(()) } -#[cfg(any(feature = "sync", feature = "tokio-sync"))] +#[cfg(feature = "sync")] async fn _using_the_sync_api() -> Result<()> { use mongodb::{bson::doc, sync::Client}; @@ -138,9 +135,9 @@ async fn _using_the_sync_api() -> Result<()> { ]; // Insert some books into the "mydb.books" collection. - collection.insert_many(docs, None)?; + collection.insert_many(docs).run()?; - let cursor = collection.find(doc! { "author": "George Orwell" }, None)?; + let cursor = collection.find(doc! { "author": "George Orwell" }).run()?; for result in cursor { println!("title: {}", result?.title); } @@ -148,18 +145,16 @@ async fn _using_the_sync_api() -> Result<()> { Ok(()) } -#[cfg(all(not(feature = "sync"), not(feature = "tokio-sync")))] +#[cfg(feature = "dns-resolver")] async fn _windows_dns_note() -> Result<()> { use mongodb::{ options::{ClientOptions, ResolverConfig}, Client, }; - let options = ClientOptions::parse_with_resolver_config( - "mongodb+srv://my.host.com", - ResolverConfig::cloudflare(), - ) - .await?; + let options = ClientOptions::parse("mongodb+srv://my.host.com") + .resolver_config(ResolverConfig::cloudflare()) + .await?; let client = Client::with_options(options)?; drop(client); diff --git a/tests/transactions_example.rs b/tests/transactions_example.rs index de1f8fecb..153938dd4 100644 --- a/tests/transactions_example.rs +++ b/tests/transactions_example.rs @@ -1,17 +1,8 @@ -#![cfg(all(feature = "tokio-runtime", not(feature = "tokio-sync")))] - // START TRANSACTIONS EXAMPLE use mongodb::{ bson::{doc, Document}, error::{Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT}, - options::{ - Acknowledgment, - ReadConcern, - ReadPreference, - SelectionCriteria, - TransactionOptions, - WriteConcern, - }, + options::{Acknowledgment, ReadConcern, ReadPreference, SelectionCriteria, WriteConcern}, Client, ClientSession, }; @@ -21,7 +12,7 @@ async fn main() -> Result<()> { let uri = std::env::var("MONGODB_URI").expect("MONGODB_URI must be set"); let client = Client::with_uri_str(uri).await?; - let mut session = client.start_session(None).await?; + let mut session = client.start_session().await?; run_transaction_with_retry(&mut session).await } @@ -46,32 +37,28 @@ async fn run_transaction_with_retry(session: &mut ClientSession) -> Result<()> { } async fn execute_transaction(session: &mut ClientSession) -> Result<()> { - let transaction_options = TransactionOptions::builder() + session + .start_transaction() .read_concern(ReadConcern::snapshot()) .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build()) .selection_criteria(SelectionCriteria::ReadPreference(ReadPreference::Primary)) - .build(); - session.start_transaction(transaction_options).await?; + .await?; let client = session.client(); let employees = client.database("hr").collection::("employees"); let events = client.database("reporting").collection("events"); employees - .update_one_with_session( + .update_one( doc! { "employee": 3 }, doc! { "$set": { "status": "Inactive" } }, - None, - session, ) + .session(&mut *session) .await?; events - .insert_one_with_session( - doc! { "employee": 3, "status": { "new": "Inactive", "old": "Active" } }, - None, - session, - ) + .insert_one(doc! { "employee": 3, "status": { "new": "Inactive", "old": "Active" } }) + .session(&mut *session) .await?; commit_with_retry(session).await